@marvalt/digivalt-core 0.2.9 → 0.2.12
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 +29 -0
- package/README.md +9 -1
- package/bin/env-example-fragments.cjs +3 -0
- package/dist/generators/google-reviews.d.ts +5 -0
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/sitemap-paths.d.ts +11 -0
- package/dist/generators.cjs +303 -1
- package/dist/generators.cjs.map +1 -1
- package/dist/generators.d.ts +19 -1
- package/dist/generators.esm.js +301 -2
- package/dist/generators.esm.js.map +1 -1
- package/dist/index.cjs +306 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +25 -9
- package/dist/index.esm.js +304 -2
- package/dist/index.esm.js.map +1 -1
- package/docs/AI-LOCAL-WORDPRESS-BOOTSTRAP.md +116 -0
- package/docs/LOVABLE-MASTER-PROMPT.md +140 -0
- package/docs/SEO-PERFORMANCE.md +9 -1
- package/package.json +3 -2
- package/template/DIGIVALT_SETUP.md +1 -1
- package/template/functions/sitemap.xml.ts +1 -1
- package/template/public/gravity-forms-data.json +7 -0
- package/template/public/sitemap-static-paths.json +3 -0
- package/template/public/wordpress-data.json +44 -0
- package/template/scripts/deploy-secrets.js +9 -7
- package/template/scripts/generate.ts +9 -5
- package/template/src/data/mautic-data.json +7 -0
package/dist/generators.d.ts
CHANGED
|
@@ -14,6 +14,24 @@ declare function generateSuiteCRMData(options?: {
|
|
|
14
14
|
outputPath?: string;
|
|
15
15
|
}): Promise<boolean>;
|
|
16
16
|
|
|
17
|
+
interface GenerateGoogleReviewsOptions {
|
|
18
|
+
outputPath?: string;
|
|
19
|
+
}
|
|
20
|
+
declare function generateGoogleReviewsData(options?: GenerateGoogleReviewsOptions): Promise<boolean>;
|
|
21
|
+
|
|
22
|
+
type GenerateSitemapPathsOptions = {
|
|
23
|
+
cwd?: string;
|
|
24
|
+
wpDataPath?: string;
|
|
25
|
+
staticInputPath?: string;
|
|
26
|
+
outputPath?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Merge sitemap-static-paths.json, navigation_menu URLs, and CPT/post links from wordpress-data.json.
|
|
30
|
+
*/
|
|
31
|
+
declare function generateSitemapPaths(options?: GenerateSitemapPathsOptions): Promise<void>;
|
|
32
|
+
declare function generateSitemapPathsSafe(options?: GenerateSitemapPathsOptions): Promise<void>;
|
|
33
|
+
|
|
17
34
|
declare function generateAllData(): Promise<void>;
|
|
18
35
|
|
|
19
|
-
export { generateAllData, generateGravityFormsData, generateMauticData, generateSuiteCRMData, generateWordPressData };
|
|
36
|
+
export { generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData };
|
|
37
|
+
export type { GenerateSitemapPathsOptions };
|
package/dist/generators.esm.js
CHANGED
|
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
|
|
|
2
2
|
import path, { join, dirname } from 'path';
|
|
3
3
|
import fs, { writeFileSync } from 'fs';
|
|
4
4
|
import { generateSuiteCRMData as generateSuiteCRMData$1 } from '@marvalt/sadapter';
|
|
5
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @license GPL-3.0-or-later
|
|
@@ -2451,6 +2452,302 @@ async function generateSuiteCRMData(options) {
|
|
|
2451
2452
|
}
|
|
2452
2453
|
}
|
|
2453
2454
|
|
|
2455
|
+
function trimTrailingSlash(value) {
|
|
2456
|
+
return value.replace(/\/+$/, '');
|
|
2457
|
+
}
|
|
2458
|
+
function resolveEndpointUrl() {
|
|
2459
|
+
const explicit = process.env.GOOGLE_REVIEWS_API_URL ||
|
|
2460
|
+
process.env.VITE_GOOGLE_REVIEWS_API_URL ||
|
|
2461
|
+
'';
|
|
2462
|
+
if (explicit) {
|
|
2463
|
+
return explicit;
|
|
2464
|
+
}
|
|
2465
|
+
const wpApiBase = process.env.WORDPRESS_API_URL ||
|
|
2466
|
+
process.env.VITE_WORDPRESS_API_URL ||
|
|
2467
|
+
'';
|
|
2468
|
+
if (!wpApiBase) {
|
|
2469
|
+
return '';
|
|
2470
|
+
}
|
|
2471
|
+
const normalizedBase = trimTrailingSlash(wpApiBase);
|
|
2472
|
+
if (normalizedBase.endsWith('/wp-json')) {
|
|
2473
|
+
return `${normalizedBase}/vibune-google-reviews/v1/reviews`;
|
|
2474
|
+
}
|
|
2475
|
+
return `${normalizedBase}/wp-json/vibune-google-reviews/v1/reviews`;
|
|
2476
|
+
}
|
|
2477
|
+
function buildHeaders() {
|
|
2478
|
+
const headers = {
|
|
2479
|
+
Accept: 'application/json',
|
|
2480
|
+
};
|
|
2481
|
+
const username = process.env.WP_API_USERNAME || process.env.VITE_WP_API_USERNAME || '';
|
|
2482
|
+
const password = process.env.WP_APP_PASSWORD || process.env.VITE_WP_APP_PASSWORD || '';
|
|
2483
|
+
if (username && password) {
|
|
2484
|
+
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
2485
|
+
}
|
|
2486
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID || '';
|
|
2487
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET || '';
|
|
2488
|
+
if (cfAccessClientId && cfAccessClientSecret) {
|
|
2489
|
+
headers['CF-Access-Client-Id'] = cfAccessClientId;
|
|
2490
|
+
headers['CF-Access-Client-Secret'] = cfAccessClientSecret;
|
|
2491
|
+
}
|
|
2492
|
+
return headers;
|
|
2493
|
+
}
|
|
2494
|
+
function hasExpectedShape(payload) {
|
|
2495
|
+
if (!payload || typeof payload !== 'object') {
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
const obj = payload;
|
|
2499
|
+
return Array.isArray(obj.reviews) && typeof obj.business === 'object' && obj.business !== null;
|
|
2500
|
+
}
|
|
2501
|
+
function truncateBody(body, max = 1200) {
|
|
2502
|
+
if (body.length <= max) {
|
|
2503
|
+
return body;
|
|
2504
|
+
}
|
|
2505
|
+
return `${body.slice(0, max)}...`;
|
|
2506
|
+
}
|
|
2507
|
+
async function generateGoogleReviewsData(options) {
|
|
2508
|
+
const outputPath = options?.outputPath || './public/google-reviews-data.json';
|
|
2509
|
+
const endpointUrl = resolveEndpointUrl();
|
|
2510
|
+
if (!endpointUrl) {
|
|
2511
|
+
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.');
|
|
2512
|
+
return false;
|
|
2513
|
+
}
|
|
2514
|
+
try {
|
|
2515
|
+
const response = await fetch(endpointUrl, {
|
|
2516
|
+
method: 'GET',
|
|
2517
|
+
headers: buildHeaders(),
|
|
2518
|
+
});
|
|
2519
|
+
if (!response.ok) {
|
|
2520
|
+
const body = truncateBody(await response.text());
|
|
2521
|
+
console.error(`❌ Google Reviews generation failed (HTTP ${response.status}): ${body}`);
|
|
2522
|
+
return false;
|
|
2523
|
+
}
|
|
2524
|
+
const payload = (await response.json());
|
|
2525
|
+
if (!hasExpectedShape(payload)) {
|
|
2526
|
+
console.error('❌ Google Reviews generation failed: endpoint returned unexpected payload shape.');
|
|
2527
|
+
return false;
|
|
2528
|
+
}
|
|
2529
|
+
const outputFilePath = path.join(process.cwd(), outputPath);
|
|
2530
|
+
await mkdir(path.dirname(outputFilePath), { recursive: true });
|
|
2531
|
+
await writeFile(outputFilePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
2532
|
+
console.log(`📁 Google Reviews data written to: ${outputFilePath}`);
|
|
2533
|
+
return true;
|
|
2534
|
+
}
|
|
2535
|
+
catch (e) {
|
|
2536
|
+
console.error('❌ Google Reviews generation failed:', e.message);
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
const SITEMAP_SKIP_TOP_LEVEL_KEYS = new Set([
|
|
2542
|
+
'config',
|
|
2543
|
+
'site_settings',
|
|
2544
|
+
'header',
|
|
2545
|
+
'footer',
|
|
2546
|
+
'theme_styles',
|
|
2547
|
+
'navigation_menu',
|
|
2548
|
+
'menus',
|
|
2549
|
+
'categories',
|
|
2550
|
+
'tags',
|
|
2551
|
+
'generated_at',
|
|
2552
|
+
'frontend_id',
|
|
2553
|
+
'frontend_name',
|
|
2554
|
+
'front_page',
|
|
2555
|
+
]);
|
|
2556
|
+
function normalizePathname(pathname) {
|
|
2557
|
+
const decoded = decodeURIComponent(pathname);
|
|
2558
|
+
if (!decoded || decoded === '/')
|
|
2559
|
+
return '/';
|
|
2560
|
+
const stripped = decoded.replace(/\/+$/, '');
|
|
2561
|
+
return stripped === '' ? '/' : stripped;
|
|
2562
|
+
}
|
|
2563
|
+
function getAllowedHosts() {
|
|
2564
|
+
const hosts = new Set();
|
|
2565
|
+
const api = process.env.VITE_WORDPRESS_API_URL || process.env.WORDPRESS_API_URL;
|
|
2566
|
+
if (api) {
|
|
2567
|
+
try {
|
|
2568
|
+
hosts.add(new URL(api).hostname);
|
|
2569
|
+
}
|
|
2570
|
+
catch {
|
|
2571
|
+
/* ignore */
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
return hosts;
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* When CMS host is unknown, accept any http(s) URL so local/offline merges still produce paths.
|
|
2578
|
+
*/
|
|
2579
|
+
function urlToPath(urlStr, allowedHosts) {
|
|
2580
|
+
const t = urlStr.trim();
|
|
2581
|
+
if (!t || t === '#' || t.toLowerCase().startsWith('mailto:') || t.toLowerCase().startsWith('tel:')) {
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
if (!/^https?:\/\//i.test(t)) {
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
let u;
|
|
2588
|
+
try {
|
|
2589
|
+
u = new URL(t);
|
|
2590
|
+
}
|
|
2591
|
+
catch {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
if (allowedHosts.size > 0 && !allowedHosts.has(u.hostname)) {
|
|
2595
|
+
return null;
|
|
2596
|
+
}
|
|
2597
|
+
return normalizePathname(u.pathname);
|
|
2598
|
+
}
|
|
2599
|
+
function parsePathsJson(raw) {
|
|
2600
|
+
try {
|
|
2601
|
+
const data = JSON.parse(raw);
|
|
2602
|
+
if (Array.isArray(data)) {
|
|
2603
|
+
return data.filter((p) => typeof p === 'string' && p.startsWith('/'));
|
|
2604
|
+
}
|
|
2605
|
+
if (data && typeof data === 'object' && Array.isArray(data.paths)) {
|
|
2606
|
+
return data.paths.filter((p) => typeof p === 'string' && p.startsWith('/'));
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
catch {
|
|
2610
|
+
/* ignore */
|
|
2611
|
+
}
|
|
2612
|
+
return [];
|
|
2613
|
+
}
|
|
2614
|
+
function loadStaticPaths(cwd, staticInputPath, allowedHosts) {
|
|
2615
|
+
const full = path.join(cwd, staticInputPath);
|
|
2616
|
+
if (!fs.existsSync(full)) {
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
const raw = fs.readFileSync(full, 'utf8');
|
|
2620
|
+
const paths = parsePathsJson(raw);
|
|
2621
|
+
const out = [];
|
|
2622
|
+
for (const p of paths) {
|
|
2623
|
+
if (p.startsWith('http://') || p.startsWith('https://')) {
|
|
2624
|
+
const normalized = urlToPath(p, allowedHosts);
|
|
2625
|
+
if (normalized)
|
|
2626
|
+
out.push(normalized);
|
|
2627
|
+
}
|
|
2628
|
+
else {
|
|
2629
|
+
out.push(normalizePathname(p));
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return out;
|
|
2633
|
+
}
|
|
2634
|
+
function walkMenuItems(items, allowedHosts, out) {
|
|
2635
|
+
if (!Array.isArray(items))
|
|
2636
|
+
return;
|
|
2637
|
+
for (const item of items) {
|
|
2638
|
+
if (!item || typeof item !== 'object')
|
|
2639
|
+
continue;
|
|
2640
|
+
const rec = item;
|
|
2641
|
+
if (typeof rec.url === 'string') {
|
|
2642
|
+
const p = urlToPath(rec.url, allowedHosts);
|
|
2643
|
+
if (p)
|
|
2644
|
+
out.add(p);
|
|
2645
|
+
}
|
|
2646
|
+
if (Array.isArray(rec.children)) {
|
|
2647
|
+
walkMenuItems(rec.children, allowedHosts, out);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
function collectLinksFromArray(arr, allowedHosts, out) {
|
|
2652
|
+
for (const item of arr) {
|
|
2653
|
+
if (!item || typeof item !== 'object')
|
|
2654
|
+
continue;
|
|
2655
|
+
const link = item.link;
|
|
2656
|
+
if (typeof link === 'string') {
|
|
2657
|
+
const p = urlToPath(link, allowedHosts);
|
|
2658
|
+
if (p)
|
|
2659
|
+
out.add(p);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
function collectPathsFromWpData(data, allowedHosts) {
|
|
2664
|
+
const out = new Set();
|
|
2665
|
+
const menu = data.navigation_menu;
|
|
2666
|
+
if (menu?.items) {
|
|
2667
|
+
walkMenuItems(menu.items, allowedHosts, out);
|
|
2668
|
+
}
|
|
2669
|
+
const config = data.config;
|
|
2670
|
+
const postTypeKeys = config?.post_types && typeof config.post_types === 'object'
|
|
2671
|
+
? Object.keys(config.post_types)
|
|
2672
|
+
: null;
|
|
2673
|
+
if (postTypeKeys && postTypeKeys.length > 0) {
|
|
2674
|
+
for (const key of postTypeKeys) {
|
|
2675
|
+
const arr = data[key];
|
|
2676
|
+
if (Array.isArray(arr)) {
|
|
2677
|
+
collectLinksFromArray(arr, allowedHosts, out);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
else {
|
|
2682
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2683
|
+
if (SITEMAP_SKIP_TOP_LEVEL_KEYS.has(key))
|
|
2684
|
+
continue;
|
|
2685
|
+
if (Array.isArray(value)) {
|
|
2686
|
+
collectLinksFromArray(value, allowedHosts, out);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
return out;
|
|
2691
|
+
}
|
|
2692
|
+
function writeFallback(cwd, outputPath) {
|
|
2693
|
+
const full = path.join(cwd, outputPath);
|
|
2694
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
2695
|
+
fs.writeFileSync(full, `${JSON.stringify({ paths: ['/'] }, null, 2)}\n`, 'utf8');
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Merge sitemap-static-paths.json, navigation_menu URLs, and CPT/post links from wordpress-data.json.
|
|
2699
|
+
*/
|
|
2700
|
+
async function generateSitemapPaths(options = {}) {
|
|
2701
|
+
dotenv.config();
|
|
2702
|
+
dotenv.config({ path: '.env.local' });
|
|
2703
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2704
|
+
const wpDataPath = options.wpDataPath ?? 'public/wordpress-data.json';
|
|
2705
|
+
const staticInputPath = options.staticInputPath ?? 'public/sitemap-static-paths.json';
|
|
2706
|
+
const outputPath = options.outputPath ?? 'public/sitemap-paths.json';
|
|
2707
|
+
const allowedHosts = getAllowedHosts();
|
|
2708
|
+
const merged = new Set();
|
|
2709
|
+
for (const p of loadStaticPaths(cwd, staticInputPath, allowedHosts)) {
|
|
2710
|
+
merged.add(normalizePathname(p));
|
|
2711
|
+
}
|
|
2712
|
+
const wpFull = path.join(cwd, wpDataPath);
|
|
2713
|
+
if (fs.existsSync(wpFull)) {
|
|
2714
|
+
try {
|
|
2715
|
+
const raw = fs.readFileSync(wpFull, 'utf8');
|
|
2716
|
+
const data = JSON.parse(raw);
|
|
2717
|
+
const fromWp = collectPathsFromWpData(data, allowedHosts);
|
|
2718
|
+
for (const p of fromWp) {
|
|
2719
|
+
merged.add(p);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
catch (e) {
|
|
2723
|
+
console.warn('⚠️ Could not parse wordpress-data.json for sitemap paths:', e.message);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
merged.add('/');
|
|
2727
|
+
const sorted = [...merged].sort((a, b) => {
|
|
2728
|
+
if (a === '/')
|
|
2729
|
+
return -1;
|
|
2730
|
+
if (b === '/')
|
|
2731
|
+
return 1;
|
|
2732
|
+
return a.localeCompare(b);
|
|
2733
|
+
});
|
|
2734
|
+
const outFull = path.join(cwd, outputPath);
|
|
2735
|
+
fs.mkdirSync(path.dirname(outFull), { recursive: true });
|
|
2736
|
+
fs.writeFileSync(outFull, `${JSON.stringify({ paths: sorted }, null, 2)}\n`, 'utf8');
|
|
2737
|
+
console.log(`📍 Sitemap paths written (${sorted.length} URLs): ${outFull}`);
|
|
2738
|
+
}
|
|
2739
|
+
async function generateSitemapPathsSafe(options = {}) {
|
|
2740
|
+
try {
|
|
2741
|
+
await generateSitemapPaths(options);
|
|
2742
|
+
}
|
|
2743
|
+
catch (e) {
|
|
2744
|
+
console.error('❌ Sitemap path generation failed; writing minimal / only:', e.message);
|
|
2745
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2746
|
+
const outputPath = options.outputPath ?? 'public/sitemap-paths.json';
|
|
2747
|
+
writeFallback(cwd, outputPath);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2454
2751
|
async function generateAllData() {
|
|
2455
2752
|
// Load .env files if they exist (for local development)
|
|
2456
2753
|
dotenv.config();
|
|
@@ -2464,12 +2761,14 @@ async function generateAllData() {
|
|
|
2464
2761
|
const gfSuccess = await generateGravityFormsData();
|
|
2465
2762
|
const mauticSuccess = await generateMauticData();
|
|
2466
2763
|
const suitecrmSuccess = await generateSuiteCRMData();
|
|
2467
|
-
|
|
2764
|
+
const googleReviewsSuccess = await generateGoogleReviewsData();
|
|
2765
|
+
if (!wpSuccess || !gfSuccess || !mauticSuccess || !suitecrmSuccess || !googleReviewsSuccess) {
|
|
2468
2766
|
console.error('❌ Some generation tasks failed. See above for details.');
|
|
2469
2767
|
process.exit(1);
|
|
2470
2768
|
}
|
|
2769
|
+
await generateSitemapPathsSafe();
|
|
2471
2770
|
console.log('✅ Generation complete!');
|
|
2472
2771
|
}
|
|
2473
2772
|
|
|
2474
|
-
export { generateAllData, generateGravityFormsData, generateMauticData, generateSuiteCRMData, generateWordPressData };
|
|
2773
|
+
export { generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData };
|
|
2475
2774
|
//# sourceMappingURL=generators.esm.js.map
|