webspresso 0.0.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.
@@ -0,0 +1,790 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Webspresso CLI
5
+ * Command-line interface for Webspresso framework
6
+ */
7
+
8
+ const { program } = require('commander');
9
+ const inquirer = require('inquirer');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { spawn } = require('child_process');
13
+
14
+ program
15
+ .name('webspresso')
16
+ .description('Webspresso CLI - Minimal file-based SSR framework')
17
+ .version(require('../package.json').version);
18
+
19
+ // New project command
20
+ program
21
+ .command('new <project-name>')
22
+ .description('Create a new Webspresso project')
23
+ .option('-t, --template <template>', 'Template to use (minimal, full)', 'minimal')
24
+ .option('--no-tailwind', 'Skip Tailwind CSS setup')
25
+ .option('-i, --install', 'Auto install dependencies and build CSS')
26
+ .action(async (projectName, options) => {
27
+ const useTailwind = options.tailwind !== false;
28
+ const autoInstall = options.install === true;
29
+ const projectPath = path.resolve(projectName);
30
+
31
+ if (fs.existsSync(projectPath)) {
32
+ console.error(`āŒ Directory ${projectName} already exists!`);
33
+ process.exit(1);
34
+ }
35
+
36
+ console.log(`\nšŸš€ Creating new Webspresso project: ${projectName}\n`);
37
+
38
+ // Create directory structure
39
+ fs.mkdirSync(projectPath, { recursive: true });
40
+ fs.mkdirSync(path.join(projectPath, 'pages'), { recursive: true });
41
+ fs.mkdirSync(path.join(projectPath, 'pages', 'locales'), { recursive: true });
42
+ fs.mkdirSync(path.join(projectPath, 'views'), { recursive: true });
43
+ fs.mkdirSync(path.join(projectPath, 'public'), { recursive: true });
44
+
45
+ // Create package.json
46
+ const packageJson = {
47
+ name: projectName,
48
+ version: '1.0.0',
49
+ description: 'Webspresso project',
50
+ main: 'server.js',
51
+ scripts: {
52
+ dev: 'node --watch server.js',
53
+ start: 'NODE_ENV=production node server.js'
54
+ },
55
+ dependencies: {
56
+ webspresso: '^1.0.0',
57
+ dotenv: '^16.3.1'
58
+ }
59
+ };
60
+
61
+ fs.writeFileSync(
62
+ path.join(projectPath, 'package.json'),
63
+ JSON.stringify(packageJson, null, 2) + '\n'
64
+ );
65
+
66
+ // Create server.js
67
+ const serverJs = `require('dotenv').config();
68
+ const { createApp } = require('webspresso');
69
+ const path = require('path');
70
+
71
+ const { app } = createApp({
72
+ pagesDir: path.join(__dirname, 'pages'),
73
+ viewsDir: path.join(__dirname, 'views'),
74
+ publicDir: path.join(__dirname, 'public')
75
+ });
76
+
77
+ const PORT = process.env.PORT || 3000;
78
+
79
+ app.listen(PORT, () => {
80
+ console.log(\`šŸš€ Server running at http://localhost:\${PORT}\`);
81
+ });
82
+ `;
83
+
84
+ fs.writeFileSync(path.join(projectPath, 'server.js'), serverJs);
85
+
86
+ // Create .env.example
87
+ const envExample = `PORT=3000
88
+ NODE_ENV=development
89
+ DEFAULT_LOCALE=en
90
+ SUPPORTED_LOCALES=en,tr
91
+ BASE_URL=http://localhost:3000
92
+ `;
93
+
94
+ fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
95
+
96
+ // Create .gitignore
97
+ const gitignore = `node_modules/
98
+ .env
99
+ .env.local
100
+ .DS_Store
101
+ coverage/
102
+ *.log
103
+ `;
104
+
105
+ fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
106
+
107
+ // Create default layout
108
+ let layoutNjk;
109
+
110
+ if (useTailwind) {
111
+ layoutNjk = `<!DOCTYPE html>
112
+ <html lang="{{ locale or 'en' }}">
113
+ <head>
114
+ <meta charset="UTF-8">
115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
116
+ <title>{{ meta.title or 'Webspresso' }}</title>
117
+ {% if meta.description %}
118
+ <meta name="description" content="{{ meta.description }}">
119
+ {% endif %}
120
+ {% if meta.canonical %}
121
+ <link rel="canonical" href="{{ meta.canonical }}">
122
+ {% else %}
123
+ <link rel="canonical" href="{{ fsy.canonical() }}">
124
+ {% endif %}
125
+ <link rel="stylesheet" href="/css/style.css">
126
+ </head>
127
+ <body class="min-h-screen flex flex-col bg-gray-50">
128
+ <nav class="bg-white shadow-sm border-b border-gray-200">
129
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
130
+ <div class="flex justify-between h-16">
131
+ <div class="flex items-center">
132
+ <a href="/" class="text-xl font-bold text-gray-900 hover:text-gray-700">
133
+ {{ t('site.name') or 'Webspresso' }}
134
+ </a>
135
+ </div>
136
+ <div class="flex items-center space-x-4">
137
+ <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 %}">
138
+ {{ t('nav.home') or 'Home' }}
139
+ </a>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </nav>
144
+
145
+ <main class="flex-1 max-w-7xl mx-auto py-6 sm:px-6 lg:px-8 w-full">
146
+ {% block content %}{% endblock %}
147
+ </main>
148
+
149
+ <footer class="bg-white border-t border-gray-200">
150
+ <div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
151
+ <p class="text-center text-sm text-gray-500">
152
+ {{ t('footer.copyright') or 'Ā© 2025 Webspresso. All rights reserved.' }}
153
+ </p>
154
+ </div>
155
+ </footer>
156
+ </body>
157
+ </html>
158
+ `;
159
+ } else {
160
+ layoutNjk = `<!DOCTYPE html>
161
+ <html lang="{{ locale or 'en' }}">
162
+ <head>
163
+ <meta charset="UTF-8">
164
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
165
+ <title>{{ meta.title or 'Webspresso' }}</title>
166
+ {% if meta.description %}
167
+ <meta name="description" content="{{ meta.description }}">
168
+ {% endif %}
169
+ <script src="https://cdn.tailwindcss.com"></script>
170
+ </head>
171
+ <body>
172
+ <main>
173
+ {% block content %}{% endblock %}
174
+ </main>
175
+ </body>
176
+ </html>
177
+ `;
178
+ }
179
+
180
+ fs.writeFileSync(path.join(projectPath, 'views', 'layout.njk'), layoutNjk);
181
+
182
+ // Create index page
183
+ let indexNjk;
184
+
185
+ if (useTailwind) {
186
+ indexNjk = `{% extends "layout.njk" %}
187
+
188
+ {% block content %}
189
+ <div class="px-4 py-16 sm:px-6 lg:px-8">
190
+ <div class="text-center">
191
+ <h1 class="text-4xl font-bold text-gray-900 sm:text-5xl md:text-6xl">
192
+ {{ t('welcome') or 'Welcome to Webspresso' }}
193
+ </h1>
194
+ <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">
195
+ {{ t('description') or 'Start building your SSR app!' }}
196
+ </p>
197
+ <div class="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
198
+ <div class="rounded-md shadow">
199
+ <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">
200
+ Get started
201
+ </a>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ {% endblock %}
207
+ `;
208
+ } else {
209
+ indexNjk = `{% extends "layout.njk" %}
210
+
211
+ {% block content %}
212
+ <div>
213
+ <h1>{{ t('welcome') or 'Welcome to Webspresso' }}</h1>
214
+ <p>{{ t('description') or 'Start building your SSR app!' }}</p>
215
+ </div>
216
+ {% endblock %}
217
+ `;
218
+ }
219
+
220
+ fs.writeFileSync(path.join(projectPath, 'pages', 'index.njk'), indexNjk);
221
+
222
+ // Create locales
223
+ const enJson = {
224
+ site: {
225
+ name: 'Webspresso'
226
+ },
227
+ nav: {
228
+ home: 'Home'
229
+ },
230
+ footer: {
231
+ copyright: 'Ā© 2025 Webspresso. All rights reserved.'
232
+ },
233
+ welcome: 'Welcome to Webspresso',
234
+ description: 'Start building your SSR app!'
235
+ };
236
+
237
+ fs.writeFileSync(
238
+ path.join(projectPath, 'pages', 'locales', 'en.json'),
239
+ JSON.stringify(enJson, null, 2) + '\n'
240
+ );
241
+
242
+ const trJson = {
243
+ site: {
244
+ name: 'Webspresso'
245
+ },
246
+ nav: {
247
+ home: 'Ana Sayfa'
248
+ },
249
+ footer: {
250
+ copyright: '© 2025 Webspresso. Tüm hakları saklıdır.'
251
+ },
252
+ welcome: 'Webspresso\'ya Hoş Geldiniz',
253
+ description: 'SSR uygulamanızı oluşturmaya başlayın!'
254
+ };
255
+
256
+ fs.writeFileSync(
257
+ path.join(projectPath, 'pages', 'locales', 'tr.json'),
258
+ JSON.stringify(trJson, null, 2) + '\n'
259
+ );
260
+
261
+ // Create README
262
+ const readme = `# ${projectName}
263
+
264
+ Webspresso project
265
+
266
+ ## Getting Started
267
+
268
+ \`\`\`bash
269
+ npm install
270
+ npm run dev
271
+ \`\`\`
272
+
273
+ Visit http://localhost:3000
274
+ `;
275
+
276
+ fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
277
+
278
+ // Add Tailwind if requested (default: true)
279
+ if (useTailwind) {
280
+ console.log('\nšŸŽØ Setting up Tailwind CSS...\n');
281
+
282
+ // Create src directory
283
+ fs.mkdirSync(path.join(projectPath, 'src'), { recursive: true });
284
+
285
+ // Create input.css
286
+ const inputCss = `@tailwind base;
287
+ @tailwind components;
288
+ @tailwind utilities;
289
+ `;
290
+ fs.writeFileSync(path.join(projectPath, 'src', 'input.css'), inputCss);
291
+
292
+ // Create tailwind.config.js
293
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
294
+ module.exports = {
295
+ content: [
296
+ './pages/**/*.{njk,js}',
297
+ './views/**/*.njk',
298
+ './src/**/*.js'
299
+ ],
300
+ theme: {
301
+ extend: {},
302
+ },
303
+ plugins: [],
304
+ }
305
+ `;
306
+ fs.writeFileSync(path.join(projectPath, 'tailwind.config.js'), tailwindConfig);
307
+
308
+ // Create postcss.config.js
309
+ const postcssConfig = `module.exports = {
310
+ plugins: {
311
+ tailwindcss: {},
312
+ autoprefixer: {},
313
+ },
314
+ }
315
+ `;
316
+ fs.writeFileSync(path.join(projectPath, 'postcss.config.js'), postcssConfig);
317
+
318
+ // Create public/css directory
319
+ fs.mkdirSync(path.join(projectPath, 'public', 'css'), { recursive: true });
320
+
321
+ // Create placeholder CSS (will be replaced by build)
322
+ fs.writeFileSync(path.join(projectPath, 'public', 'css', 'style.css'), '/* Run npm run build:css */\n');
323
+
324
+ // Update package.json
325
+ const updatedPackageJson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
326
+ updatedPackageJson.devDependencies = updatedPackageJson.devDependencies || {};
327
+ updatedPackageJson.devDependencies['tailwindcss'] = '^3.4.1';
328
+ updatedPackageJson.devDependencies['postcss'] = '^8.4.35';
329
+ updatedPackageJson.devDependencies['autoprefixer'] = '^10.4.17';
330
+
331
+ updatedPackageJson.scripts['build:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --minify';
332
+ updatedPackageJson.scripts['watch:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --watch';
333
+ updatedPackageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
334
+ updatedPackageJson.scripts.start = 'npm run build:css && NODE_ENV=production node server.js';
335
+
336
+ fs.writeFileSync(
337
+ path.join(projectPath, 'package.json'),
338
+ JSON.stringify(updatedPackageJson, null, 2) + '\n'
339
+ );
340
+
341
+ console.log('āœ… Tailwind CSS setup complete!');
342
+ }
343
+
344
+ // Auto install if requested
345
+ if (autoInstall) {
346
+ console.log('\nšŸ“¦ Installing dependencies...\n');
347
+ const { execSync } = require('child_process');
348
+ try {
349
+ execSync('npm install', {
350
+ stdio: 'inherit',
351
+ cwd: projectPath
352
+ });
353
+
354
+ if (useTailwind) {
355
+ console.log('\nšŸŽØ Building Tailwind CSS...\n');
356
+ execSync('npm run build:css', {
357
+ stdio: 'inherit',
358
+ cwd: projectPath
359
+ });
360
+ }
361
+
362
+ console.log('\nāœ… Project ready!\n');
363
+ console.log('Start developing:');
364
+ console.log(` cd ${projectName}`);
365
+ console.log(' npm run dev\n');
366
+ } catch (err) {
367
+ console.error('āŒ Installation failed:', err.message);
368
+ process.exit(1);
369
+ }
370
+ } else {
371
+ console.log('\nāœ… Project created successfully!\n');
372
+ console.log('Next steps:');
373
+ console.log(` cd ${projectName}`);
374
+ console.log(' npm install');
375
+ if (useTailwind) {
376
+ console.log(' npm run build:css');
377
+ }
378
+ console.log(' npm run dev\n');
379
+ }
380
+ });
381
+
382
+ // Add page command
383
+ program
384
+ .command('page')
385
+ .description('Add a new page to the current project')
386
+ .action(async () => {
387
+ if (!fs.existsSync('pages')) {
388
+ console.error('āŒ Not a Webspresso project! Run this command in your project directory.');
389
+ process.exit(1);
390
+ }
391
+
392
+ const answers = await inquirer.prompt([
393
+ {
394
+ type: 'input',
395
+ name: 'route',
396
+ message: 'Route path (e.g., /about or /blog/post):',
397
+ validate: (input) => {
398
+ if (!input.startsWith('/')) {
399
+ return 'Route must start with /';
400
+ }
401
+ return true;
402
+ }
403
+ },
404
+ {
405
+ type: 'confirm',
406
+ name: 'hasConfig',
407
+ message: 'Add route config file (.js)?',
408
+ default: false
409
+ },
410
+ {
411
+ type: 'confirm',
412
+ name: 'hasLocales',
413
+ message: 'Add locale files?',
414
+ default: false
415
+ }
416
+ ]);
417
+
418
+ const route = answers.route.replace(/^\//, '');
419
+ const routePath = path.join('pages', route);
420
+ const dirPath = path.dirname(routePath);
421
+ const fileName = path.basename(routePath);
422
+
423
+ // Create directory
424
+ fs.mkdirSync(dirPath, { recursive: true });
425
+
426
+ // Create .njk file
427
+ const templateName = fileName === 'index' ? 'index' : fileName;
428
+ const njkFile = path.join(dirPath, `${templateName}.njk`);
429
+
430
+ const njkContent = `{% extends "layout.njk" %}
431
+
432
+ {% block content %}
433
+ <div>
434
+ <h1>{{ t('title') or '${route}' }}</h1>
435
+ <p>{{ t('description') or 'Page content' }}</p>
436
+ </div>
437
+ {% endblock %}
438
+ `;
439
+
440
+ fs.writeFileSync(njkFile, njkContent);
441
+ console.log(`āœ… Created ${njkFile}`);
442
+
443
+ // Create config file if requested
444
+ if (answers.hasConfig) {
445
+ const jsFile = path.join(dirPath, `${templateName}.js`);
446
+ const jsContent = `module.exports = {
447
+ async load(req, ctx) {
448
+ return {
449
+ // Add your data here
450
+ };
451
+ },
452
+
453
+ meta(req, ctx) {
454
+ return {
455
+ title: ctx.t('meta.title') || '${route}',
456
+ description: ctx.t('meta.description') || ''
457
+ };
458
+ }
459
+ };
460
+ `;
461
+
462
+ fs.writeFileSync(jsFile, jsContent);
463
+ console.log(`āœ… Created ${jsFile}`);
464
+ }
465
+
466
+ // Create locales if requested
467
+ if (answers.hasLocales) {
468
+ const localesDir = path.join(dirPath, 'locales');
469
+ fs.mkdirSync(localesDir, { recursive: true });
470
+
471
+ const enContent = {
472
+ title: route,
473
+ description: 'Page description',
474
+ meta: {
475
+ title: `${route} - Webspresso`,
476
+ description: 'Page description'
477
+ }
478
+ };
479
+
480
+ fs.writeFileSync(
481
+ path.join(localesDir, 'en.json'),
482
+ JSON.stringify(enContent, null, 2) + '\n'
483
+ );
484
+
485
+ const trContent = {
486
+ title: route,
487
+ description: 'Sayfa aƧıklaması',
488
+ meta: {
489
+ title: `${route} - Webspresso`,
490
+ description: 'Sayfa aƧıklaması'
491
+ }
492
+ };
493
+
494
+ fs.writeFileSync(
495
+ path.join(localesDir, 'tr.json'),
496
+ JSON.stringify(trContent, null, 2) + '\n'
497
+ );
498
+
499
+ console.log(`āœ… Created locale files in ${localesDir}`);
500
+ }
501
+
502
+ console.log(`\nāœ… Page created at ${route}\n`);
503
+ });
504
+
505
+ // Add API command
506
+ program
507
+ .command('api')
508
+ .description('Add a new API endpoint to the current project')
509
+ .action(async () => {
510
+ if (!fs.existsSync('pages')) {
511
+ console.error('āŒ Not a Webspresso project! Run this command in your project directory.');
512
+ process.exit(1);
513
+ }
514
+
515
+ const answers = await inquirer.prompt([
516
+ {
517
+ type: 'input',
518
+ name: 'route',
519
+ message: 'API route path (e.g., /api/users or /api/users/[id]):',
520
+ validate: (input) => {
521
+ if (!input.startsWith('/api/')) {
522
+ return 'API route must start with /api/';
523
+ }
524
+ return true;
525
+ }
526
+ },
527
+ {
528
+ type: 'list',
529
+ name: 'method',
530
+ message: 'HTTP method:',
531
+ choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
532
+ default: 'GET'
533
+ }
534
+ ]);
535
+
536
+ const route = answers.route.replace(/^\/api\//, '');
537
+ const routePath = path.join('pages', 'api', route);
538
+ const dirPath = path.dirname(routePath);
539
+ const fileName = path.basename(routePath);
540
+
541
+ // Create directory
542
+ fs.mkdirSync(dirPath, { recursive: true });
543
+
544
+ // Create API file
545
+ const apiFile = path.join(dirPath, `${fileName}.${answers.method.toLowerCase()}.js`);
546
+
547
+ const apiContent = `/**
548
+ * ${answers.method} ${answers.route}
549
+ */
550
+
551
+ module.exports = async function handler(req, res) {
552
+ res.json({
553
+ message: 'Hello from ${answers.route}',
554
+ method: '${answers.method}',
555
+ timestamp: new Date().toISOString()
556
+ });
557
+ };
558
+ `;
559
+
560
+ fs.writeFileSync(apiFile, apiContent);
561
+ console.log(`\nāœ… Created ${apiFile}\n`);
562
+ });
563
+
564
+ // Dev command
565
+ program
566
+ .command('dev')
567
+ .description('Start development server')
568
+ .option('-p, --port <port>', 'Port number', '3000')
569
+ .option('--no-css', 'Skip CSS watch (if Tailwind is set up)')
570
+ .action((options) => {
571
+ if (!fs.existsSync('server.js')) {
572
+ console.error('āŒ server.js not found! Make sure you are in a Webspresso project.');
573
+ process.exit(1);
574
+ }
575
+
576
+ process.env.PORT = options.port;
577
+ process.env.NODE_ENV = 'development';
578
+
579
+ const hasTailwind = fs.existsSync('tailwind.config.js') && fs.existsSync('src/input.css');
580
+ const shouldWatchCss = hasTailwind && options.css !== false;
581
+
582
+ if (shouldWatchCss) {
583
+ console.log(`\nšŸš€ Starting development server on port ${options.port}...`);
584
+ console.log(' Watching CSS and server files...\n');
585
+
586
+ // Start CSS watch
587
+ const cssWatch = spawn('npm', ['run', 'watch:css'], {
588
+ stdio: 'inherit',
589
+ shell: true
590
+ });
591
+
592
+ // Start server
593
+ const server = spawn('node', ['--watch', 'server.js'], {
594
+ stdio: 'inherit',
595
+ shell: true,
596
+ env: { ...process.env, PORT: options.port, NODE_ENV: 'development' }
597
+ });
598
+
599
+ // Handle exit
600
+ const cleanup = () => {
601
+ cssWatch.kill();
602
+ server.kill();
603
+ process.exit(0);
604
+ };
605
+
606
+ process.on('SIGINT', cleanup);
607
+ process.on('SIGTERM', cleanup);
608
+
609
+ cssWatch.on('exit', cleanup);
610
+ server.on('exit', cleanup);
611
+ } else {
612
+ console.log(`\nšŸš€ Starting development server on port ${options.port}...\n`);
613
+
614
+ const { spawn } = require('child_process');
615
+ const child = spawn('node', ['--watch', 'server.js'], {
616
+ stdio: 'inherit',
617
+ shell: true,
618
+ env: { ...process.env, PORT: options.port, NODE_ENV: 'development' }
619
+ });
620
+
621
+ child.on('exit', (code) => {
622
+ process.exit(code || 0);
623
+ });
624
+ }
625
+ });
626
+
627
+ // Start command
628
+ program
629
+ .command('start')
630
+ .description('Start production server')
631
+ .option('-p, --port <port>', 'Port number', '3000')
632
+ .action((options) => {
633
+ if (!fs.existsSync('server.js')) {
634
+ console.error('āŒ server.js not found! Make sure you are in a Webspresso project.');
635
+ process.exit(1);
636
+ }
637
+
638
+ process.env.PORT = options.port;
639
+ process.env.NODE_ENV = 'production';
640
+
641
+ console.log(`\nšŸš€ Starting production server on port ${options.port}...\n`);
642
+
643
+ const serverPath = path.resolve(process.cwd(), 'server.js');
644
+ require(serverPath);
645
+ });
646
+
647
+ // Add Tailwind command
648
+ program
649
+ .command('add tailwind')
650
+ .description('Add Tailwind CSS to the project with build process')
651
+ .action(async () => {
652
+ if (!fs.existsSync('package.json')) {
653
+ console.error('āŒ Not a Webspresso project! Run this command in your project directory.');
654
+ process.exit(1);
655
+ }
656
+
657
+ console.log('\nšŸŽØ Adding Tailwind CSS to your project...\n');
658
+
659
+ // Read package.json
660
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
661
+
662
+ // Add dev dependencies
663
+ if (!packageJson.devDependencies) {
664
+ packageJson.devDependencies = {};
665
+ }
666
+
667
+ packageJson.devDependencies['tailwindcss'] = '^3.4.1';
668
+ packageJson.devDependencies['postcss'] = '^8.4.35';
669
+ packageJson.devDependencies['autoprefixer'] = '^10.4.17';
670
+
671
+ // Add build scripts
672
+ if (!packageJson.scripts) {
673
+ packageJson.scripts = {};
674
+ }
675
+
676
+ packageJson.scripts['build:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --minify';
677
+ packageJson.scripts['watch:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --watch';
678
+
679
+ // Update dev script to include CSS watch
680
+ if (packageJson.scripts.dev) {
681
+ packageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
682
+ }
683
+
684
+ // Update start script to build CSS
685
+ if (packageJson.scripts.start) {
686
+ packageJson.scripts.start = 'npm run build:css && NODE_ENV=production node server.js';
687
+ }
688
+
689
+ fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
690
+ console.log('āœ… Updated package.json');
691
+
692
+ // Create src directory if it doesn't exist
693
+ if (!fs.existsSync('src')) {
694
+ fs.mkdirSync('src', { recursive: true });
695
+ }
696
+
697
+ // Create input.css
698
+ const inputCss = `@tailwind base;
699
+ @tailwind components;
700
+ @tailwind utilities;
701
+ `;
702
+
703
+ fs.writeFileSync('src/input.css', inputCss);
704
+ console.log('āœ… Created src/input.css');
705
+
706
+ // Create tailwind.config.js
707
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
708
+ module.exports = {
709
+ content: [
710
+ './pages/**/*.{njk,js}',
711
+ './views/**/*.njk',
712
+ './src/**/*.js'
713
+ ],
714
+ theme: {
715
+ extend: {},
716
+ },
717
+ plugins: [],
718
+ }
719
+ `;
720
+
721
+ fs.writeFileSync('tailwind.config.js', tailwindConfig);
722
+ console.log('āœ… Created tailwind.config.js');
723
+
724
+ // Create postcss.config.js
725
+ const postcssConfig = `module.exports = {
726
+ plugins: {
727
+ tailwindcss: {},
728
+ autoprefixer: {},
729
+ },
730
+ }
731
+ `;
732
+
733
+ fs.writeFileSync('postcss.config.js', postcssConfig);
734
+ console.log('āœ… Created postcss.config.js');
735
+
736
+ // Check if layout.njk exists and update it (before creating CSS)
737
+ const layoutPath = 'views/layout.njk';
738
+ if (fs.existsSync(layoutPath)) {
739
+ let layoutContent = fs.readFileSync(layoutPath, 'utf-8');
740
+
741
+ // Remove CDN script if exists
742
+ layoutContent = layoutContent.replace(
743
+ /<script src="https:\/\/cdn\.tailwindcss\.com"><\/script>/g,
744
+ ''
745
+ );
746
+
747
+ // Add local CSS link if not exists
748
+ if (!layoutContent.includes('/css/style.css')) {
749
+ layoutContent = layoutContent.replace(
750
+ /(<\/head>)/,
751
+ ' <link rel="stylesheet" href="/css/style.css">\n$1'
752
+ );
753
+ }
754
+
755
+ fs.writeFileSync(layoutPath, layoutContent);
756
+ console.log('āœ… Updated views/layout.njk');
757
+ }
758
+
759
+ // Create public/css directory
760
+ if (!fs.existsSync('public/css')) {
761
+ fs.mkdirSync('public/css', { recursive: true });
762
+ }
763
+
764
+ // Create placeholder CSS
765
+ fs.writeFileSync('public/css/style.css', '/* Run npm run build:css */\n');
766
+ console.log('āœ… Created public/css/style.css');
767
+
768
+ // Try to build CSS if tailwindcss is already installed
769
+ const tailwindBin = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss');
770
+ if (fs.existsSync(tailwindBin)) {
771
+ try {
772
+ const { execSync } = require('child_process');
773
+ console.log('\nšŸŽØ Building Tailwind CSS from your templates...');
774
+ execSync('npm run build:css', { stdio: 'inherit', cwd: process.cwd() });
775
+ console.log('āœ… Tailwind CSS built successfully!\n');
776
+ } catch (err) {
777
+ console.log('\nāš ļø CSS build failed. Run "npm run build:css" manually.\n');
778
+ }
779
+ } else {
780
+ console.log('\nāœ… Tailwind CSS added successfully!\n');
781
+ console.log('Next steps:');
782
+ console.log(' npm install');
783
+ console.log(' npm run build:css');
784
+ console.log(' npm run dev\n');
785
+ }
786
+ });
787
+
788
+ // Parse arguments
789
+ program.parse();
790
+