webspresso 0.0.7 → 0.0.9
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 +12 -2
- package/bin/webspresso.js +102 -10
- package/package.json +3 -2
- package/plugins/analytics.js +270 -0
- package/plugins/dashboard/app.js +267 -0
- package/plugins/dashboard/index.js +142 -0
- package/plugins/dashboard/styles.js +384 -0
- package/plugins/index.js +17 -0
- package/plugins/schema-explorer.js +398 -0
- package/plugins/sitemap.js +221 -0
package/README.md
CHANGED
|
@@ -49,13 +49,19 @@ npm run dev
|
|
|
49
49
|
|
|
50
50
|
## CLI Commands
|
|
51
51
|
|
|
52
|
-
### `webspresso new
|
|
52
|
+
### `webspresso new [project-name]`
|
|
53
53
|
|
|
54
54
|
Create a new Webspresso project with Tailwind CSS (default).
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
+
# Create in a new directory
|
|
57
58
|
webspresso new my-app
|
|
58
59
|
|
|
60
|
+
# Create in current directory (interactive)
|
|
61
|
+
webspresso new
|
|
62
|
+
# → Prompts: "Install in current directory?"
|
|
63
|
+
# → If yes, asks for project name (for package.json)
|
|
64
|
+
|
|
59
65
|
# Auto install dependencies and build CSS
|
|
60
66
|
webspresso new my-app --install
|
|
61
67
|
|
|
@@ -63,6 +69,11 @@ webspresso new my-app --install
|
|
|
63
69
|
webspresso new my-app --no-tailwind
|
|
64
70
|
```
|
|
65
71
|
|
|
72
|
+
**Interactive Mode (no arguments):**
|
|
73
|
+
- Asks if you want to install in the current directory
|
|
74
|
+
- If current directory is not empty, shows a warning
|
|
75
|
+
- Prompts for project name (defaults to current folder name)
|
|
76
|
+
|
|
66
77
|
Options:
|
|
67
78
|
- `-i, --install` - Auto run `npm install` and `npm run build:css`
|
|
68
79
|
- `--no-tailwind` - Skip Tailwind CSS setup
|
|
@@ -602,7 +613,6 @@ module.exports = {
|
|
|
602
613
|
Add JSON files to `pages/locales/`:
|
|
603
614
|
|
|
604
615
|
```json
|
|
605
|
-
// pages/locales/en.json
|
|
606
616
|
{
|
|
607
617
|
"nav": {
|
|
608
618
|
"home": "Home",
|
package/bin/webspresso.js
CHANGED
|
@@ -18,25 +18,113 @@ program
|
|
|
18
18
|
|
|
19
19
|
// New project command
|
|
20
20
|
program
|
|
21
|
-
.command('new
|
|
21
|
+
.command('new [project-name]')
|
|
22
22
|
.description('Create a new Webspresso project')
|
|
23
23
|
.option('-t, --template <template>', 'Template to use (minimal, full)', 'minimal')
|
|
24
24
|
.option('--no-tailwind', 'Skip Tailwind CSS setup')
|
|
25
25
|
.option('-i, --install', 'Auto install dependencies and build CSS')
|
|
26
|
-
.action(async (
|
|
26
|
+
.action(async (projectNameArg, options) => {
|
|
27
27
|
const useTailwind = options.tailwind !== false;
|
|
28
28
|
const autoInstall = options.install === true;
|
|
29
|
-
const projectPath = path.resolve(projectName);
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
let projectName;
|
|
31
|
+
let projectPath;
|
|
32
|
+
let useCurrentDir = false;
|
|
33
|
+
|
|
34
|
+
if (!projectNameArg) {
|
|
35
|
+
// No project name provided - ask if they want to use current directory
|
|
36
|
+
const currentDirName = path.basename(process.cwd());
|
|
37
|
+
const currentDirFiles = fs.readdirSync(process.cwd());
|
|
38
|
+
const hasExistingFiles = currentDirFiles.some(f => !f.startsWith('.'));
|
|
39
|
+
|
|
40
|
+
const { useCurrent } = await inquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: 'confirm',
|
|
43
|
+
name: 'useCurrent',
|
|
44
|
+
message: `Install in current directory (${currentDirName})?`,
|
|
45
|
+
default: true
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
if (useCurrent) {
|
|
50
|
+
useCurrentDir = true;
|
|
51
|
+
projectPath = process.cwd();
|
|
52
|
+
|
|
53
|
+
// Check for existing Webspresso files
|
|
54
|
+
if (fs.existsSync(path.join(projectPath, 'server.js')) ||
|
|
55
|
+
fs.existsSync(path.join(projectPath, 'pages'))) {
|
|
56
|
+
console.error('❌ Current directory already contains a Webspresso project!');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Warn if there are existing files
|
|
61
|
+
if (hasExistingFiles) {
|
|
62
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
63
|
+
{
|
|
64
|
+
type: 'confirm',
|
|
65
|
+
name: 'continueAnyway',
|
|
66
|
+
message: '⚠️ Current directory is not empty. Continue anyway?',
|
|
67
|
+
default: false
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
if (!continueAnyway) {
|
|
72
|
+
console.log('Cancelled.');
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Ask for project name (for package.json)
|
|
78
|
+
const { name } = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'name',
|
|
82
|
+
message: 'Project name:',
|
|
83
|
+
default: currentDirName,
|
|
84
|
+
validate: (input) => {
|
|
85
|
+
if (!input.trim()) return 'Project name is required';
|
|
86
|
+
if (!/^[a-z0-9-_]+$/i.test(input)) return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
projectName = name;
|
|
93
|
+
} else {
|
|
94
|
+
// Ask for directory name
|
|
95
|
+
const { dirName } = await inquirer.prompt([
|
|
96
|
+
{
|
|
97
|
+
type: 'input',
|
|
98
|
+
name: 'dirName',
|
|
99
|
+
message: 'Project directory name:',
|
|
100
|
+
validate: (input) => {
|
|
101
|
+
if (!input.trim()) return 'Directory name is required';
|
|
102
|
+
if (!/^[a-z0-9-_]+$/i.test(input)) return 'Directory name can only contain letters, numbers, hyphens, and underscores';
|
|
103
|
+
if (fs.existsSync(path.resolve(input))) return `Directory ${input} already exists!`;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
projectName = dirName;
|
|
110
|
+
projectPath = path.resolve(dirName);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
projectName = projectNameArg;
|
|
114
|
+
projectPath = path.resolve(projectNameArg);
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(projectPath)) {
|
|
117
|
+
console.error(`❌ Directory ${projectName} already exists!`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
34
120
|
}
|
|
35
121
|
|
|
36
122
|
console.log(`\n🚀 Creating new Webspresso project: ${projectName}\n`);
|
|
37
123
|
|
|
38
|
-
// Create directory structure
|
|
39
|
-
|
|
124
|
+
// Create directory structure (skip root if using current dir)
|
|
125
|
+
if (!useCurrentDir) {
|
|
126
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
127
|
+
}
|
|
40
128
|
fs.mkdirSync(path.join(projectPath, 'pages'), { recursive: true });
|
|
41
129
|
fs.mkdirSync(path.join(projectPath, 'pages', 'locales'), { recursive: true });
|
|
42
130
|
fs.mkdirSync(path.join(projectPath, 'views'), { recursive: true });
|
|
@@ -361,7 +449,9 @@ module.exports = {
|
|
|
361
449
|
|
|
362
450
|
console.log('\n✅ Project ready!\n');
|
|
363
451
|
console.log('Start developing:');
|
|
364
|
-
|
|
452
|
+
if (!useCurrentDir) {
|
|
453
|
+
console.log(` cd ${projectName}`);
|
|
454
|
+
}
|
|
365
455
|
console.log(' npm run dev\n');
|
|
366
456
|
} catch (err) {
|
|
367
457
|
console.error('❌ Installation failed:', err.message);
|
|
@@ -370,7 +460,9 @@ module.exports = {
|
|
|
370
460
|
} else {
|
|
371
461
|
console.log('\n✅ Project created successfully!\n');
|
|
372
462
|
console.log('Next steps:');
|
|
373
|
-
|
|
463
|
+
if (!useCurrentDir) {
|
|
464
|
+
console.log(` cd ${projectName}`);
|
|
465
|
+
}
|
|
374
466
|
console.log(' npm install');
|
|
375
467
|
if (useTailwind) {
|
|
376
468
|
console.log(' npm run build:css');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webspresso",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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
|
+
|