@marvalt/digivalt-core 0.2.21 → 0.2.22
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 +6 -0
- package/dist/config/integrations.d.ts +16 -0
- package/dist/config.cjs +15 -0
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.ts +17 -1
- package/dist/config.esm.js +15 -1
- package/dist/config.esm.js.map +1 -1
- package/dist/generators/index.d.ts +1 -0
- package/dist/generators/phplist.d.ts +3 -0
- package/dist/generators.cjs +312 -0
- package/dist/generators.cjs.map +1 -1
- package/dist/generators.d.ts +5 -1
- package/dist/generators.esm.js +312 -1
- package/dist/generators.esm.js.map +1 -1
- package/dist/index.cjs +313 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.esm.js +313 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/seo.cjs +11 -0
- package/dist/seo.cjs.map +1 -1
- package/dist/seo.esm.js +11 -0
- package/dist/seo.esm.js.map +1 -1
- package/dist/services.cjs +11 -0
- package/dist/services.cjs.map +1 -1
- package/dist/services.esm.js +11 -0
- package/dist/services.esm.js.map +1 -1
- package/docs/README.md +1 -0
- package/package.json +3 -2
- package/template/DIGIVALT_SETUP.md +17 -0
package/dist/generators.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ declare function generateMauticData(options?: {
|
|
|
10
10
|
outputPath?: string;
|
|
11
11
|
}): Promise<boolean>;
|
|
12
12
|
|
|
13
|
+
declare function generatePhplistData(options?: {
|
|
14
|
+
outputPath?: string;
|
|
15
|
+
}): Promise<boolean>;
|
|
16
|
+
|
|
13
17
|
declare function generateSuiteCRMData(options?: {
|
|
14
18
|
outputPath?: string;
|
|
15
19
|
}): Promise<boolean>;
|
|
@@ -39,5 +43,5 @@ declare function generateSitemapPathsSafe(options?: GenerateSitemapPathsOptions)
|
|
|
39
43
|
|
|
40
44
|
declare function generateAllData(): Promise<void>;
|
|
41
45
|
|
|
42
|
-
export { PSEUDO_POST_TYPES, generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData, getWordPressPostTypes, isGoogleReviewsEnabled, parseEnabledPostTypes };
|
|
46
|
+
export { PSEUDO_POST_TYPES, generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generatePhplistData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData, getWordPressPostTypes, isGoogleReviewsEnabled, parseEnabledPostTypes };
|
|
43
47
|
export type { GenerateSitemapPathsOptions };
|
package/dist/generators.esm.js
CHANGED
|
@@ -2433,6 +2433,316 @@ async function generateMauticData(options) {
|
|
|
2433
2433
|
}
|
|
2434
2434
|
}
|
|
2435
2435
|
|
|
2436
|
+
/** Normalize PHPList base URL to REST API v2 root. */
|
|
2437
|
+
function getApiBaseUrl(phplistUrl) {
|
|
2438
|
+
const trimmed = phplistUrl.replace(/\/$/, '');
|
|
2439
|
+
if (trimmed.endsWith('/api/v2'))
|
|
2440
|
+
return trimmed;
|
|
2441
|
+
if (trimmed.endsWith('/lists'))
|
|
2442
|
+
return `${trimmed}/api/v2`;
|
|
2443
|
+
return `${trimmed}/lists/api/v2`;
|
|
2444
|
+
}
|
|
2445
|
+
function buildViewProxyPath(messageId, uid) {
|
|
2446
|
+
const params = new URLSearchParams({ endpoint: 'view', mid: String(messageId) });
|
|
2447
|
+
return `/api/phplist-proxy?${params.toString()}`;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
let cachedSession = null;
|
|
2451
|
+
function buildAccessHeaders(options) {
|
|
2452
|
+
const headers = {};
|
|
2453
|
+
if (options.cfAccessClientId && options.cfAccessClientSecret) {
|
|
2454
|
+
headers['CF-Access-Client-Id'] = options.cfAccessClientId;
|
|
2455
|
+
headers['CF-Access-Client-Secret'] = options.cfAccessClientSecret;
|
|
2456
|
+
}
|
|
2457
|
+
return headers;
|
|
2458
|
+
}
|
|
2459
|
+
async function createPhplistSession(options) {
|
|
2460
|
+
if (cachedSession && cachedSession.expiresAt > Date.now() + 300000) {
|
|
2461
|
+
return cachedSession.key;
|
|
2462
|
+
}
|
|
2463
|
+
const apiBase = getApiBaseUrl(options.apiUrl);
|
|
2464
|
+
const headers = {
|
|
2465
|
+
'Content-Type': 'application/json',
|
|
2466
|
+
...buildAccessHeaders(options),
|
|
2467
|
+
};
|
|
2468
|
+
const response = await fetch(`${apiBase}/sessions`, {
|
|
2469
|
+
method: 'POST',
|
|
2470
|
+
headers,
|
|
2471
|
+
body: JSON.stringify({
|
|
2472
|
+
login_name: options.apiUser,
|
|
2473
|
+
password: options.apiPassword,
|
|
2474
|
+
}),
|
|
2475
|
+
});
|
|
2476
|
+
if (!response.ok) {
|
|
2477
|
+
const errText = await response.text();
|
|
2478
|
+
throw new Error(`PHPList session failed: ${response.status} ${errText}`);
|
|
2479
|
+
}
|
|
2480
|
+
const data = (await response.json());
|
|
2481
|
+
const expiryMs = data.expiry ? Date.parse(data.expiry) : Date.now() + 3600000;
|
|
2482
|
+
cachedSession = { key: data.key, expiresAt: expiryMs };
|
|
2483
|
+
return data.key;
|
|
2484
|
+
}
|
|
2485
|
+
async function phplistApiFetch(options, path, init = {}) {
|
|
2486
|
+
const sessionKey = await createPhplistSession(options);
|
|
2487
|
+
const apiBase = getApiBaseUrl(options.apiUrl);
|
|
2488
|
+
const url = path.startsWith('http') ? path : `${apiBase}${path.startsWith('/') ? path : `/${path}`}`;
|
|
2489
|
+
const headers = {
|
|
2490
|
+
'Content-Type': 'application/json',
|
|
2491
|
+
'php-auth-pw': sessionKey,
|
|
2492
|
+
...init.headers,
|
|
2493
|
+
...buildAccessHeaders(options),
|
|
2494
|
+
};
|
|
2495
|
+
const response = await fetch(url, { ...init, headers });
|
|
2496
|
+
if (!response.ok) {
|
|
2497
|
+
const errText = await response.text();
|
|
2498
|
+
throw new Error(`PHPList API ${path} failed: ${response.status} ${errText}`);
|
|
2499
|
+
}
|
|
2500
|
+
if (response.status === 204) {
|
|
2501
|
+
return undefined;
|
|
2502
|
+
}
|
|
2503
|
+
const text = await response.text();
|
|
2504
|
+
if (!text)
|
|
2505
|
+
return undefined;
|
|
2506
|
+
return JSON.parse(text);
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/** URL-safe slug from subject line. */
|
|
2510
|
+
function slugifySubject(subject) {
|
|
2511
|
+
return subject
|
|
2512
|
+
.toLowerCase()
|
|
2513
|
+
.normalize('NFKD')
|
|
2514
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
2515
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
2516
|
+
.replace(/^-+|-+$/g, '')
|
|
2517
|
+
.slice(0, 80) || 'newsletter';
|
|
2518
|
+
}
|
|
2519
|
+
function slugifyListName(name) {
|
|
2520
|
+
return slugifySubject(name);
|
|
2521
|
+
}
|
|
2522
|
+
function buildNewsletterSlug(listSlug, messageId, subject) {
|
|
2523
|
+
return `${listSlug}/${messageId}-${slugifySubject(subject)}`;
|
|
2524
|
+
}
|
|
2525
|
+
function stripHtmlExcerpt(html, maxLength = 200) {
|
|
2526
|
+
const text = html
|
|
2527
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
2528
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
2529
|
+
.replace(/<[^>]+>/g, ' ')
|
|
2530
|
+
.replace(/\s+/g, ' ')
|
|
2531
|
+
.trim();
|
|
2532
|
+
if (text.length <= maxLength)
|
|
2533
|
+
return text;
|
|
2534
|
+
return `${text.slice(0, maxLength).trim()}…`;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
async function generatePhplistData$1(config) {
|
|
2538
|
+
console.log('🔄 Generating PHPList newsletter JSON data...');
|
|
2539
|
+
try {
|
|
2540
|
+
const authOptions = {
|
|
2541
|
+
apiUrl: config.apiUrl,
|
|
2542
|
+
apiUser: config.apiUser,
|
|
2543
|
+
apiPassword: config.apiPassword,
|
|
2544
|
+
cfAccessClientId: config.cfAccessClientId,
|
|
2545
|
+
cfAccessClientSecret: config.cfAccessClientSecret,
|
|
2546
|
+
};
|
|
2547
|
+
const list = await phplistApiFetch(authOptions, `/lists/${config.listId}`);
|
|
2548
|
+
const listSlug = slugifyListName(list.name);
|
|
2549
|
+
const existing = readExistingData(config.outputPath);
|
|
2550
|
+
const knownIds = new Set(existing?.newsletters.map((n) => n.id) ?? []);
|
|
2551
|
+
const lastSentAt = existing?.newsletters.reduce((max, n) => {
|
|
2552
|
+
const t = Date.parse(n.sent_at);
|
|
2553
|
+
return Number.isNaN(t) ? max : Math.max(max, t);
|
|
2554
|
+
}, 0) ?? 0;
|
|
2555
|
+
const listMessageIds = await fetchListMessageIds(authOptions, config.listId);
|
|
2556
|
+
const listMessageIdSet = new Set(listMessageIds);
|
|
2557
|
+
const newItems = [];
|
|
2558
|
+
let afterId = 0;
|
|
2559
|
+
let hasMore = true;
|
|
2560
|
+
while (hasMore) {
|
|
2561
|
+
const page = await phplistApiFetch(authOptions, `/campaigns?limit=100&after_id=${afterId || 1}`);
|
|
2562
|
+
const items = page.items ?? [];
|
|
2563
|
+
if (items.length === 0)
|
|
2564
|
+
break;
|
|
2565
|
+
for (const summary of items) {
|
|
2566
|
+
const id = summary.id;
|
|
2567
|
+
if (!id || !listMessageIdSet.has(id) || knownIds.has(id))
|
|
2568
|
+
continue;
|
|
2569
|
+
if (summary.message_metadata?.status !== 'sent')
|
|
2570
|
+
continue;
|
|
2571
|
+
const sentAt = summary.message_metadata?.sent;
|
|
2572
|
+
if (sentAt && lastSentAt > 0 && Date.parse(sentAt) <= lastSentAt) {
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
const detail = await phplistApiFetch(authOptions, `/campaigns/${id}`);
|
|
2576
|
+
const item = messageToNewsletter(detail, listSlug, config.newsletterRouteBase);
|
|
2577
|
+
if (item) {
|
|
2578
|
+
newItems.push(item);
|
|
2579
|
+
knownIds.add(id);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const lastId = items[items.length - 1]?.id;
|
|
2583
|
+
if (!lastId || items.length < 100) {
|
|
2584
|
+
hasMore = false;
|
|
2585
|
+
}
|
|
2586
|
+
else {
|
|
2587
|
+
afterId = lastId;
|
|
2588
|
+
const oldestSent = items
|
|
2589
|
+
.map((m) => m.message_metadata?.sent)
|
|
2590
|
+
.filter(Boolean)
|
|
2591
|
+
.map((s) => Date.parse(s))
|
|
2592
|
+
.filter((t) => !Number.isNaN(t));
|
|
2593
|
+
if (lastSentAt > 0 && oldestSent.length && Math.min(...oldestSent) <= lastSentAt) {
|
|
2594
|
+
hasMore = false;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
const mergedNewsletters = [
|
|
2599
|
+
...(existing?.newsletters ?? []),
|
|
2600
|
+
...newItems,
|
|
2601
|
+
].sort((a, b) => Date.parse(b.sent_at) - Date.parse(a.sent_at));
|
|
2602
|
+
const subscribePages = await fetchSubscribePages(authOptions, config.subscribePageIds);
|
|
2603
|
+
const data = {
|
|
2604
|
+
generated_at: new Date().toISOString(),
|
|
2605
|
+
list: {
|
|
2606
|
+
id: list.id,
|
|
2607
|
+
name: list.name,
|
|
2608
|
+
slug: listSlug,
|
|
2609
|
+
description: list.description,
|
|
2610
|
+
},
|
|
2611
|
+
subscribe_pages: subscribePages,
|
|
2612
|
+
newsletters: mergedNewsletters,
|
|
2613
|
+
};
|
|
2614
|
+
writeStaticData(config.outputPath, data);
|
|
2615
|
+
console.log(`✅ PHPList: ${mergedNewsletters.length} newsletters (${newItems.length} new)`);
|
|
2616
|
+
return { success: true, data };
|
|
2617
|
+
}
|
|
2618
|
+
catch (error) {
|
|
2619
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2620
|
+
console.warn(`⚠️ PHPList generation failed: ${message}`);
|
|
2621
|
+
return { success: false, error: message };
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
async function fetchListMessageIds(authOptions, listId) {
|
|
2625
|
+
const response = await phplistApiFetch(authOptions, `/list-messages/list/${listId}/messages`);
|
|
2626
|
+
return (response.items ?? []).map((row) => row.message_id).filter(Boolean);
|
|
2627
|
+
}
|
|
2628
|
+
async function fetchSubscribePages(authOptions, pageIds) {
|
|
2629
|
+
const pages = [];
|
|
2630
|
+
for (const pageId of pageIds) {
|
|
2631
|
+
try {
|
|
2632
|
+
const publicPage = await phplistApiFetch(authOptions, `/public/subscribe-pages/${pageId}`);
|
|
2633
|
+
pages.push({
|
|
2634
|
+
id: pageId,
|
|
2635
|
+
title: publicPage.title,
|
|
2636
|
+
intro: publicPage.intro,
|
|
2637
|
+
attributes: publicPage.attributes ?? [],
|
|
2638
|
+
list_ids: publicPage.list_ids,
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
catch (e) {
|
|
2642
|
+
console.warn(`⚠️ Could not load subscribe page ${pageId}:`, e.message);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
return pages;
|
|
2646
|
+
}
|
|
2647
|
+
function messageToNewsletter(message, listSlug, _routeBase) {
|
|
2648
|
+
const subject = message.message_content?.subject?.trim();
|
|
2649
|
+
const html = message.message_content?.text ?? '';
|
|
2650
|
+
const sentAt = message.message_metadata?.sent;
|
|
2651
|
+
if (!subject || !sentAt || message.message_metadata?.status !== 'sent') {
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
const slug = buildNewsletterSlug(listSlug, message.id, subject);
|
|
2655
|
+
return {
|
|
2656
|
+
id: message.id,
|
|
2657
|
+
unique_id: message.unique_id,
|
|
2658
|
+
slug,
|
|
2659
|
+
subject,
|
|
2660
|
+
sent_at: sentAt,
|
|
2661
|
+
excerpt: stripHtmlExcerpt(html),
|
|
2662
|
+
html,
|
|
2663
|
+
plain_text: message.message_content?.text_message,
|
|
2664
|
+
view_url: buildViewProxyPath(message.id),
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
function readExistingData(outputPath) {
|
|
2668
|
+
try {
|
|
2669
|
+
if (!fs.existsSync(outputPath))
|
|
2670
|
+
return null;
|
|
2671
|
+
return JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
|
2672
|
+
}
|
|
2673
|
+
catch {
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
function writeStaticData(outputPath, data) {
|
|
2678
|
+
const dir = path.dirname(outputPath);
|
|
2679
|
+
if (!fs.existsSync(dir)) {
|
|
2680
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2681
|
+
}
|
|
2682
|
+
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function parseSubscribePageIds(raw) {
|
|
2686
|
+
if (!raw?.trim())
|
|
2687
|
+
return [];
|
|
2688
|
+
return raw
|
|
2689
|
+
.split(',')
|
|
2690
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
2691
|
+
.filter((n) => !Number.isNaN(n));
|
|
2692
|
+
}
|
|
2693
|
+
async function generatePhplistData(options) {
|
|
2694
|
+
const outputPath = options?.outputPath || './public/phplist-data.json';
|
|
2695
|
+
const phplistUrl = process.env.PHPLIST_URL || process.env.VITE_PHPLIST_URL;
|
|
2696
|
+
const listIdRaw = process.env.PHPLIST_LIST_ID || process.env.VITE_PHPLIST_LIST_ID;
|
|
2697
|
+
const apiUser = process.env.PHPLIST_API_USER || process.env.VITE_PHPLIST_API_USER;
|
|
2698
|
+
const apiPassword = process.env.PHPLIST_API_PASSWORD || process.env.VITE_PHPLIST_API_PASSWORD;
|
|
2699
|
+
const proxyUrl = process.env.PHPLIST_PROXY_URL || process.env.VITE_PHPLIST_PROXY_URL;
|
|
2700
|
+
const subscribePageIds = parseSubscribePageIds(process.env.PHPLIST_SUBSCRIBE_PAGE_IDS || process.env.VITE_PHPLIST_SUBSCRIBE_PAGE_IDS);
|
|
2701
|
+
const newsletterRouteBase = process.env.VITE_PHPLIST_NEWSLETTER_ROUTE_BASE || '/newsletters';
|
|
2702
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || process.env.VITE_CF_ACCESS_CLIENT_ID;
|
|
2703
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || process.env.VITE_CF_ACCESS_CLIENT_SECRET;
|
|
2704
|
+
if (!phplistUrl) {
|
|
2705
|
+
console.log('⚠️ PHPList URL not configured. Skipping PHPList data generation.');
|
|
2706
|
+
return true;
|
|
2707
|
+
}
|
|
2708
|
+
const listId = parseInt(listIdRaw || '0', 10);
|
|
2709
|
+
if (!listId || Number.isNaN(listId)) {
|
|
2710
|
+
console.warn('⚠️ PHPList list ID not configured. Skipping PHPList data generation.');
|
|
2711
|
+
return true;
|
|
2712
|
+
}
|
|
2713
|
+
if (!apiUser || !apiPassword) {
|
|
2714
|
+
console.warn('⚠️ PHPList API credentials missing. Skipping PHPList data generation.');
|
|
2715
|
+
return true;
|
|
2716
|
+
}
|
|
2717
|
+
try {
|
|
2718
|
+
const authMode = proxyUrl ? 'cloudflare_proxy' : 'direct';
|
|
2719
|
+
const result = await generatePhplistData$1({
|
|
2720
|
+
authMode,
|
|
2721
|
+
apiUrl: phplistUrl,
|
|
2722
|
+
cloudflareWorkerUrl: authMode === 'cloudflare_proxy' ? proxyUrl : undefined,
|
|
2723
|
+
apiUser,
|
|
2724
|
+
apiPassword,
|
|
2725
|
+
listId,
|
|
2726
|
+
subscribePageIds,
|
|
2727
|
+
outputPath,
|
|
2728
|
+
newsletterRouteBase,
|
|
2729
|
+
cfAccessClientId,
|
|
2730
|
+
cfAccessClientSecret,
|
|
2731
|
+
});
|
|
2732
|
+
if (result.success) {
|
|
2733
|
+
console.log(`📁 PHPList data written to: ${path.join(process.cwd(), outputPath)}`);
|
|
2734
|
+
}
|
|
2735
|
+
else {
|
|
2736
|
+
console.warn(`⚠️ PHPList generation completed with warnings: ${result.error ?? 'unknown'}`);
|
|
2737
|
+
}
|
|
2738
|
+
return true;
|
|
2739
|
+
}
|
|
2740
|
+
catch (e) {
|
|
2741
|
+
console.warn(`⚠️ PHPList generation failed: ${e.message}`);
|
|
2742
|
+
return true;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2436
2746
|
async function generateSuiteCRMData(options) {
|
|
2437
2747
|
const outputPath = options?.outputPath || './src/data/suitecrm-data.json';
|
|
2438
2748
|
const apiUrl = process.env.VITE_SUITECRM_URL;
|
|
@@ -2780,6 +3090,7 @@ async function generateAllData() {
|
|
|
2780
3090
|
const wpSuccess = await generateWordPressData();
|
|
2781
3091
|
const gfSuccess = await generateGravityFormsData();
|
|
2782
3092
|
const mauticSuccess = await generateMauticData();
|
|
3093
|
+
await generatePhplistData();
|
|
2783
3094
|
const suitecrmSuccess = await generateSuiteCRMData();
|
|
2784
3095
|
const googleReviewsSuccess = await generateGoogleReviewsData();
|
|
2785
3096
|
if (!wpSuccess || !gfSuccess || !mauticSuccess || !suitecrmSuccess || !googleReviewsSuccess) {
|
|
@@ -2790,5 +3101,5 @@ async function generateAllData() {
|
|
|
2790
3101
|
console.log('✅ Generation complete!');
|
|
2791
3102
|
}
|
|
2792
3103
|
|
|
2793
|
-
export { PSEUDO_POST_TYPES, generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData, getWordPressPostTypes, isGoogleReviewsEnabled, parseEnabledPostTypes };
|
|
3104
|
+
export { PSEUDO_POST_TYPES, generateAllData, generateGoogleReviewsData, generateGravityFormsData, generateMauticData, generatePhplistData, generateSitemapPaths, generateSitemapPathsSafe, generateSuiteCRMData, generateWordPressData, getWordPressPostTypes, isGoogleReviewsEnabled, parseEnabledPostTypes };
|
|
2794
3105
|
//# sourceMappingURL=generators.esm.js.map
|