@marvalt/digivalt-core 0.2.34 → 0.2.35

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/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
- async function generateWordPressData(options) {
38
- const outputPath = options?.outputPath || './public/wordpress-data.json';
39
- const apiUrl = process.env.WORDPRESS_API_URL || process.env.VITE_WORDPRESS_API_URL;
40
- const username = process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME;
41
- const password = process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD;
42
- const cloudflareWorkerUrl = process.env.CLOUDFLARE_WORKER_URL || process.env.VITE_CLOUDFLARE_WORKER_URL;
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
- async function generateGravityFormsData(options) {
85
- const outputPath = options?.outputPath || './public/gravity-forms-data.json';
86
- const wpApiUrl = process.env.WORDPRESS_API_URL || process.env.VITE_WORDPRESS_API_URL;
87
- const gfApiUrl = process.env.GRAVITY_FORMS_API_URL || process.env.VITE_GRAVITY_FORMS_API_URL;
88
- const username = process.env.GRAVITY_FORMS_USERNAME || process.env.VITE_GRAVITY_FORMS_USERNAME ||
89
- process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME || '';
90
- const password = process.env.GRAVITY_FORMS_PASSWORD || process.env.VITE_GRAVITY_FORMS_PASSWORD ||
91
- process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD || '';
92
- const cloudflareWorkerUrl = process.env.CLOUDFLARE_WORKER_URL || process.env.VITE_CLOUDFLARE_WORKER_URL;
93
- const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
94
- const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
95
- const apiUrl = gfApiUrl || (wpApiUrl ? `${wpApiUrl}/wp-json/gf-api/v1` : undefined);
96
- if (!apiUrl) {
97
- console.error('❌ Missing WordPress/Gravity Forms API URL.');
98
- return false;
99
- }
100
- const localDev = process.env.VITE_LOCAL_DEVELOPMENT === 'true';
101
- const requestedAuth = process.env.VITE_AUTH_MODE;
102
- const hasProxy = !!cloudflareWorkerUrl;
103
- const authMode = localDev ? 'direct' : (requestedAuth === 'cloudflare_proxy' && hasProxy ? 'cloudflare_proxy' : 'direct');
104
- const config = {
105
- authMode,
106
- cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? cloudflareWorkerUrl : undefined,
107
- apiUrl,
108
- username,
109
- password,
110
- cfAccessClientId,
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
- catch (e) {
124
- console.error('❌ Gravity Forms generation failed:', e.message);
125
- return false;
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, '&amp;')
88
+ .replace(/</g, '&lt;')
89
+ .replace(/>/g, '&gt;')
90
+ .replace(/"/g, '&quot;');
91
+ }
92
+ function escapeAttr(s) {
93
+ return escapeHtml(s).replace(/'/g, '&#39;');
127
94
  }
128
95
 
129
- async function generateMauticData(options) {
130
- const outputPath = options?.outputPath || './src/data/mautic-data.json';
131
- const mauticUrl = process.env.MAUTIC_URL || process.env.VITE_MAUTIC_URL;
132
- const mauticProxyUrl = process.env.MAUTIC_PROXY_URL || process.env.VITE_MAUTIC_PROXY_URL;
133
- const clientId = process.env.MAUTIC_API_PUBLIC_KEY || process.env.VITE_MAUTIC_API_PUBLIC_KEY;
134
- const clientSecret = process.env.MAUTIC_API_SECRET_KEY || process.env.VITE_MAUTIC_API_SECRET_KEY;
135
- const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
136
- const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
137
- if (!mauticUrl) {
138
- console.log('⚠️ Mautic URL not configured. Skipping Mautic data generation.');
139
- return true;
140
- }
141
- try {
142
- const hasProxy = !!mauticProxyUrl;
143
- const authMode = hasProxy ? 'cloudflare_proxy' : 'direct';
144
- const result = await generators$1.generateMauticData({
145
- authMode,
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
- catch (e) {
159
- console.warn(`⚠️ Mautic generation failed (build continues): ${e.message}`);
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
- function parseSubscribePageIds(raw) {
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
- async function generatePhplistData(options) {
173
- const outputPath = options?.outputPath || './public/phplist-data.json';
174
- const phplistUrl = process.env.PHPLIST_URL || process.env.VITE_PHPLIST_URL;
175
- const listIdRaw = process.env.PHPLIST_LIST_ID || process.env.VITE_PHPLIST_LIST_ID;
176
- const apiUser = process.env.PHPLIST_API_USER || process.env.VITE_PHPLIST_API_USER;
177
- const apiPassword = process.env.PHPLIST_API_PASSWORD || process.env.VITE_PHPLIST_API_PASSWORD;
178
- const proxyUrl = process.env.PHPLIST_PROXY_URL || process.env.VITE_PHPLIST_PROXY_URL;
179
- const subscribePageIds = parseSubscribePageIds(process.env.PHPLIST_SUBSCRIBE_PAGE_IDS || process.env.VITE_PHPLIST_SUBSCRIBE_PAGE_IDS);
180
- const newsletterRouteBase = process.env.VITE_PHPLIST_NEWSLETTER_ROUTE_BASE || '/newsletters';
181
- const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
182
- const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
183
- const restApiEndpoint = process.env.PHPLIST_API_ENDPOINT || process.env.VITE_PHPLIST_API_ENDPOINT;
184
- const publicBaseUrl = process.env.PHPLIST_PUBLIC_BASE_URL || process.env.VITE_PHPLIST_PUBLIC_BASE_URL;
185
- const apiSecret = process.env.PHPLIST_API_SECRET || process.env.VITE_PHPLIST_API_SECRET;
186
- const subscribeStrategy = process.env.PHPLIST_SUBSCRIBE_STRATEGY || process.env.VITE_PHPLIST_SUBSCRIBE_STRATEGY;
187
- const subscribePageId = process.env.PHPLIST_SUBSCRIBE_PAGE_ID || process.env.VITE_PHPLIST_SUBSCRIBE_PAGE_ID;
188
- const apiModeRaw = process.env.PHPLIST_API_MODE || process.env.VITE_PHPLIST_API_MODE;
189
- const apiMode = apiModeRaw === 'rest_v2' || apiModeRaw === 'v2'
190
- ? 'rest_v2'
191
- : apiModeRaw === 'restapi' || apiModeRaw === 'plugin' || restApiEndpoint
192
- ? 'restapi'
193
- : undefined;
194
- if (!phplistUrl) {
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
- const listId = parseInt(listIdRaw || '0', 10);
199
- if (!listId || Number.isNaN(listId)) {
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, '&amp;')
418
- .replace(/</g, '&lt;')
419
- .replace(/>/g, '&gt;')
420
- .replace(/"/g, '&quot;');
421
- }
422
- function escapeAttr(s) {
423
- return escapeHtml(s).replace(/'/g, '&#39;');
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
- return out;
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
- function getFrontPageSlug(data) {
484
- return getFrontPageRef(data).slug;
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 newsletterRouteBase() {
492
- const raw = process.env.VITE_PHPLIST_NEWSLETTER_ROUTE_BASE || '/newsletters';
493
- return normalizePathname(raw.startsWith('/') ? raw : `/${raw}`);
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 collectSitemapUrls(args) {
496
- const prefixes = args.prefixes ?? parseRoutePrefixes();
497
- const front = args.wpData ? getFrontPageRef(args.wpData) : {};
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
- add({ path: '/' });
515
- for (const p of args.staticPaths) {
516
- add({ path: p });
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
- if (args.wpData) {
536
- for (const [type, prefix] of Object.entries(prefixes)) {
537
- if (type === 'pages' || type === 'page')
538
- continue;
539
- const arr = args.wpData[type];
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
- const newsletters = args.phplistData && Array.isArray(args.phplistData.newsletters)
558
- ? args.phplistData.newsletters
559
- : [];
560
- const nBase = newsletterRouteBase();
561
- for (const raw of newsletters) {
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 known = new Set(byPath.keys());
573
- const considerMenu = (items) => {
574
- if (!Array.isArray(items))
575
- return;
576
- for (const item of items) {
577
- if (!item || typeof item !== 'object')
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
- const sorted = [...byPath.values()].sort((a, b) => {
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 menuUrlToFrontendPath(urlStr, frontSlug) {
605
- const t = urlStr.trim();
606
- if (!t || t === '#' || t.toLowerCase().startsWith('mailto:') || t.toLowerCase().startsWith('tel:')) {
607
- return null;
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
- let pathname;
610
- if (t.startsWith('/')) {
611
- pathname = normalizePathname(t.split('?')[0]);
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
- else if (/^https?:\/\//i.test(t)) {
614
- try {
615
- pathname = normalizePathname(new URL(t).pathname);
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
- catch {
618
- return null;
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
- else {
622
- return null;
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) {