@marvalt/wadapter 2.3.26 → 2.3.29

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.
@@ -1,6 +1,5 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
- import { join, extname, dirname } from 'path';
3
- import { createHash } from 'crypto';
1
+ import { writeFileSync } from 'fs';
2
+ import { join } from 'path';
4
3
 
5
4
  /**
6
5
  * @license GPL-3.0-or-later
@@ -264,13 +263,6 @@ class WordPressClient {
264
263
  async getSettings() {
265
264
  return this.makeRequest('/wp-custom/v1/settings');
266
265
  }
267
- /**
268
- * Fetch Theme Styles from custom endpoint
269
- * @returns Theme customization settings (typography, colors, backgrounds, shadows, layout)
270
- */
271
- async getThemeStyles() {
272
- return this.makeRequest('/wp-custom/v1/theme-styles');
273
- }
274
266
  }
275
267
 
276
268
  let document;
@@ -472,101 +464,6 @@ function addBlockFromStack(endOffset) {
472
464
  output.push(block);
473
465
  }
474
466
 
475
- /**
476
- * @license GPL-3.0-or-later
477
- *
478
- * This file is part of the MarVAlt Open SDK.
479
- * Copyright (c) 2025 Vibune Pty Ltd.
480
- *
481
- * This program is free software: you can redistribute it and/or modify
482
- * it under the terms of the GNU General Public License as published by
483
- * the Free Software Foundation, either version 3 of the License, or
484
- * (at your option) any later version.
485
- *
486
- * This program is distributed in the hope that it will be useful,
487
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
488
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
489
- * See the GNU General Public License for more details.
490
- */
491
- /**
492
- * Generate a unique filename for an image based on its URL
493
- */
494
- function generateImageFilename(imageUrl) {
495
- // Create hash from URL for uniqueness
496
- const hash = createHash('md5').update(imageUrl).digest('hex').substring(0, 12);
497
- // Try to extract extension from URL
498
- const urlPath = new URL(imageUrl).pathname;
499
- const ext = extname(urlPath) || '.jpg'; // Default to .jpg if no extension
500
- return `${hash}${ext}`;
501
- }
502
- /**
503
- * Download an image from WordPress and save it to public/images
504
- * Returns the local path relative to public directory (e.g., /images/abc123.jpg)
505
- * Returns null if download fails
506
- */
507
- async function downloadImageToPublic(imageUrl, config) {
508
- try {
509
- // Generate filename
510
- const filename = generateImageFilename(imageUrl);
511
- const localPath = join(config.outputDir, filename);
512
- const publicPath = `/images/${filename}`;
513
- // Check if image already exists
514
- if (existsSync(localPath)) {
515
- return publicPath;
516
- }
517
- // Ensure output directory exists
518
- mkdirSync(config.outputDir, { recursive: true });
519
- // Prepare fetch options with authentication if provided
520
- const fetchOptions = {
521
- method: 'GET',
522
- headers: {
523
- 'User-Agent': 'MarVAlt-Wadapter/1.0',
524
- },
525
- signal: AbortSignal.timeout(config.timeout || 30000), // 30 second timeout
526
- };
527
- // Add Basic Auth if credentials provided
528
- if (config.username && config.password) {
529
- const auth = Buffer.from(`${config.username}:${config.password}`).toString('base64');
530
- fetchOptions.headers = {
531
- ...fetchOptions.headers,
532
- 'Authorization': `Basic ${auth}`,
533
- };
534
- }
535
- // Download image
536
- const response = await fetch(imageUrl, fetchOptions);
537
- if (!response.ok) {
538
- console.warn(`⚠️ Failed to download image: ${imageUrl} (Status: ${response.status})`);
539
- return null;
540
- }
541
- // Get image as buffer
542
- const arrayBuffer = await response.arrayBuffer();
543
- const buffer = Buffer.from(arrayBuffer);
544
- // Write to file
545
- writeFileSync(localPath, buffer);
546
- return publicPath;
547
- }
548
- catch (error) {
549
- console.warn(`⚠️ Error downloading image ${imageUrl}:`, error instanceof Error ? error.message : String(error));
550
- return null;
551
- }
552
- }
553
- /**
554
- * Batch download images with concurrency control
555
- */
556
- async function downloadImagesBatch(imageUrls, config, concurrency = 5) {
557
- const results = new Map();
558
- const uniqueUrls = Array.from(new Set(imageUrls)); // Deduplicate
559
- // Process in batches
560
- for (let i = 0; i < uniqueUrls.length; i += concurrency) {
561
- const batch = uniqueUrls.slice(i, i + concurrency);
562
- const batchResults = await Promise.all(batch.map(url => downloadImageToPublic(url, config).then(path => ({ url, path }))));
563
- batchResults.forEach(({ url, path }) => {
564
- results.set(url, path);
565
- });
566
- }
567
- return results;
568
- }
569
-
570
467
  /**
571
468
  * @license GPL-3.0-or-later
572
469
  *
@@ -586,233 +483,8 @@ async function downloadImagesBatch(imageUrls, config, concurrency = 5) {
586
483
  // WordPress Static Data Generator
587
484
  class WordPressGenerator {
588
485
  constructor(config) {
589
- this.imageDownloadCache = new Map();
590
486
  this.config = config;
591
487
  }
592
- /**
593
- * Check if an image has Cloudflare URL available
594
- */
595
- hasCloudflareImage(item) {
596
- return !!(item?.cloudflare_image ||
597
- item?.cloudflareImage ||
598
- item?._embedded?.['wp:featuredmedia']?.[0]?.cloudflare_image ||
599
- item?._embedded?.['wp:featuredmedia']?.[0]?.cloudflareImage);
600
- }
601
- /**
602
- * Extract image URL from various sources (media item, featured media, etc.)
603
- */
604
- extractImageUrl(item) {
605
- // Check for Cloudflare image first
606
- if (item?.cloudflare_image)
607
- return item.cloudflare_image;
608
- if (item?.cloudflareImage)
609
- return item.cloudflareImage;
610
- // Check embedded featured media
611
- const featuredMedia = item?._embedded?.['wp:featuredmedia']?.[0];
612
- if (featuredMedia?.cloudflare_image)
613
- return featuredMedia.cloudflare_image;
614
- if (featuredMedia?.cloudflareImage)
615
- return featuredMedia.cloudflareImage;
616
- // Fallback to source_url
617
- if (item?.source_url)
618
- return item.source_url;
619
- if (featuredMedia?.source_url)
620
- return featuredMedia.source_url;
621
- return null;
622
- }
623
- /**
624
- * Process images in blocks recursively
625
- */
626
- extractImageUrlsFromBlocks(blocks) {
627
- const urls = [];
628
- const processBlock = (block) => {
629
- if (!block || typeof block !== 'object')
630
- return;
631
- // Check block attributes for image URLs
632
- if (block.attributes) {
633
- const attrs = block.attributes;
634
- // core/image block
635
- if (attrs.url)
636
- urls.push(attrs.url);
637
- if (attrs.mediaUrl)
638
- urls.push(attrs.mediaUrl);
639
- // core/cover block
640
- if (attrs.url)
641
- urls.push(attrs.url);
642
- if (attrs.backgroundImage)
643
- urls.push(attrs.backgroundImage);
644
- // core/media-text block
645
- if (attrs.mediaUrl)
646
- urls.push(attrs.mediaUrl);
647
- if (attrs.mediaLink)
648
- urls.push(attrs.mediaLink);
649
- // core/gallery block
650
- if (Array.isArray(attrs.images)) {
651
- attrs.images.forEach((img) => {
652
- if (img.url)
653
- urls.push(img.url);
654
- if (img.source_url)
655
- urls.push(img.source_url);
656
- });
657
- }
658
- }
659
- // Process inner blocks recursively
660
- if (Array.isArray(block.innerBlocks)) {
661
- block.innerBlocks.forEach(processBlock);
662
- }
663
- };
664
- blocks.forEach(processBlock);
665
- return urls;
666
- }
667
- /**
668
- * Process and download images if needed
669
- * Downloads images when Cloudflare images are not available (works in both direct and proxy modes)
670
- */
671
- async processImages(items, itemType, outputDir) {
672
- // Collect all image URLs that need downloading
673
- const imageUrls = [];
674
- // Process media items
675
- if (itemType === 'media') {
676
- items.forEach((item) => {
677
- if (!this.hasCloudflareImage(item)) {
678
- const url = this.extractImageUrl(item);
679
- if (url && !url.includes('imagedelivery.net')) {
680
- imageUrls.push(url);
681
- }
682
- }
683
- });
684
- }
685
- // Process posts/pages (featured media and block content)
686
- if (itemType === 'posts' || itemType === 'pages') {
687
- items.forEach((item) => {
688
- // Featured media
689
- const featuredMedia = item?._embedded?.['wp:featuredmedia']?.[0];
690
- if (featuredMedia && !this.hasCloudflareImage(featuredMedia)) {
691
- const url = this.extractImageUrl(featuredMedia);
692
- if (url && !url.includes('imagedelivery.net')) {
693
- imageUrls.push(url);
694
- }
695
- }
696
- // Block content images
697
- if (Array.isArray(item.blocks)) {
698
- const blockUrls = this.extractImageUrlsFromBlocks(item.blocks);
699
- blockUrls.forEach(url => {
700
- if (url && !url.includes('imagedelivery.net') && !url.startsWith('/images/')) {
701
- imageUrls.push(url);
702
- }
703
- });
704
- }
705
- });
706
- }
707
- if (imageUrls.length === 0) {
708
- return;
709
- }
710
- console.log(`📥 Downloading ${imageUrls.length} image(s) for ${itemType}...`);
711
- // Download images
712
- const downloadConfig = {
713
- outputDir,
714
- username: this.config.username,
715
- password: this.config.password,
716
- timeout: 30000,
717
- };
718
- const downloadResults = await downloadImagesBatch(imageUrls, downloadConfig, 5);
719
- // Cache results
720
- downloadResults.forEach((path, url) => {
721
- this.imageDownloadCache.set(url, path);
722
- });
723
- const successCount = Array.from(downloadResults.values()).filter(p => p !== null).length;
724
- console.log(`✅ Downloaded ${successCount}/${imageUrls.length} image(s) for ${itemType}`);
725
- }
726
- /**
727
- * Replace WordPress image URLs with local paths in an item
728
- */
729
- replaceImageUrls(item, itemType) {
730
- const processed = { ...item };
731
- // Process media items
732
- if (itemType === 'media') {
733
- if (!this.hasCloudflareImage(processed)) {
734
- const originalUrl = this.extractImageUrl(processed);
735
- if (originalUrl) {
736
- const localPath = this.imageDownloadCache.get(originalUrl);
737
- if (localPath) {
738
- processed.source_url = localPath;
739
- if (processed.media_details?.sizes) {
740
- // Update sizes URLs
741
- Object.keys(processed.media_details.sizes).forEach(size => {
742
- const sizeData = processed.media_details.sizes[size];
743
- if (sizeData.source_url) {
744
- const sizeLocalPath = this.imageDownloadCache.get(sizeData.source_url);
745
- if (sizeLocalPath) {
746
- sizeData.source_url = sizeLocalPath;
747
- }
748
- }
749
- });
750
- }
751
- }
752
- }
753
- }
754
- }
755
- // Process posts/pages
756
- if (itemType === 'posts' || itemType === 'pages') {
757
- // Featured media
758
- if (processed._embedded?.['wp:featuredmedia']?.[0]) {
759
- const featuredMedia = { ...processed._embedded['wp:featuredmedia'][0] };
760
- if (!this.hasCloudflareImage(featuredMedia)) {
761
- const originalUrl = this.extractImageUrl(featuredMedia);
762
- if (originalUrl) {
763
- const localPath = this.imageDownloadCache.get(originalUrl);
764
- if (localPath) {
765
- featuredMedia.source_url = localPath;
766
- processed._embedded['wp:featuredmedia'] = [featuredMedia];
767
- }
768
- }
769
- }
770
- }
771
- // Block content
772
- if (Array.isArray(processed.blocks)) {
773
- const replaceBlockUrls = (block) => {
774
- const processedBlock = { ...block };
775
- if (processedBlock.attributes) {
776
- const attrs = { ...processedBlock.attributes };
777
- // Replace various image URL fields
778
- ['url', 'mediaUrl', 'backgroundImage', 'mediaLink'].forEach(field => {
779
- if (attrs[field]) {
780
- const localPath = this.imageDownloadCache.get(attrs[field]);
781
- if (localPath) {
782
- attrs[field] = localPath;
783
- }
784
- }
785
- });
786
- // Handle gallery images
787
- if (Array.isArray(attrs.images)) {
788
- attrs.images = attrs.images.map((img) => {
789
- const processedImg = { ...img };
790
- if (img.url) {
791
- const localPath = this.imageDownloadCache.get(img.url);
792
- if (localPath)
793
- processedImg.url = localPath;
794
- }
795
- if (img.source_url) {
796
- const localPath = this.imageDownloadCache.get(img.source_url);
797
- if (localPath)
798
- processedImg.source_url = localPath;
799
- }
800
- return processedImg;
801
- });
802
- }
803
- processedBlock.attributes = attrs;
804
- }
805
- // Process inner blocks
806
- if (Array.isArray(processedBlock.innerBlocks)) {
807
- processedBlock.innerBlocks = processedBlock.innerBlocks.map(replaceBlockUrls);
808
- }
809
- return processedBlock;
810
- };
811
- processed.blocks = processed.blocks.map(replaceBlockUrls);
812
- }
813
- }
814
- return processed;
815
- }
816
488
  async generateStaticData() {
817
489
  const client = new WordPressClient(this.config);
818
490
  console.log('🚀 Starting WordPress static data generation...');
@@ -828,7 +500,7 @@ class WordPressGenerator {
828
500
  };
829
501
  console.log(`📋 Fetching post types: ${postTypes.join(', ')}`);
830
502
  console.log(`⚙️ Settings: maxItems=${fetchParams.per_page}, includeEmbedded=${fetchParams._embed}`);
831
- // Fetch WordPress Settings (Reading Settings + Site Branding)
503
+ // Fetch WordPress Settings (Reading Settings + Site Branding + Theme Styles)
832
504
  let frontPage;
833
505
  let settingsPageId;
834
506
  let siteSettings;
@@ -864,53 +536,24 @@ class WordPressGenerator {
864
536
  console.log(`✅ Site name: ${siteSettings.site_name}`);
865
537
  }
866
538
  }
867
- }
868
- catch (error) {
869
- console.warn('⚠️ Could not fetch WordPress settings, will use fallback logic:', error);
870
- }
871
- // Fetch Theme Styles (Typography, Colors, Backgrounds, Shadows, Layout)
872
- try {
873
- console.log('🎨 Fetching Theme Styles...');
874
- const styles = await client.getThemeStyles();
875
- if (styles && (styles.typography || styles.colors || styles.backgrounds || styles.shadows || styles.layout || styles.theme_palette)) {
876
- themeStyles = styles;
539
+ // Extract theme styles
540
+ if (settings.theme_styles) {
541
+ themeStyles = settings.theme_styles;
877
542
  console.log(`✅ Theme styles found:`, {
878
- typography: !!styles.typography,
879
- colors: !!styles.colors,
880
- theme_palette: !!styles.theme_palette,
881
- theme_palette_keys: styles.theme_palette ? Object.keys(styles.theme_palette) : [],
882
- backgrounds: !!styles.backgrounds,
883
- shadows: !!styles.shadows,
884
- layout: !!styles.layout,
543
+ hasTypography: !!themeStyles.typography,
544
+ hasColors: !!themeStyles.colors,
545
+ hasLayout: !!themeStyles.layout,
885
546
  });
886
- if (styles.theme_palette) {
887
- console.log(`🎨 Theme palette colors:`, styles.theme_palette);
888
- }
547
+ }
548
+ else {
549
+ console.log('⚠️ No theme_styles found in settings response');
889
550
  }
890
551
  }
891
552
  catch (error) {
892
- console.warn('⚠️ Could not fetch theme styles, will use defaults:', error);
553
+ console.warn('⚠️ Could not fetch WordPress settings, will use fallback logic:', error);
893
554
  }
894
555
  // Fetch standard post types
895
556
  const data = await client.getAllData(fetchParams);
896
- // Determine output directory for images (public/images relative to output file)
897
- const outputDir = join(process.cwd(), dirname(this.config.outputPath), 'images');
898
- // Process and download images if needed (when Cloudflare images are not available)
899
- console.log('📸 Processing images for fallback download...');
900
- // Process media items
901
- if (data.media && data.media.length > 0) {
902
- await this.processImages(data.media, 'media', outputDir);
903
- data.media = data.media.map(item => this.replaceImageUrls(item, 'media'));
904
- }
905
- // Process posts
906
- if (data.posts && data.posts.length > 0) {
907
- await this.processImages(data.posts, 'posts', outputDir);
908
- data.posts = data.posts.map(item => this.replaceImageUrls(item, 'posts'));
909
- }
910
- // Process pages (will be processed again after blocks are fetched)
911
- if (data.pages && data.pages.length > 0) {
912
- await this.processImages(data.pages, 'pages', outputDir);
913
- }
914
557
  // Determine front page if not already set from settings
915
558
  if (!frontPage) {
916
559
  if (settingsPageId) {
@@ -1038,31 +681,7 @@ class WordPressGenerator {
1038
681
  }
1039
682
  }
1040
683
  }
1041
- // Process images in blocks if needed
1042
- let processedPage = { ...p, blocks };
1043
- if (blocks.length > 0) {
1044
- // Extract and download any new images from blocks
1045
- const blockUrls = this.extractImageUrlsFromBlocks(blocks);
1046
- const newUrls = blockUrls.filter(url => url &&
1047
- !url.includes('imagedelivery.net') &&
1048
- !url.startsWith('/images/') &&
1049
- !this.imageDownloadCache.has(url));
1050
- if (newUrls.length > 0) {
1051
- const downloadConfig = {
1052
- outputDir,
1053
- username: this.config.username,
1054
- password: this.config.password,
1055
- timeout: 30000,
1056
- };
1057
- const downloadResults = await downloadImagesBatch(newUrls, downloadConfig, 5);
1058
- downloadResults.forEach((path, url) => {
1059
- this.imageDownloadCache.set(url, path);
1060
- });
1061
- }
1062
- // Replace URLs in blocks
1063
- processedPage = this.replaceImageUrls(processedPage, 'pages');
1064
- }
1065
- return processedPage;
684
+ return { ...p, blocks };
1066
685
  })),
1067
686
  media: data.media,
1068
687
  categories: data.categories,