@marvalt/digivalt-core 0.2.34 → 0.2.36
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/CHANGELOG.md +15 -0
- package/GLOBAL-SHORTCODES.md +33 -1
- package/dist/generators/wp-guard.d.ts +11 -0
- package/dist/generators.cjs +625 -506
- package/dist/generators.cjs.map +1 -1
- package/dist/generators.esm.js +625 -506
- package/dist/generators.esm.js.map +1 -1
- package/dist/icons.cjs +73 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.d.ts +6 -0
- package/dist/icons.esm.js +71 -0
- package/dist/icons.esm.js.map +1 -0
- package/dist/index.cjs +625 -506
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +625 -506
- package/dist/index.esm.js.map +1 -1
- package/dist/registry/iconShortcode.d.ts +3 -0
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry.cjs +68 -2
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.d.ts +5 -1
- package/dist/registry.esm.js +68 -3
- package/dist/registry.esm.js.map +1 -1
- package/docs/SHORTCODES.md +35 -0
- package/package.json +8 -2
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var dotenv = require('dotenv');
|
|
4
|
+
var fs = require('fs');
|
|
4
5
|
var path = require('path');
|
|
5
6
|
var generators = require('@marvalt/wadapter/generators');
|
|
6
7
|
var generators$1 = require('@marvalt/madapter/generators');
|
|
7
8
|
var generators$2 = require('@marvalt/plister/generators');
|
|
8
9
|
var sadapter = require('@marvalt/sadapter');
|
|
9
10
|
var promises = require('fs/promises');
|
|
10
|
-
var fs = require('fs');
|
|
11
11
|
|
|
12
12
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
13
|
const DEFAULT_ENABLED_POST_TYPES = 'posts,pages';
|
|
@@ -34,169 +34,553 @@ function getWordPressPostTypes() {
|
|
|
34
34
|
.map((pt) => POST_TYPE_ALIASES[pt] || pt);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
44
|
-
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
45
|
-
if (!apiUrl) {
|
|
46
|
-
console.error('❌ Missing WordPress API URL. Set WORDPRESS_API_URL or VITE_WORDPRESS_API_URL');
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
const localDev = process.env.VITE_LOCAL_DEVELOPMENT === 'true';
|
|
50
|
-
const requestedAuth = process.env.VITE_AUTH_MODE;
|
|
51
|
-
const hasProxy = !!cloudflareWorkerUrl;
|
|
52
|
-
const authMode = localDev ? 'direct' : (requestedAuth === 'cloudflare_proxy' && hasProxy ? 'cloudflare_proxy' : 'direct');
|
|
53
|
-
const config = {
|
|
54
|
-
authMode,
|
|
55
|
-
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? cloudflareWorkerUrl : undefined,
|
|
56
|
-
apiUrl,
|
|
57
|
-
username,
|
|
58
|
-
password,
|
|
59
|
-
cfAccessClientId,
|
|
60
|
-
cfAccessClientSecret,
|
|
61
|
-
};
|
|
62
|
-
const postTypes = getWordPressPostTypes();
|
|
63
|
-
const frontendId = process.env.VITE_FRONTEND_ID || process.env.FRONTEND_ID || undefined;
|
|
64
|
-
const frontendName = process.env.VITE_FRONTEND_NAME || process.env.FRONTEND_NAME || 'Default Frontend';
|
|
65
|
-
try {
|
|
66
|
-
const result = await generators.generateWordPressData({
|
|
67
|
-
...config,
|
|
68
|
-
outputPath,
|
|
69
|
-
postTypes,
|
|
70
|
-
...(frontendId && { frontendId }),
|
|
71
|
-
frontendName,
|
|
72
|
-
includeEmbedded: true,
|
|
73
|
-
maxItems: parseInt(process.env.VITE_DEFAULT_MAX_ITEMS || '200', 10),
|
|
74
|
-
});
|
|
75
|
-
console.log(`📁 WordPress data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
console.error('❌ WordPress generation failed:', e.message);
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
37
|
+
function normalizePathname(pathname) {
|
|
38
|
+
const decoded = decodeURIComponent(pathname);
|
|
39
|
+
if (!decoded || decoded === '/')
|
|
40
|
+
return '/';
|
|
41
|
+
const stripped = decoded.replace(/\/+$/, '');
|
|
42
|
+
return stripped === '' ? '/' : stripped;
|
|
82
43
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
cfAccessClientSecret,
|
|
112
|
-
useCustomEndpoint: true,
|
|
113
|
-
};
|
|
114
|
-
try {
|
|
115
|
-
const result = await generators.generateGravityFormsData({
|
|
116
|
-
...config,
|
|
117
|
-
outputPath,
|
|
118
|
-
includeInactive: false,
|
|
119
|
-
});
|
|
120
|
-
console.log(`📁 Gravity Forms data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
121
|
-
return true;
|
|
44
|
+
function joinPath(prefix, slug) {
|
|
45
|
+
const p = prefix.replace(/\/+$/, '');
|
|
46
|
+
const s = slug.replace(/^\/+/, '');
|
|
47
|
+
if (!p || p === '/')
|
|
48
|
+
return normalizePathname(`/${s}`);
|
|
49
|
+
return normalizePathname(`${p.startsWith('/') ? p : `/${p}`}/${s}`);
|
|
50
|
+
}
|
|
51
|
+
function stripHtml(html) {
|
|
52
|
+
return html
|
|
53
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
54
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
55
|
+
.replace(/<[^>]+>/g, ' ')
|
|
56
|
+
.replace(/\s+/g, ' ')
|
|
57
|
+
.trim();
|
|
58
|
+
}
|
|
59
|
+
function sanitizeBodyHtml(html) {
|
|
60
|
+
return html
|
|
61
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
62
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
63
|
+
.replace(/\son\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
64
|
+
}
|
|
65
|
+
function renderedText(value) {
|
|
66
|
+
if (typeof value === 'string')
|
|
67
|
+
return stripHtml(value);
|
|
68
|
+
if (value && typeof value === 'object' && 'rendered' in value) {
|
|
69
|
+
const r = value.rendered;
|
|
70
|
+
if (typeof r === 'string')
|
|
71
|
+
return stripHtml(r);
|
|
122
72
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
function renderedHtml(value) {
|
|
76
|
+
if (typeof value === 'string')
|
|
77
|
+
return value;
|
|
78
|
+
if (value && typeof value === 'object' && 'rendered' in value) {
|
|
79
|
+
const r = value.rendered;
|
|
80
|
+
if (typeof r === 'string')
|
|
81
|
+
return r;
|
|
126
82
|
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
function escapeHtml(s) {
|
|
86
|
+
return s
|
|
87
|
+
.replace(/&/g, '&')
|
|
88
|
+
.replace(/</g, '<')
|
|
89
|
+
.replace(/>/g, '>')
|
|
90
|
+
.replace(/"/g, '"');
|
|
91
|
+
}
|
|
92
|
+
function escapeAttr(s) {
|
|
93
|
+
return escapeHtml(s).replace(/'/g, ''');
|
|
127
94
|
}
|
|
128
95
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
apiUrl: mauticUrl,
|
|
147
|
-
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? mauticProxyUrl : undefined,
|
|
148
|
-
clientId: clientId,
|
|
149
|
-
clientSecret: clientSecret,
|
|
150
|
-
cfAccessClientId,
|
|
151
|
-
cfAccessClientSecret,
|
|
152
|
-
outputPath,
|
|
153
|
-
includeInactive: false,
|
|
154
|
-
});
|
|
155
|
-
console.log(`📁 Mautic data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
156
|
-
return true;
|
|
96
|
+
const SITEMAP_URL_SHARD_SIZE = 45000;
|
|
97
|
+
function parseRoutePrefixes(raw) {
|
|
98
|
+
const out = {};
|
|
99
|
+
const src = (raw ?? process.env.VITE_SEO_ROUTE_PREFIXES ?? '').trim();
|
|
100
|
+
if (!src)
|
|
101
|
+
return out;
|
|
102
|
+
for (const part of src.split(',')) {
|
|
103
|
+
const trimmed = part.trim();
|
|
104
|
+
if (!trimmed)
|
|
105
|
+
continue;
|
|
106
|
+
const idx = trimmed.indexOf(':');
|
|
107
|
+
if (idx <= 0)
|
|
108
|
+
continue;
|
|
109
|
+
const key = trimmed.slice(0, idx).trim();
|
|
110
|
+
const prefix = trimmed.slice(idx + 1).trim();
|
|
111
|
+
if (key)
|
|
112
|
+
out[key] = prefix || '/';
|
|
157
113
|
}
|
|
158
|
-
|
|
159
|
-
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function isPublishedEntity(item) {
|
|
117
|
+
const s = item.status;
|
|
118
|
+
if (s === undefined || s === null || s === '')
|
|
160
119
|
return true;
|
|
161
|
-
|
|
120
|
+
return s === 'publish';
|
|
162
121
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (!raw?.trim())
|
|
166
|
-
return [];
|
|
167
|
-
return raw
|
|
168
|
-
.split(',')
|
|
169
|
-
.map((s) => parseInt(s.trim(), 10))
|
|
170
|
-
.filter((n) => !Number.isNaN(n));
|
|
122
|
+
function isNoindexEntity(item) {
|
|
123
|
+
return item.digivalt_seo?.robots_index === false;
|
|
171
124
|
}
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
console.log('⚠️ PHPList URL not configured. Skipping PHPList data generation.');
|
|
196
|
-
return true;
|
|
125
|
+
function entityLastmod(item) {
|
|
126
|
+
const v = item.modified_gmt || item.modified || item.date_gmt || item.date;
|
|
127
|
+
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
128
|
+
}
|
|
129
|
+
function entityImage(item) {
|
|
130
|
+
const v = item.digivalt_seo?.og_image ||
|
|
131
|
+
item.featured_media_cloudflare_url ||
|
|
132
|
+
item.featured_media_url;
|
|
133
|
+
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
134
|
+
}
|
|
135
|
+
function getFrontPageRef(data) {
|
|
136
|
+
const fp = data.front_page;
|
|
137
|
+
const envSlug = (process.env.VITE_SEO_FRONT_PAGE_SLUG || '').trim();
|
|
138
|
+
let slug;
|
|
139
|
+
let id;
|
|
140
|
+
if (fp && typeof fp === 'object') {
|
|
141
|
+
const rec = fp;
|
|
142
|
+
if (typeof rec.slug === 'string' && rec.slug.trim())
|
|
143
|
+
slug = rec.slug.trim();
|
|
144
|
+
if (typeof rec.id === 'number')
|
|
145
|
+
id = rec.id;
|
|
146
|
+
else if (typeof rec.id === 'string')
|
|
147
|
+
id = Number(rec.id) || undefined;
|
|
197
148
|
}
|
|
198
|
-
|
|
199
|
-
|
|
149
|
+
if (envSlug)
|
|
150
|
+
slug = envSlug;
|
|
151
|
+
return { slug, id };
|
|
152
|
+
}
|
|
153
|
+
function getFrontPageSlug(data) {
|
|
154
|
+
return getFrontPageRef(data).slug;
|
|
155
|
+
}
|
|
156
|
+
function asRecord(item) {
|
|
157
|
+
if (!item || typeof item !== 'object')
|
|
158
|
+
return null;
|
|
159
|
+
return item;
|
|
160
|
+
}
|
|
161
|
+
function newsletterRouteBase() {
|
|
162
|
+
const raw = process.env.VITE_PHPLIST_NEWSLETTER_ROUTE_BASE || '/newsletters';
|
|
163
|
+
return normalizePathname(raw.startsWith('/') ? raw : `/${raw}`);
|
|
164
|
+
}
|
|
165
|
+
function collectSitemapUrls(args) {
|
|
166
|
+
const prefixes = args.prefixes ?? parseRoutePrefixes();
|
|
167
|
+
const front = args.wpData ? getFrontPageRef(args.wpData) : {};
|
|
168
|
+
const byPath = new Map();
|
|
169
|
+
const add = (entry) => {
|
|
170
|
+
const path = normalizePathname(entry.path);
|
|
171
|
+
if (!path)
|
|
172
|
+
return;
|
|
173
|
+
const prev = byPath.get(path);
|
|
174
|
+
if (!prev) {
|
|
175
|
+
byPath.set(path, { ...entry, path });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
byPath.set(path, {
|
|
179
|
+
path,
|
|
180
|
+
lastmod: entry.lastmod || prev.lastmod,
|
|
181
|
+
image: entry.image || prev.image,
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
add({ path: '/' });
|
|
185
|
+
for (const p of args.staticPaths) {
|
|
186
|
+
add({ path: p });
|
|
187
|
+
}
|
|
188
|
+
const pages = args.wpData && Array.isArray(args.wpData.pages) ? args.wpData.pages : [];
|
|
189
|
+
for (const raw of pages) {
|
|
190
|
+
const item = asRecord(raw);
|
|
191
|
+
if (!item || !isPublishedEntity(item) || isNoindexEntity(item))
|
|
192
|
+
continue;
|
|
193
|
+
const slug = typeof item.slug === 'string' ? item.slug.trim() : '';
|
|
194
|
+
if (!slug)
|
|
195
|
+
continue;
|
|
196
|
+
const isFront = (front.slug && slug === front.slug) ||
|
|
197
|
+
(typeof front.id === 'number' && typeof item.id === 'number' && item.id === front.id);
|
|
198
|
+
const path = isFront ? '/' : joinPath('/', slug);
|
|
199
|
+
add({
|
|
200
|
+
path,
|
|
201
|
+
lastmod: entityLastmod(item),
|
|
202
|
+
image: entityImage(item),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (args.wpData) {
|
|
206
|
+
for (const [type, prefix] of Object.entries(prefixes)) {
|
|
207
|
+
if (type === 'pages' || type === 'page')
|
|
208
|
+
continue;
|
|
209
|
+
const arr = args.wpData[type];
|
|
210
|
+
if (!Array.isArray(arr))
|
|
211
|
+
continue;
|
|
212
|
+
for (const raw of arr) {
|
|
213
|
+
const item = asRecord(raw);
|
|
214
|
+
if (!item || !isPublishedEntity(item) || isNoindexEntity(item))
|
|
215
|
+
continue;
|
|
216
|
+
const slug = typeof item.slug === 'string' ? item.slug.trim() : '';
|
|
217
|
+
if (!slug)
|
|
218
|
+
continue;
|
|
219
|
+
add({
|
|
220
|
+
path: joinPath(prefix, slug),
|
|
221
|
+
lastmod: entityLastmod(item),
|
|
222
|
+
image: entityImage(item),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const newsletters = args.phplistData && Array.isArray(args.phplistData.newsletters)
|
|
228
|
+
? args.phplistData.newsletters
|
|
229
|
+
: [];
|
|
230
|
+
const nBase = newsletterRouteBase();
|
|
231
|
+
for (const raw of newsletters) {
|
|
232
|
+
if (!raw || typeof raw !== 'object')
|
|
233
|
+
continue;
|
|
234
|
+
const rec = raw;
|
|
235
|
+
if (typeof rec.slug !== 'string' || !rec.slug.trim())
|
|
236
|
+
continue;
|
|
237
|
+
add({
|
|
238
|
+
path: joinPath(nBase, rec.slug.trim()),
|
|
239
|
+
lastmod: typeof rec.sent_at === 'string' ? rec.sent_at : undefined,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const known = new Set(byPath.keys());
|
|
243
|
+
const considerMenu = (items) => {
|
|
244
|
+
if (!Array.isArray(items))
|
|
245
|
+
return;
|
|
246
|
+
for (const item of items) {
|
|
247
|
+
if (!item || typeof item !== 'object')
|
|
248
|
+
continue;
|
|
249
|
+
const rec = item;
|
|
250
|
+
if (typeof rec.url === 'string') {
|
|
251
|
+
const path = menuUrlToFrontendPath(rec.url, front.slug);
|
|
252
|
+
if (path && known.has(path)) {
|
|
253
|
+
add({ path });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
considerMenu(rec.children);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
if (args.wpData) {
|
|
260
|
+
const header = args.wpData.navigation_menu;
|
|
261
|
+
const footer = args.wpData.footer_navigation_menu;
|
|
262
|
+
considerMenu(header?.items);
|
|
263
|
+
considerMenu(footer?.items);
|
|
264
|
+
}
|
|
265
|
+
const sorted = [...byPath.values()].sort((a, b) => {
|
|
266
|
+
if (a.path === '/')
|
|
267
|
+
return -1;
|
|
268
|
+
if (b.path === '/')
|
|
269
|
+
return 1;
|
|
270
|
+
return a.path.localeCompare(b.path);
|
|
271
|
+
});
|
|
272
|
+
return sorted;
|
|
273
|
+
}
|
|
274
|
+
function menuUrlToFrontendPath(urlStr, frontSlug) {
|
|
275
|
+
const t = urlStr.trim();
|
|
276
|
+
if (!t || t === '#' || t.toLowerCase().startsWith('mailto:') || t.toLowerCase().startsWith('tel:')) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
let pathname;
|
|
280
|
+
if (t.startsWith('/')) {
|
|
281
|
+
pathname = normalizePathname(t.split('?')[0]);
|
|
282
|
+
}
|
|
283
|
+
else if (/^https?:\/\//i.test(t)) {
|
|
284
|
+
try {
|
|
285
|
+
pathname = normalizePathname(new URL(t).pathname);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if (pathname === '/')
|
|
295
|
+
return '/';
|
|
296
|
+
const slug = pathname.replace(/^\//, '');
|
|
297
|
+
if (frontSlug && slug === frontSlug)
|
|
298
|
+
return '/';
|
|
299
|
+
return pathname;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const SHOWN_SLUGS = 5;
|
|
303
|
+
function pageSlug(page) {
|
|
304
|
+
if (!page || typeof page !== 'object')
|
|
305
|
+
return '';
|
|
306
|
+
const slug = page.slug;
|
|
307
|
+
return typeof slug === 'string' ? slug.trim() : '';
|
|
308
|
+
}
|
|
309
|
+
function pageId(page) {
|
|
310
|
+
if (!page || typeof page !== 'object')
|
|
311
|
+
return undefined;
|
|
312
|
+
const id = page.id;
|
|
313
|
+
if (typeof id === 'number' && Number.isFinite(id))
|
|
314
|
+
return id;
|
|
315
|
+
if (typeof id === 'string' && id.trim()) {
|
|
316
|
+
const n = Number(id);
|
|
317
|
+
return Number.isFinite(n) ? n : undefined;
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
function isEmptyChrome(value) {
|
|
322
|
+
if (value == null)
|
|
323
|
+
return true;
|
|
324
|
+
if (typeof value === 'string')
|
|
325
|
+
return value.trim() === '';
|
|
326
|
+
if (Array.isArray(value))
|
|
327
|
+
return value.length === 0;
|
|
328
|
+
if (typeof value === 'object')
|
|
329
|
+
return Object.keys(value).length === 0;
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
function receivedSlugs(pages) {
|
|
333
|
+
const slugs = pages.map(pageSlug).filter(Boolean);
|
|
334
|
+
const shown = slugs.slice(0, SHOWN_SLUGS);
|
|
335
|
+
if (slugs.length > SHOWN_SLUGS)
|
|
336
|
+
shown.push('...');
|
|
337
|
+
return shown;
|
|
338
|
+
}
|
|
339
|
+
function reject(pages, expectedSlug) {
|
|
340
|
+
return {
|
|
341
|
+
ok: false,
|
|
342
|
+
expectedSlug,
|
|
343
|
+
pageCount: pages.length,
|
|
344
|
+
receivedSlugs: receivedSlugs(pages),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/** Reject a WordPress payload that cannot be the app's front page. */
|
|
348
|
+
function checkWordPressPayload(payload) {
|
|
349
|
+
if (process.env.VITE_IS_LOVABLE === 'true') {
|
|
350
|
+
return { ok: true, warnEmptyChrome: false };
|
|
351
|
+
}
|
|
352
|
+
if (!payload || typeof payload !== 'object') {
|
|
353
|
+
return { ok: false, pageCount: 0, receivedSlugs: [] };
|
|
354
|
+
}
|
|
355
|
+
const data = payload;
|
|
356
|
+
const pages = Array.isArray(data.pages) ? data.pages : null;
|
|
357
|
+
const envSlug = (process.env.VITE_SEO_FRONT_PAGE_SLUG || '').trim();
|
|
358
|
+
const ref = getFrontPageRef(data);
|
|
359
|
+
if (!pages || pages.length === 0) {
|
|
360
|
+
return reject([], envSlug || ref.slug);
|
|
361
|
+
}
|
|
362
|
+
if (envSlug) {
|
|
363
|
+
if (!pages.some((page) => pageSlug(page) === envSlug)) {
|
|
364
|
+
return reject(pages, envSlug);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (ref.slug || typeof ref.id === 'number') {
|
|
368
|
+
const matches = pages.some((page) => {
|
|
369
|
+
if (ref.slug && pageSlug(page) === ref.slug)
|
|
370
|
+
return true;
|
|
371
|
+
return typeof ref.id === 'number' && pageId(page) === ref.id;
|
|
372
|
+
});
|
|
373
|
+
if (!matches)
|
|
374
|
+
return reject(pages, ref.slug);
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
ok: true,
|
|
378
|
+
warnEmptyChrome: isEmptyChrome(data.site_settings) || isEmptyChrome(data.navigation_menu),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function generateWordPressData(options) {
|
|
383
|
+
const outputPath = options?.outputPath || './public/wordpress-data.json';
|
|
384
|
+
const apiUrl = process.env.WORDPRESS_API_URL || process.env.VITE_WORDPRESS_API_URL;
|
|
385
|
+
const username = process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME;
|
|
386
|
+
const password = process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD;
|
|
387
|
+
const cloudflareWorkerUrl = process.env.CLOUDFLARE_WORKER_URL || process.env.VITE_CLOUDFLARE_WORKER_URL;
|
|
388
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
389
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
390
|
+
if (!apiUrl) {
|
|
391
|
+
console.error('❌ Missing WordPress API URL. Set WORDPRESS_API_URL or VITE_WORDPRESS_API_URL');
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
const localDev = process.env.VITE_LOCAL_DEVELOPMENT === 'true';
|
|
395
|
+
const requestedAuth = process.env.VITE_AUTH_MODE;
|
|
396
|
+
const hasProxy = !!cloudflareWorkerUrl;
|
|
397
|
+
const authMode = localDev ? 'direct' : (requestedAuth === 'cloudflare_proxy' && hasProxy ? 'cloudflare_proxy' : 'direct');
|
|
398
|
+
const config = {
|
|
399
|
+
authMode,
|
|
400
|
+
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? cloudflareWorkerUrl : undefined,
|
|
401
|
+
apiUrl,
|
|
402
|
+
username,
|
|
403
|
+
password,
|
|
404
|
+
cfAccessClientId,
|
|
405
|
+
cfAccessClientSecret,
|
|
406
|
+
};
|
|
407
|
+
const postTypes = getWordPressPostTypes();
|
|
408
|
+
const frontendId = process.env.VITE_FRONTEND_ID || process.env.FRONTEND_ID || undefined;
|
|
409
|
+
const frontendName = process.env.VITE_FRONTEND_NAME || process.env.FRONTEND_NAME || 'Default Frontend';
|
|
410
|
+
const absPath = path.join(process.cwd(), outputPath);
|
|
411
|
+
let previous = null;
|
|
412
|
+
try {
|
|
413
|
+
if (fs.existsSync(absPath))
|
|
414
|
+
previous = fs.readFileSync(absPath);
|
|
415
|
+
}
|
|
416
|
+
catch (e) {
|
|
417
|
+
console.error('❌ Could not read existing WordPress data before generation:', e.message);
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const result = await generators.generateWordPressData({
|
|
422
|
+
...config,
|
|
423
|
+
outputPath,
|
|
424
|
+
postTypes,
|
|
425
|
+
...(frontendId && { frontendId }),
|
|
426
|
+
frontendName,
|
|
427
|
+
includeEmbedded: true,
|
|
428
|
+
maxItems: parseInt(process.env.VITE_DEFAULT_MAX_ITEMS || '200', 10),
|
|
429
|
+
});
|
|
430
|
+
const check = checkWordPressPayload(result);
|
|
431
|
+
if (check.ok === false) {
|
|
432
|
+
console.error(formatRejection(check, revertWordPressFile(absPath, previous)));
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
if (check.warnEmptyChrome) {
|
|
436
|
+
console.warn('⚠️ WordPress payload is missing site_settings or navigation_menu. wp-custom/v1 may have been unreachable.');
|
|
437
|
+
}
|
|
438
|
+
console.log(`📁 WordPress data written to: ${absPath}`);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
const hadPrevious = revertWordPressFile(absPath, previous);
|
|
443
|
+
console.error('❌ WordPress generation failed:', e.message);
|
|
444
|
+
console.error(restoreMessage(hadPrevious));
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function revertWordPressFile(absPath, previous) {
|
|
449
|
+
if (previous) {
|
|
450
|
+
fs.writeFileSync(absPath, previous);
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
if (fs.existsSync(absPath))
|
|
454
|
+
fs.unlinkSync(absPath);
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
function restoreMessage(hadPrevious) {
|
|
458
|
+
return hadPrevious
|
|
459
|
+
? 'Restored previous wordpress-data.json.'
|
|
460
|
+
: 'No previous wordpress-data.json to restore; removed the generated file.';
|
|
461
|
+
}
|
|
462
|
+
function formatRejection(check, hadPrevious) {
|
|
463
|
+
const expected = check.expectedSlug || '(none)';
|
|
464
|
+
const slugs = check.receivedSlugs.length ? check.receivedSlugs.join(', ') : '(none)';
|
|
465
|
+
return `❌ WordPress payload rejected: expected front page "${expected}", received ${check.pageCount} page(s): ${slugs}. ${restoreMessage(hadPrevious)}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function generateGravityFormsData(options) {
|
|
469
|
+
const outputPath = options?.outputPath || './public/gravity-forms-data.json';
|
|
470
|
+
const wpApiUrl = process.env.WORDPRESS_API_URL || process.env.VITE_WORDPRESS_API_URL;
|
|
471
|
+
const gfApiUrl = process.env.GRAVITY_FORMS_API_URL || process.env.VITE_GRAVITY_FORMS_API_URL;
|
|
472
|
+
const username = process.env.GRAVITY_FORMS_USERNAME || process.env.VITE_GRAVITY_FORMS_USERNAME ||
|
|
473
|
+
process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME || '';
|
|
474
|
+
const password = process.env.GRAVITY_FORMS_PASSWORD || process.env.VITE_GRAVITY_FORMS_PASSWORD ||
|
|
475
|
+
process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD || '';
|
|
476
|
+
const cloudflareWorkerUrl = process.env.CLOUDFLARE_WORKER_URL || process.env.VITE_CLOUDFLARE_WORKER_URL;
|
|
477
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
478
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
479
|
+
const apiUrl = gfApiUrl || (wpApiUrl ? `${wpApiUrl}/wp-json/gf-api/v1` : undefined);
|
|
480
|
+
if (!apiUrl) {
|
|
481
|
+
console.error('❌ Missing WordPress/Gravity Forms API URL.');
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
const localDev = process.env.VITE_LOCAL_DEVELOPMENT === 'true';
|
|
485
|
+
const requestedAuth = process.env.VITE_AUTH_MODE;
|
|
486
|
+
const hasProxy = !!cloudflareWorkerUrl;
|
|
487
|
+
const authMode = localDev ? 'direct' : (requestedAuth === 'cloudflare_proxy' && hasProxy ? 'cloudflare_proxy' : 'direct');
|
|
488
|
+
const config = {
|
|
489
|
+
authMode,
|
|
490
|
+
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? cloudflareWorkerUrl : undefined,
|
|
491
|
+
apiUrl,
|
|
492
|
+
username,
|
|
493
|
+
password,
|
|
494
|
+
cfAccessClientId,
|
|
495
|
+
cfAccessClientSecret,
|
|
496
|
+
useCustomEndpoint: true,
|
|
497
|
+
};
|
|
498
|
+
try {
|
|
499
|
+
const result = await generators.generateGravityFormsData({
|
|
500
|
+
...config,
|
|
501
|
+
outputPath,
|
|
502
|
+
includeInactive: false,
|
|
503
|
+
});
|
|
504
|
+
console.log(`📁 Gravity Forms data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
console.error('❌ Gravity Forms generation failed:', e.message);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function generateMauticData(options) {
|
|
514
|
+
const outputPath = options?.outputPath || './src/data/mautic-data.json';
|
|
515
|
+
const mauticUrl = process.env.MAUTIC_URL || process.env.VITE_MAUTIC_URL;
|
|
516
|
+
const mauticProxyUrl = process.env.MAUTIC_PROXY_URL || process.env.VITE_MAUTIC_PROXY_URL;
|
|
517
|
+
const clientId = process.env.MAUTIC_API_PUBLIC_KEY || process.env.VITE_MAUTIC_API_PUBLIC_KEY;
|
|
518
|
+
const clientSecret = process.env.MAUTIC_API_SECRET_KEY || process.env.VITE_MAUTIC_API_SECRET_KEY;
|
|
519
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
520
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
521
|
+
if (!mauticUrl) {
|
|
522
|
+
console.log('⚠️ Mautic URL not configured. Skipping Mautic data generation.');
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const hasProxy = !!mauticProxyUrl;
|
|
527
|
+
const authMode = hasProxy ? 'cloudflare_proxy' : 'direct';
|
|
528
|
+
const result = await generators$1.generateMauticData({
|
|
529
|
+
authMode,
|
|
530
|
+
apiUrl: mauticUrl,
|
|
531
|
+
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? mauticProxyUrl : undefined,
|
|
532
|
+
clientId: clientId,
|
|
533
|
+
clientSecret: clientSecret,
|
|
534
|
+
cfAccessClientId,
|
|
535
|
+
cfAccessClientSecret,
|
|
536
|
+
outputPath,
|
|
537
|
+
includeInactive: false,
|
|
538
|
+
});
|
|
539
|
+
console.log(`📁 Mautic data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
catch (e) {
|
|
543
|
+
console.warn(`⚠️ Mautic generation failed (build continues): ${e.message}`);
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function parseSubscribePageIds(raw) {
|
|
549
|
+
if (!raw?.trim())
|
|
550
|
+
return [];
|
|
551
|
+
return raw
|
|
552
|
+
.split(',')
|
|
553
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
554
|
+
.filter((n) => !Number.isNaN(n));
|
|
555
|
+
}
|
|
556
|
+
async function generatePhplistData(options) {
|
|
557
|
+
const outputPath = options?.outputPath || './public/phplist-data.json';
|
|
558
|
+
const phplistUrl = process.env.PHPLIST_URL || process.env.VITE_PHPLIST_URL;
|
|
559
|
+
const listIdRaw = process.env.PHPLIST_LIST_ID || process.env.VITE_PHPLIST_LIST_ID;
|
|
560
|
+
const apiUser = process.env.PHPLIST_API_USER || process.env.VITE_PHPLIST_API_USER;
|
|
561
|
+
const apiPassword = process.env.PHPLIST_API_PASSWORD || process.env.VITE_PHPLIST_API_PASSWORD;
|
|
562
|
+
const proxyUrl = process.env.PHPLIST_PROXY_URL || process.env.VITE_PHPLIST_PROXY_URL;
|
|
563
|
+
const subscribePageIds = parseSubscribePageIds(process.env.PHPLIST_SUBSCRIBE_PAGE_IDS || process.env.VITE_PHPLIST_SUBSCRIBE_PAGE_IDS);
|
|
564
|
+
const newsletterRouteBase = process.env.VITE_PHPLIST_NEWSLETTER_ROUTE_BASE || '/newsletters';
|
|
565
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
566
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
567
|
+
const restApiEndpoint = process.env.PHPLIST_API_ENDPOINT || process.env.VITE_PHPLIST_API_ENDPOINT;
|
|
568
|
+
const publicBaseUrl = process.env.PHPLIST_PUBLIC_BASE_URL || process.env.VITE_PHPLIST_PUBLIC_BASE_URL;
|
|
569
|
+
const apiSecret = process.env.PHPLIST_API_SECRET || process.env.VITE_PHPLIST_API_SECRET;
|
|
570
|
+
const subscribeStrategy = process.env.PHPLIST_SUBSCRIBE_STRATEGY || process.env.VITE_PHPLIST_SUBSCRIBE_STRATEGY;
|
|
571
|
+
const subscribePageId = process.env.PHPLIST_SUBSCRIBE_PAGE_ID || process.env.VITE_PHPLIST_SUBSCRIBE_PAGE_ID;
|
|
572
|
+
const apiModeRaw = process.env.PHPLIST_API_MODE || process.env.VITE_PHPLIST_API_MODE;
|
|
573
|
+
const apiMode = apiModeRaw === 'rest_v2' || apiModeRaw === 'v2'
|
|
574
|
+
? 'rest_v2'
|
|
575
|
+
: apiModeRaw === 'restapi' || apiModeRaw === 'plugin' || restApiEndpoint
|
|
576
|
+
? 'restapi'
|
|
577
|
+
: undefined;
|
|
578
|
+
if (!phplistUrl) {
|
|
579
|
+
console.log('⚠️ PHPList URL not configured. Skipping PHPList data generation.');
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
const listId = parseInt(listIdRaw || '0', 10);
|
|
583
|
+
if (!listId || Number.isNaN(listId)) {
|
|
200
584
|
console.warn('⚠️ PHPList list ID not configured. Skipping PHPList data generation.');
|
|
201
585
|
return true;
|
|
202
586
|
}
|
|
@@ -257,376 +641,111 @@ async function generateSuiteCRMData(options) {
|
|
|
257
641
|
clientId: process.env.VITE_SUITECRM_CLIENT_ID,
|
|
258
642
|
clientSecret: process.env.VITE_SUITECRM_CLIENT_SECRET,
|
|
259
643
|
username: process.env.VITE_SUITECRM_USERNAME,
|
|
260
|
-
password: process.env.VITE_SUITECRM_PASSWORD,
|
|
261
|
-
},
|
|
262
|
-
accessFunctionPath: '/api/fetch-with-access',
|
|
263
|
-
outputPath,
|
|
264
|
-
modules: ['Leads', 'Contacts', 'Accounts'],
|
|
265
|
-
includeFields: true,
|
|
266
|
-
includeRelationships: false,
|
|
267
|
-
});
|
|
268
|
-
console.log(`📁 SuiteCRM data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
catch (e) {
|
|
272
|
-
console.warn(`⚠️ SuiteCRM generation failed (build continues): ${e.message}`);
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function trimTrailingSlash(value) {
|
|
278
|
-
return value.replace(/\/+$/, '');
|
|
279
|
-
}
|
|
280
|
-
function resolveEndpointUrl() {
|
|
281
|
-
const explicit = process.env.GOOGLE_REVIEWS_API_URL ||
|
|
282
|
-
process.env.VITE_GOOGLE_REVIEWS_API_URL ||
|
|
283
|
-
'';
|
|
284
|
-
if (explicit) {
|
|
285
|
-
return explicit;
|
|
286
|
-
}
|
|
287
|
-
const wpApiBase = process.env.WORDPRESS_API_URL ||
|
|
288
|
-
process.env.VITE_WORDPRESS_API_URL ||
|
|
289
|
-
'';
|
|
290
|
-
if (!wpApiBase) {
|
|
291
|
-
return '';
|
|
292
|
-
}
|
|
293
|
-
const normalizedBase = trimTrailingSlash(wpApiBase);
|
|
294
|
-
if (normalizedBase.endsWith('/wp-json')) {
|
|
295
|
-
return `${normalizedBase}/vibune-google-reviews/v1/reviews`;
|
|
296
|
-
}
|
|
297
|
-
return `${normalizedBase}/wp-json/vibune-google-reviews/v1/reviews`;
|
|
298
|
-
}
|
|
299
|
-
function buildHeaders() {
|
|
300
|
-
const headers = {
|
|
301
|
-
Accept: 'application/json',
|
|
302
|
-
};
|
|
303
|
-
const username = process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME || '';
|
|
304
|
-
const password = process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD || '';
|
|
305
|
-
if (username && password) {
|
|
306
|
-
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
307
|
-
}
|
|
308
|
-
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID || '';
|
|
309
|
-
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET || '';
|
|
310
|
-
if (cfAccessClientId && cfAccessClientSecret) {
|
|
311
|
-
headers['CF-Access-Client-Id'] = cfAccessClientId;
|
|
312
|
-
headers['CF-Access-Client-Secret'] = cfAccessClientSecret;
|
|
313
|
-
}
|
|
314
|
-
return headers;
|
|
315
|
-
}
|
|
316
|
-
function hasExpectedShape(payload) {
|
|
317
|
-
if (!payload || typeof payload !== 'object') {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
const obj = payload;
|
|
321
|
-
return Array.isArray(obj.reviews) && typeof obj.business === 'object' && obj.business !== null;
|
|
322
|
-
}
|
|
323
|
-
function truncateBody(body, max = 1200) {
|
|
324
|
-
if (body.length <= max) {
|
|
325
|
-
return body;
|
|
326
|
-
}
|
|
327
|
-
return `${body.slice(0, max)}...`;
|
|
328
|
-
}
|
|
329
|
-
async function generateGoogleReviewsData(options) {
|
|
330
|
-
if (!isGoogleReviewsEnabled()) {
|
|
331
|
-
console.log('⚠️ Skipping Google Reviews data generation (google_reviews not in VITE_ENABLED_POST_TYPES).');
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
const outputPath = options?.outputPath || './public/google-reviews-data.json';
|
|
335
|
-
const endpointUrl = resolveEndpointUrl();
|
|
336
|
-
if (!endpointUrl) {
|
|
337
|
-
console.error('❌ Missing Google Reviews endpoint. Set GOOGLE_REVIEWS_API_URL or VITE_GOOGLE_REVIEWS_API_URL, or configure WORDPRESS_API_URL/VITE_WORDPRESS_API_URL.');
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
const response = await fetch(endpointUrl, {
|
|
342
|
-
method: 'GET',
|
|
343
|
-
headers: buildHeaders(),
|
|
344
|
-
});
|
|
345
|
-
if (!response.ok) {
|
|
346
|
-
const body = truncateBody(await response.text());
|
|
347
|
-
console.error(`❌ Google Reviews generation failed (HTTP ${response.status}): ${body}`);
|
|
348
|
-
return false;
|
|
349
|
-
}
|
|
350
|
-
const payload = (await response.json());
|
|
351
|
-
if (!hasExpectedShape(payload)) {
|
|
352
|
-
console.error('❌ Google Reviews generation failed: endpoint returned unexpected payload shape.');
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
const outputFilePath = path.join(process.cwd(), outputPath);
|
|
356
|
-
await promises.mkdir(path.dirname(outputFilePath), { recursive: true });
|
|
357
|
-
await promises.writeFile(outputFilePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
358
|
-
console.log(`📁 Google Reviews data written to: ${outputFilePath}`);
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
catch (e) {
|
|
362
|
-
console.error('❌ Google Reviews generation failed:', e.message);
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function normalizePathname(pathname) {
|
|
368
|
-
const decoded = decodeURIComponent(pathname);
|
|
369
|
-
if (!decoded || decoded === '/')
|
|
370
|
-
return '/';
|
|
371
|
-
const stripped = decoded.replace(/\/+$/, '');
|
|
372
|
-
return stripped === '' ? '/' : stripped;
|
|
373
|
-
}
|
|
374
|
-
function joinPath(prefix, slug) {
|
|
375
|
-
const p = prefix.replace(/\/+$/, '');
|
|
376
|
-
const s = slug.replace(/^\/+/, '');
|
|
377
|
-
if (!p || p === '/')
|
|
378
|
-
return normalizePathname(`/${s}`);
|
|
379
|
-
return normalizePathname(`${p.startsWith('/') ? p : `/${p}`}/${s}`);
|
|
380
|
-
}
|
|
381
|
-
function stripHtml(html) {
|
|
382
|
-
return html
|
|
383
|
-
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
384
|
-
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
385
|
-
.replace(/<[^>]+>/g, ' ')
|
|
386
|
-
.replace(/\s+/g, ' ')
|
|
387
|
-
.trim();
|
|
388
|
-
}
|
|
389
|
-
function sanitizeBodyHtml(html) {
|
|
390
|
-
return html
|
|
391
|
-
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
392
|
-
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
393
|
-
.replace(/\son\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
394
|
-
}
|
|
395
|
-
function renderedText(value) {
|
|
396
|
-
if (typeof value === 'string')
|
|
397
|
-
return stripHtml(value);
|
|
398
|
-
if (value && typeof value === 'object' && 'rendered' in value) {
|
|
399
|
-
const r = value.rendered;
|
|
400
|
-
if (typeof r === 'string')
|
|
401
|
-
return stripHtml(r);
|
|
402
|
-
}
|
|
403
|
-
return '';
|
|
404
|
-
}
|
|
405
|
-
function renderedHtml(value) {
|
|
406
|
-
if (typeof value === 'string')
|
|
407
|
-
return value;
|
|
408
|
-
if (value && typeof value === 'object' && 'rendered' in value) {
|
|
409
|
-
const r = value.rendered;
|
|
410
|
-
if (typeof r === 'string')
|
|
411
|
-
return r;
|
|
412
|
-
}
|
|
413
|
-
return '';
|
|
414
|
-
}
|
|
415
|
-
function escapeHtml(s) {
|
|
416
|
-
return s
|
|
417
|
-
.replace(/&/g, '&')
|
|
418
|
-
.replace(/</g, '<')
|
|
419
|
-
.replace(/>/g, '>')
|
|
420
|
-
.replace(/"/g, '"');
|
|
421
|
-
}
|
|
422
|
-
function escapeAttr(s) {
|
|
423
|
-
return escapeHtml(s).replace(/'/g, ''');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const SITEMAP_URL_SHARD_SIZE = 45000;
|
|
427
|
-
function parseRoutePrefixes(raw) {
|
|
428
|
-
const out = {};
|
|
429
|
-
const src = (raw ?? process.env.VITE_SEO_ROUTE_PREFIXES ?? '').trim();
|
|
430
|
-
if (!src)
|
|
431
|
-
return out;
|
|
432
|
-
for (const part of src.split(',')) {
|
|
433
|
-
const trimmed = part.trim();
|
|
434
|
-
if (!trimmed)
|
|
435
|
-
continue;
|
|
436
|
-
const idx = trimmed.indexOf(':');
|
|
437
|
-
if (idx <= 0)
|
|
438
|
-
continue;
|
|
439
|
-
const key = trimmed.slice(0, idx).trim();
|
|
440
|
-
const prefix = trimmed.slice(idx + 1).trim();
|
|
441
|
-
if (key)
|
|
442
|
-
out[key] = prefix || '/';
|
|
644
|
+
password: process.env.VITE_SUITECRM_PASSWORD,
|
|
645
|
+
},
|
|
646
|
+
accessFunctionPath: '/api/fetch-with-access',
|
|
647
|
+
outputPath,
|
|
648
|
+
modules: ['Leads', 'Contacts', 'Accounts'],
|
|
649
|
+
includeFields: true,
|
|
650
|
+
includeRelationships: false,
|
|
651
|
+
});
|
|
652
|
+
console.log(`📁 SuiteCRM data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
653
|
+
return true;
|
|
443
654
|
}
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
function isPublishedEntity(item) {
|
|
447
|
-
const s = item.status;
|
|
448
|
-
if (s === undefined || s === null || s === '')
|
|
655
|
+
catch (e) {
|
|
656
|
+
console.warn(`⚠️ SuiteCRM generation failed (build continues): ${e.message}`);
|
|
449
657
|
return true;
|
|
450
|
-
return s === 'publish';
|
|
451
|
-
}
|
|
452
|
-
function isNoindexEntity(item) {
|
|
453
|
-
return item.digivalt_seo?.robots_index === false;
|
|
454
|
-
}
|
|
455
|
-
function entityLastmod(item) {
|
|
456
|
-
const v = item.modified_gmt || item.modified || item.date_gmt || item.date;
|
|
457
|
-
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
458
|
-
}
|
|
459
|
-
function entityImage(item) {
|
|
460
|
-
const v = item.digivalt_seo?.og_image ||
|
|
461
|
-
item.featured_media_cloudflare_url ||
|
|
462
|
-
item.featured_media_url;
|
|
463
|
-
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
464
|
-
}
|
|
465
|
-
function getFrontPageRef(data) {
|
|
466
|
-
const fp = data.front_page;
|
|
467
|
-
const envSlug = (process.env.VITE_SEO_FRONT_PAGE_SLUG || '').trim();
|
|
468
|
-
let slug;
|
|
469
|
-
let id;
|
|
470
|
-
if (fp && typeof fp === 'object') {
|
|
471
|
-
const rec = fp;
|
|
472
|
-
if (typeof rec.slug === 'string' && rec.slug.trim())
|
|
473
|
-
slug = rec.slug.trim();
|
|
474
|
-
if (typeof rec.id === 'number')
|
|
475
|
-
id = rec.id;
|
|
476
|
-
else if (typeof rec.id === 'string')
|
|
477
|
-
id = Number(rec.id) || undefined;
|
|
478
658
|
}
|
|
479
|
-
if (envSlug)
|
|
480
|
-
slug = envSlug;
|
|
481
|
-
return { slug, id };
|
|
482
659
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
function asRecord(item) {
|
|
487
|
-
if (!item || typeof item !== 'object')
|
|
488
|
-
return null;
|
|
489
|
-
return item;
|
|
660
|
+
|
|
661
|
+
function trimTrailingSlash(value) {
|
|
662
|
+
return value.replace(/\/+$/, '');
|
|
490
663
|
}
|
|
491
|
-
function
|
|
492
|
-
const
|
|
493
|
-
|
|
664
|
+
function resolveEndpointUrl() {
|
|
665
|
+
const explicit = process.env.GOOGLE_REVIEWS_API_URL ||
|
|
666
|
+
process.env.VITE_GOOGLE_REVIEWS_API_URL ||
|
|
667
|
+
'';
|
|
668
|
+
if (explicit) {
|
|
669
|
+
return explicit;
|
|
670
|
+
}
|
|
671
|
+
const wpApiBase = process.env.WORDPRESS_API_URL ||
|
|
672
|
+
process.env.VITE_WORDPRESS_API_URL ||
|
|
673
|
+
'';
|
|
674
|
+
if (!wpApiBase) {
|
|
675
|
+
return '';
|
|
676
|
+
}
|
|
677
|
+
const normalizedBase = trimTrailingSlash(wpApiBase);
|
|
678
|
+
if (normalizedBase.endsWith('/wp-json')) {
|
|
679
|
+
return `${normalizedBase}/vibune-google-reviews/v1/reviews`;
|
|
680
|
+
}
|
|
681
|
+
return `${normalizedBase}/wp-json/vibune-google-reviews/v1/reviews`;
|
|
494
682
|
}
|
|
495
|
-
function
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
const byPath = new Map();
|
|
499
|
-
const add = (entry) => {
|
|
500
|
-
const path = normalizePathname(entry.path);
|
|
501
|
-
if (!path)
|
|
502
|
-
return;
|
|
503
|
-
const prev = byPath.get(path);
|
|
504
|
-
if (!prev) {
|
|
505
|
-
byPath.set(path, { ...entry, path });
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
byPath.set(path, {
|
|
509
|
-
path,
|
|
510
|
-
lastmod: entry.lastmod || prev.lastmod,
|
|
511
|
-
image: entry.image || prev.image,
|
|
512
|
-
});
|
|
683
|
+
function buildHeaders() {
|
|
684
|
+
const headers = {
|
|
685
|
+
Accept: 'application/json',
|
|
513
686
|
};
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const pages = args.wpData && Array.isArray(args.wpData.pages) ? args.wpData.pages : [];
|
|
519
|
-
for (const raw of pages) {
|
|
520
|
-
const item = asRecord(raw);
|
|
521
|
-
if (!item || !isPublishedEntity(item) || isNoindexEntity(item))
|
|
522
|
-
continue;
|
|
523
|
-
const slug = typeof item.slug === 'string' ? item.slug.trim() : '';
|
|
524
|
-
if (!slug)
|
|
525
|
-
continue;
|
|
526
|
-
const isFront = (front.slug && slug === front.slug) ||
|
|
527
|
-
(typeof front.id === 'number' && typeof item.id === 'number' && item.id === front.id);
|
|
528
|
-
const path = isFront ? '/' : joinPath('/', slug);
|
|
529
|
-
add({
|
|
530
|
-
path,
|
|
531
|
-
lastmod: entityLastmod(item),
|
|
532
|
-
image: entityImage(item),
|
|
533
|
-
});
|
|
687
|
+
const username = process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME || '';
|
|
688
|
+
const password = process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD || '';
|
|
689
|
+
if (username && password) {
|
|
690
|
+
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
534
691
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (!Array.isArray(arr))
|
|
541
|
-
continue;
|
|
542
|
-
for (const raw of arr) {
|
|
543
|
-
const item = asRecord(raw);
|
|
544
|
-
if (!item || !isPublishedEntity(item) || isNoindexEntity(item))
|
|
545
|
-
continue;
|
|
546
|
-
const slug = typeof item.slug === 'string' ? item.slug.trim() : '';
|
|
547
|
-
if (!slug)
|
|
548
|
-
continue;
|
|
549
|
-
add({
|
|
550
|
-
path: joinPath(prefix, slug),
|
|
551
|
-
lastmod: entityLastmod(item),
|
|
552
|
-
image: entityImage(item),
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
}
|
|
692
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID || '';
|
|
693
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET || '';
|
|
694
|
+
if (cfAccessClientId && cfAccessClientSecret) {
|
|
695
|
+
headers['CF-Access-Client-Id'] = cfAccessClientId;
|
|
696
|
+
headers['CF-Access-Client-Secret'] = cfAccessClientSecret;
|
|
556
697
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (!raw || typeof raw !== 'object')
|
|
563
|
-
continue;
|
|
564
|
-
const rec = raw;
|
|
565
|
-
if (typeof rec.slug !== 'string' || !rec.slug.trim())
|
|
566
|
-
continue;
|
|
567
|
-
add({
|
|
568
|
-
path: joinPath(nBase, rec.slug.trim()),
|
|
569
|
-
lastmod: typeof rec.sent_at === 'string' ? rec.sent_at : undefined,
|
|
570
|
-
});
|
|
698
|
+
return headers;
|
|
699
|
+
}
|
|
700
|
+
function hasExpectedShape(payload) {
|
|
701
|
+
if (!payload || typeof payload !== 'object') {
|
|
702
|
+
return false;
|
|
571
703
|
}
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
continue;
|
|
579
|
-
const rec = item;
|
|
580
|
-
if (typeof rec.url === 'string') {
|
|
581
|
-
const path = menuUrlToFrontendPath(rec.url, front.slug);
|
|
582
|
-
if (path && known.has(path)) {
|
|
583
|
-
add({ path });
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
considerMenu(rec.children);
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
if (args.wpData) {
|
|
590
|
-
const header = args.wpData.navigation_menu;
|
|
591
|
-
const footer = args.wpData.footer_navigation_menu;
|
|
592
|
-
considerMenu(header?.items);
|
|
593
|
-
considerMenu(footer?.items);
|
|
704
|
+
const obj = payload;
|
|
705
|
+
return Array.isArray(obj.reviews) && typeof obj.business === 'object' && obj.business !== null;
|
|
706
|
+
}
|
|
707
|
+
function truncateBody(body, max = 1200) {
|
|
708
|
+
if (body.length <= max) {
|
|
709
|
+
return body;
|
|
594
710
|
}
|
|
595
|
-
|
|
596
|
-
if (a.path === '/')
|
|
597
|
-
return -1;
|
|
598
|
-
if (b.path === '/')
|
|
599
|
-
return 1;
|
|
600
|
-
return a.path.localeCompare(b.path);
|
|
601
|
-
});
|
|
602
|
-
return sorted;
|
|
711
|
+
return `${body.slice(0, max)}...`;
|
|
603
712
|
}
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
return
|
|
713
|
+
async function generateGoogleReviewsData(options) {
|
|
714
|
+
if (!isGoogleReviewsEnabled()) {
|
|
715
|
+
console.log('⚠️ Skipping Google Reviews data generation (google_reviews not in VITE_ENABLED_POST_TYPES).');
|
|
716
|
+
return true;
|
|
608
717
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
718
|
+
const outputPath = options?.outputPath || './public/google-reviews-data.json';
|
|
719
|
+
const endpointUrl = resolveEndpointUrl();
|
|
720
|
+
if (!endpointUrl) {
|
|
721
|
+
console.error('❌ Missing Google Reviews endpoint. Set GOOGLE_REVIEWS_API_URL or VITE_GOOGLE_REVIEWS_API_URL, or configure WORDPRESS_API_URL/VITE_WORDPRESS_API_URL.');
|
|
722
|
+
return false;
|
|
612
723
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
724
|
+
try {
|
|
725
|
+
const response = await fetch(endpointUrl, {
|
|
726
|
+
method: 'GET',
|
|
727
|
+
headers: buildHeaders(),
|
|
728
|
+
});
|
|
729
|
+
if (!response.ok) {
|
|
730
|
+
const body = truncateBody(await response.text());
|
|
731
|
+
console.error(`❌ Google Reviews generation failed (HTTP ${response.status}): ${body}`);
|
|
732
|
+
return false;
|
|
616
733
|
}
|
|
617
|
-
|
|
618
|
-
|
|
734
|
+
const payload = (await response.json());
|
|
735
|
+
if (!hasExpectedShape(payload)) {
|
|
736
|
+
console.error('❌ Google Reviews generation failed: endpoint returned unexpected payload shape.');
|
|
737
|
+
return false;
|
|
619
738
|
}
|
|
739
|
+
const outputFilePath = path.join(process.cwd(), outputPath);
|
|
740
|
+
await promises.mkdir(path.dirname(outputFilePath), { recursive: true });
|
|
741
|
+
await promises.writeFile(outputFilePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
742
|
+
console.log(`📁 Google Reviews data written to: ${outputFilePath}`);
|
|
743
|
+
return true;
|
|
620
744
|
}
|
|
621
|
-
|
|
622
|
-
|
|
745
|
+
catch (e) {
|
|
746
|
+
console.error('❌ Google Reviews generation failed:', e.message);
|
|
747
|
+
return false;
|
|
623
748
|
}
|
|
624
|
-
if (pathname === '/')
|
|
625
|
-
return '/';
|
|
626
|
-
const slug = pathname.replace(/^\//, '');
|
|
627
|
-
if (frontSlug && slug === frontSlug)
|
|
628
|
-
return '/';
|
|
629
|
-
return pathname;
|
|
630
749
|
}
|
|
631
750
|
|
|
632
751
|
function parseStaticPathsJson(raw) {
|