webspresso 0.0.8 → 0.0.10

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/README.md CHANGED
@@ -73,9 +73,31 @@ webspresso new my-app --no-tailwind
73
73
  - Asks if you want to install in the current directory
74
74
  - If current directory is not empty, shows a warning
75
75
  - Prompts for project name (defaults to current folder name)
76
+ - After project creation, asks if you want to install dependencies
77
+ - If yes, runs `npm install` and `npm run build:css`
78
+ - Then asks if you want to start the development server
79
+ - If yes, starts `npm run dev` automatically
80
+
81
+ **Auto Installation:**
82
+ ```bash
83
+ # With --install flag (semi-interactive)
84
+ webspresso new my-app --install
85
+ # → Automatically runs: npm install && npm run build:css
86
+ # → Then prompts: "Start development server?" [Y/n]
87
+ # → If yes: starts npm run dev (with watch:css if Tailwind enabled)
88
+
89
+ # Without --install flag (fully interactive)
90
+ webspresso new my-app
91
+ # → Prompts: "Install dependencies and build CSS now?" [Y/n]
92
+ # → If yes: runs npm install && npm run build:css
93
+ # → Then: "Start development server?" [Y/n]
94
+ # → If yes: starts npm run dev (with watch:css if Tailwind enabled)
95
+ ```
96
+
97
+ **Note:** When dev server starts with Tailwind CSS, it automatically runs `watch:css` in the background to watch for CSS changes.
76
98
 
77
99
  Options:
78
- - `-i, --install` - Auto run `npm install` and `npm run build:css`
100
+ - `-i, --install` - Auto run `npm install` and `npm run build:css` (non-interactive)
79
101
  - `--no-tailwind` - Skip Tailwind CSS setup
80
102
 
81
103
  The project includes:
package/bin/webspresso.js CHANGED
@@ -429,8 +429,8 @@ module.exports = {
429
429
  console.log('✅ Tailwind CSS setup complete!');
430
430
  }
431
431
 
432
- // Auto install if requested
433
- if (autoInstall) {
432
+ // Helper function to run installation
433
+ async function runInstallation(projectPath, useTailwind) {
434
434
  console.log('\n📦 Installing dependencies...\n');
435
435
  const { execSync } = require('child_process');
436
436
  try {
@@ -447,27 +447,137 @@ module.exports = {
447
447
  });
448
448
  }
449
449
 
450
- console.log('\n✅ Project ready!\n');
450
+ console.log('\n✅ Installation complete!\n');
451
+ } catch (err) {
452
+ console.error('❌ Installation failed:', err.message);
453
+ process.exit(1);
454
+ }
455
+ }
456
+
457
+ // Helper function to start dev server
458
+ function startDevServer(projectPath, useTailwind) {
459
+ console.log('\n🚀 Starting development server...\n');
460
+ console.log('Press Ctrl+C to stop\n');
461
+
462
+ const { spawn } = require('child_process');
463
+
464
+ if (useTailwind) {
465
+ // Start CSS watch and server together
466
+ const cssWatch = spawn('npm', ['run', 'watch:css'], {
467
+ stdio: 'inherit',
468
+ shell: true,
469
+ cwd: projectPath
470
+ });
471
+
472
+ const server = spawn('node', ['--watch', 'server.js'], {
473
+ stdio: 'inherit',
474
+ shell: true,
475
+ cwd: projectPath,
476
+ env: { ...process.env, PORT: process.env.PORT || '3000', NODE_ENV: 'development' }
477
+ });
478
+
479
+ // Handle exit
480
+ const cleanup = () => {
481
+ cssWatch.kill();
482
+ server.kill();
483
+ process.exit(0);
484
+ };
485
+
486
+ process.on('SIGINT', cleanup);
487
+ process.on('SIGTERM', cleanup);
488
+
489
+ cssWatch.on('exit', cleanup);
490
+ server.on('exit', cleanup);
491
+ } else {
492
+ // Just start server
493
+ const server = spawn('node', ['--watch', 'server.js'], {
494
+ stdio: 'inherit',
495
+ shell: true,
496
+ cwd: projectPath,
497
+ env: { ...process.env, PORT: process.env.PORT || '3000', NODE_ENV: 'development' }
498
+ });
499
+
500
+ server.on('exit', (code) => {
501
+ process.exit(code || 0);
502
+ });
503
+
504
+ process.on('SIGINT', () => {
505
+ server.kill();
506
+ process.exit(0);
507
+ });
508
+ }
509
+ }
510
+
511
+ // Auto install if requested or ask interactively
512
+ if (autoInstall) {
513
+ await runInstallation(projectPath, useTailwind);
514
+
515
+ // Ask if user wants to start dev server
516
+ const { shouldStartDev } = await inquirer.prompt([
517
+ {
518
+ type: 'confirm',
519
+ name: 'shouldStartDev',
520
+ message: 'Start development server?',
521
+ default: true
522
+ }
523
+ ]);
524
+
525
+ if (shouldStartDev) {
526
+ startDevServer(projectPath, useTailwind);
527
+ } else {
528
+ console.log('✅ Project ready!\n');
451
529
  console.log('Start developing:');
452
530
  if (!useCurrentDir) {
453
531
  console.log(` cd ${projectName}`);
454
532
  }
455
533
  console.log(' npm run dev\n');
456
- } catch (err) {
457
- console.error('❌ Installation failed:', err.message);
458
- process.exit(1);
459
534
  }
460
535
  } else {
461
- console.log('\n✅ Project created successfully!\n');
462
- console.log('Next steps:');
463
- if (!useCurrentDir) {
464
- console.log(` cd ${projectName}`);
465
- }
466
- console.log(' npm install');
467
- if (useTailwind) {
468
- console.log(' npm run build:css');
536
+ // Ask if user wants to install dependencies
537
+ const { shouldInstall } = await inquirer.prompt([
538
+ {
539
+ type: 'confirm',
540
+ name: 'shouldInstall',
541
+ message: 'Install dependencies and build CSS now?',
542
+ default: true
543
+ }
544
+ ]);
545
+
546
+ if (shouldInstall) {
547
+ await runInstallation(projectPath, useTailwind);
548
+
549
+ // Ask if user wants to start dev server
550
+ const { shouldStartDev } = await inquirer.prompt([
551
+ {
552
+ type: 'confirm',
553
+ name: 'shouldStartDev',
554
+ message: 'Start development server?',
555
+ default: true
556
+ }
557
+ ]);
558
+
559
+ if (shouldStartDev) {
560
+ startDevServer(projectPath, useTailwind);
561
+ } else {
562
+ console.log('\n✅ Project ready!\n');
563
+ console.log('Start developing:');
564
+ if (!useCurrentDir) {
565
+ console.log(` cd ${projectName}`);
566
+ }
567
+ console.log(' npm run dev\n');
568
+ }
569
+ } else {
570
+ console.log('\n✅ Project created successfully!\n');
571
+ console.log('Next steps:');
572
+ if (!useCurrentDir) {
573
+ console.log(` cd ${projectName}`);
574
+ }
575
+ console.log(' npm install');
576
+ if (useTailwind) {
577
+ console.log(' npm run build:css');
578
+ }
579
+ console.log(' npm run dev\n');
469
580
  }
470
- console.log(' npm run dev\n');
471
581
  }
472
582
  });
473
583
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -33,7 +33,8 @@
33
33
  "bin/",
34
34
  "src/",
35
35
  "utils/",
36
- "core/"
36
+ "core/",
37
+ "plugins/"
37
38
  ],
38
39
  "dependencies": {
39
40
  "commander": "^11.1.0",
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Webspresso Analytics Plugin
3
+ * Adds tracking scripts and verification meta tags for Google, Yandex, and Bing
4
+ */
5
+
6
+ /**
7
+ * Generate Google Analytics (gtag.js) script
8
+ */
9
+ function generateGtagScript(config) {
10
+ if (!config || !config.measurementId) return '';
11
+
12
+ const { measurementId, adsId } = config;
13
+
14
+ let script = `<!-- Google Analytics -->
15
+ <script async src="https://www.googletagmanager.com/gtag/js?id=${measurementId}"></script>
16
+ <script>
17
+ window.dataLayer = window.dataLayer || [];
18
+ function gtag(){dataLayer.push(arguments);}
19
+ gtag('js', new Date());
20
+ gtag('config', '${measurementId}');`;
21
+
22
+ if (adsId) {
23
+ script += `
24
+ gtag('config', '${adsId}');`;
25
+ }
26
+
27
+ script += `
28
+ </script>`;
29
+
30
+ return script;
31
+ }
32
+
33
+ /**
34
+ * Generate Google Tag Manager script
35
+ */
36
+ function generateGtmScript(config) {
37
+ if (!config || !config.containerId) return '';
38
+
39
+ const { containerId } = config;
40
+
41
+ return `<!-- Google Tag Manager -->
42
+ <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
43
+ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
44
+ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
45
+ 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
46
+ })(window,document,'script','dataLayer','${containerId}');</script>`;
47
+ }
48
+
49
+ /**
50
+ * Generate Google Tag Manager noscript fallback
51
+ */
52
+ function generateGtmNoscript(config) {
53
+ if (!config || !config.containerId) return '';
54
+
55
+ return `<!-- Google Tag Manager (noscript) -->
56
+ <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${config.containerId}"
57
+ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`;
58
+ }
59
+
60
+ /**
61
+ * Generate Yandex.Metrika script
62
+ */
63
+ function generateYandexScript(config) {
64
+ if (!config || !config.counterId) return '';
65
+
66
+ const { counterId, clickmap = true, trackLinks = true, accurateTrackBounce = true, webvisor = false } = config;
67
+
68
+ return `<!-- Yandex.Metrika -->
69
+ <script>
70
+ (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
71
+ m[i].l=1*new Date();
72
+ for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
73
+ k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
74
+ (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
75
+
76
+ ym(${counterId}, "init", {
77
+ clickmap:${clickmap},
78
+ trackLinks:${trackLinks},
79
+ accurateTrackBounce:${accurateTrackBounce}${webvisor ? ',\n webvisor:true' : ''}
80
+ });
81
+ </script>
82
+ <noscript><div><img src="https://mc.yandex.ru/watch/${counterId}" style="position:absolute; left:-9999px;" alt="" /></div></noscript>`;
83
+ }
84
+
85
+ /**
86
+ * Generate Microsoft/Bing UET script
87
+ */
88
+ function generateBingScript(config) {
89
+ if (!config || !config.uetId) return '';
90
+
91
+ const { uetId } = config;
92
+
93
+ return `<!-- Microsoft UET -->
94
+ <script>
95
+ (function(w,d,t,r,u){var f,n,i;w[u]=w[u]||[],f=function(){var o={ti:"${uetId}",enableAutoSpaTracking:true};
96
+ o.q=w[u],w[u]=new UET(o),w[u].push("pageLoad")},n=d.createElement(t),n.src=r,n.async=1,n.onload=n.onreadystatechange=function(){
97
+ var s=this.readyState;s&&s!=="loaded"&&s!=="complete"||(f(),n.onload=n.onreadystatechange=null)},i=d.getElementsByTagName(t)[0],i.parentNode.insertBefore(n,i)
98
+ })(window,document,"script","//bat.bing.com/bat.js","uetq");
99
+ </script>`;
100
+ }
101
+
102
+ /**
103
+ * Generate Facebook Pixel script
104
+ */
105
+ function generateFacebookScript(config) {
106
+ if (!config || !config.pixelId) return '';
107
+
108
+ const { pixelId } = config;
109
+
110
+ return `<!-- Facebook Pixel -->
111
+ <script>
112
+ !function(f,b,e,v,n,t,s)
113
+ {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
114
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};
115
+ if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
116
+ n.queue=[];t=b.createElement(e);t.async=!0;
117
+ t.src=v;s=b.getElementsByTagName(e)[0];
118
+ s.parentNode.insertBefore(t,s)}(window, document,'script',
119
+ 'https://connect.facebook.net/en_US/fbevents.js');
120
+ fbq('init', '${pixelId}');
121
+ fbq('track', 'PageView');
122
+ </script>
123
+ <noscript><img height="1" width="1" style="display:none"
124
+ src="https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1"/></noscript>`;
125
+ }
126
+
127
+ /**
128
+ * Generate verification meta tags
129
+ */
130
+ function generateVerificationTags(options) {
131
+ const tags = [];
132
+
133
+ if (options.google?.verificationCode) {
134
+ tags.push(`<meta name="google-site-verification" content="${options.google.verificationCode}">`);
135
+ }
136
+
137
+ if (options.yandex?.verificationCode) {
138
+ tags.push(`<meta name="yandex-verification" content="${options.yandex.verificationCode}">`);
139
+ }
140
+
141
+ if (options.bing?.verificationCode) {
142
+ tags.push(`<meta name="msvalidate.01" content="${options.bing.verificationCode}">`);
143
+ }
144
+
145
+ if (options.facebook?.domainVerification) {
146
+ tags.push(`<meta name="facebook-domain-verification" content="${options.facebook.domainVerification}">`);
147
+ }
148
+
149
+ if (options.pinterest?.verificationCode) {
150
+ tags.push(`<meta name="p:domain_verify" content="${options.pinterest.verificationCode}">`);
151
+ }
152
+
153
+ return tags.join('\n');
154
+ }
155
+
156
+ /**
157
+ * Create the analytics plugin
158
+ * @param {Object} options - Plugin options
159
+ * @param {Object} options.google - Google Analytics config
160
+ * @param {string} options.google.measurementId - GA4 Measurement ID (G-XXXXXXXXXX)
161
+ * @param {string} options.google.adsId - Google Ads ID (AW-XXXXXXXXXX)
162
+ * @param {string} options.google.verificationCode - Search Console verification
163
+ * @param {Object} options.gtm - Google Tag Manager config
164
+ * @param {string} options.gtm.containerId - GTM Container ID (GTM-XXXXXXX)
165
+ * @param {Object} options.yandex - Yandex.Metrika config
166
+ * @param {string} options.yandex.counterId - Yandex counter ID
167
+ * @param {string} options.yandex.verificationCode - Yandex Webmaster verification
168
+ * @param {boolean} options.yandex.webvisor - Enable Webvisor
169
+ * @param {Object} options.bing - Microsoft/Bing config
170
+ * @param {string} options.bing.uetId - UET tag ID
171
+ * @param {string} options.bing.verificationCode - Bing Webmaster verification
172
+ * @param {Object} options.facebook - Facebook/Meta config
173
+ * @param {string} options.facebook.pixelId - Facebook Pixel ID
174
+ * @param {string} options.facebook.domainVerification - Domain verification code
175
+ */
176
+ function analyticsPlugin(options = {}) {
177
+ const { google, gtm, yandex, bing, facebook, pinterest } = options;
178
+
179
+ return {
180
+ name: 'analytics',
181
+ version: '1.0.0',
182
+ _options: options,
183
+
184
+ /**
185
+ * Public API for other plugins
186
+ */
187
+ api: {
188
+ /**
189
+ * Get configuration
190
+ */
191
+ getConfig() {
192
+ return { ...options };
193
+ },
194
+
195
+ /**
196
+ * Check if a tracker is configured
197
+ */
198
+ hasTracker(name) {
199
+ return !!options[name];
200
+ }
201
+ },
202
+
203
+ /**
204
+ * Register helpers
205
+ */
206
+ register(ctx) {
207
+ // Google Analytics (gtag.js)
208
+ ctx.addHelper('gtag', () => generateGtagScript(google));
209
+
210
+ // Google Tag Manager
211
+ ctx.addHelper('gtm', () => generateGtmScript(gtm));
212
+ ctx.addHelper('gtmNoscript', () => generateGtmNoscript(gtm));
213
+
214
+ // Yandex.Metrika
215
+ ctx.addHelper('yandexMetrika', () => generateYandexScript(yandex));
216
+
217
+ // Microsoft/Bing UET
218
+ ctx.addHelper('bingUET', () => generateBingScript(bing));
219
+
220
+ // Facebook Pixel
221
+ ctx.addHelper('facebookPixel', () => generateFacebookScript(facebook));
222
+
223
+ // Verification meta tags
224
+ ctx.addHelper('verificationTags', () => generateVerificationTags(options));
225
+
226
+ // All analytics scripts combined
227
+ ctx.addHelper('allAnalytics', () => {
228
+ const scripts = [];
229
+
230
+ if (google) scripts.push(generateGtagScript(google));
231
+ if (gtm) scripts.push(generateGtmScript(gtm));
232
+ if (yandex) scripts.push(generateYandexScript(yandex));
233
+ if (bing) scripts.push(generateBingScript(bing));
234
+ if (facebook) scripts.push(generateFacebookScript(facebook));
235
+
236
+ return scripts.filter(Boolean).join('\n\n');
237
+ });
238
+
239
+ // Head scripts (verification + analytics)
240
+ ctx.addHelper('analyticsHead', () => {
241
+ const parts = [];
242
+
243
+ // Verification tags first
244
+ const verificationTags = generateVerificationTags(options);
245
+ if (verificationTags) parts.push(verificationTags);
246
+
247
+ // GTM should be as early as possible
248
+ if (gtm) parts.push(generateGtmScript(gtm));
249
+
250
+ // Other analytics
251
+ if (google) parts.push(generateGtagScript(google));
252
+ if (yandex) parts.push(generateYandexScript(yandex));
253
+ if (bing) parts.push(generateBingScript(bing));
254
+ if (facebook) parts.push(generateFacebookScript(facebook));
255
+
256
+ return parts.filter(Boolean).join('\n\n');
257
+ });
258
+
259
+ // Body open scripts (GTM noscript)
260
+ ctx.addHelper('analyticsBodyOpen', () => {
261
+ return generateGtmNoscript(gtm);
262
+ });
263
+ }
264
+ };
265
+ }
266
+
267
+ module.exports = analyticsPlugin;
268
+
269
+
270
+