codeplay-common 4.5.6 → 4.5.8

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.
@@ -208,12 +208,15 @@ async function ensureEndCardBanner() {
208
208
  + '- Keep all logos and badge text sharp, correctly proportioned, and completely visible.\n'
209
209
  + '- Match the background colors and visual style to the actual app icon.\n'
210
210
  + '- Create a polished, professional background related to the app’s purpose.\n'
211
- + '- Keep the background subtle so it does not compete with the icon or download badges.\n'
211
+ + '- Keep the background subtle so it does not compete with the icon, title, feature text, or download badges.\n'
212
212
  + '- Use a balanced landscape composition:\n'
213
213
  + ' - Large actual app icon on the left.\n'
214
214
  + ' - App Store badge followed by Google Play badge on the right.\n'
215
215
  + ' - Maintain comfortable margins and clear space around every element.\n'
216
- + '- Do not add extra marketing text unless it is specifically requested.\n'
216
+ + '- Add the application title prominently.\n'
217
+ + '- Add concise promotional text describing the application’s actual features, presenting each feature one by one.\n'
218
+ + '- Keep the title and feature text sharp, readable, accurate, and visually balanced with the icon and download badges.\n'
219
+ + '- Do not invent unsupported features or add unrelated marketing text.\n'
217
220
  + '- Do not add watermarks, fake UI, generated symbols, or unrelated decorations.\n'
218
221
  + '- Rounded corners and a subtle shadow may be applied around the app icon, but its internal artwork must remain unchanged.\n'
219
222
  + '- Use image generation only for the background.\n'
@@ -590,13 +590,6 @@ async function getPageDetails(client) {
590
590
  async function capturePageSnapshot(client) {
591
591
  const evaluation = await client.send('Runtime.evaluate', {
592
592
  expression: `(async () => {
593
- const clonedRoot = document.documentElement.cloneNode(true);
594
- const originalElements = [
595
- document.documentElement,
596
- ...document.documentElement.querySelectorAll('*'),
597
- ];
598
- const clonedElements = [clonedRoot, ...clonedRoot.querySelectorAll('*')];
599
-
600
593
  const blobToDataUrl = (blob) => new Promise((resolve, reject) => {
601
594
  const reader = new FileReader();
602
595
  reader.onload = () => resolve(reader.result);
@@ -617,109 +610,147 @@ async function capturePageSnapshot(client) {
617
610
  return canvas.toDataURL('image/png');
618
611
  };
619
612
 
620
- for (let index = 0; index < originalElements.length; index += 1) {
621
- const original = originalElements[index];
622
- const clone = clonedElements[index];
613
+ const serializeDocument = async (sourceDocument) => {
614
+ const clonedRoot = sourceDocument.documentElement.cloneNode(true);
615
+ const originalElements = [
616
+ sourceDocument.documentElement,
617
+ ...sourceDocument.documentElement.querySelectorAll('*'),
618
+ ];
619
+ const clonedElements = [clonedRoot, ...clonedRoot.querySelectorAll('*')];
623
620
 
624
- if (!clone) continue;
621
+ clonedRoot.setAttribute(
622
+ 'data-responsive-window-scroll-y',
623
+ String(sourceDocument.defaultView?.scrollY || 0),
624
+ );
625
625
 
626
- if (original.scrollTop || original.scrollLeft) {
627
- clone.setAttribute('data-responsive-scroll-top', String(original.scrollTop));
628
- clone.setAttribute('data-responsive-scroll-left', String(original.scrollLeft));
629
- }
626
+ for (let index = 0; index < originalElements.length; index += 1) {
627
+ const original = originalElements[index];
628
+ const clone = clonedElements[index];
630
629
 
631
- if (original.classList.contains('swiper-initialized')) {
632
- clone.setAttribute('data-responsive-swiper-width', String(original.clientWidth));
633
- clone.setAttribute('data-responsive-swiper-height', String(original.clientHeight));
634
- }
630
+ if (!clone) continue;
635
631
 
636
- if (original instanceof HTMLInputElement) {
637
- clone.setAttribute('value', original.value);
638
- clone.toggleAttribute('checked', original.checked);
639
- } else if (original instanceof HTMLTextAreaElement) {
640
- clone.textContent = original.value;
641
- } else if (original instanceof HTMLSelectElement) {
642
- for (let optionIndex = 0; optionIndex < original.options.length; optionIndex += 1) {
643
- clone.options[optionIndex].selected = original.options[optionIndex].selected;
644
- }
645
- } else if (original instanceof HTMLCanvasElement) {
646
- try {
647
- clone.style.setProperty(
648
- 'background-image',
649
- 'url("' + original.toDataURL('image/png') + '")',
650
- 'important',
651
- );
652
- clone.style.setProperty('background-position', 'center', 'important');
653
- clone.style.setProperty('background-repeat', 'no-repeat', 'important');
654
- clone.style.setProperty('background-size', '100% 100%', 'important');
655
- } catch {
656
- // Keep the canvas element when its pixels cannot be exported.
632
+ if (original.scrollTop || original.scrollLeft) {
633
+ clone.setAttribute('data-responsive-scroll-top', String(original.scrollTop));
634
+ clone.setAttribute('data-responsive-scroll-left', String(original.scrollLeft));
657
635
  }
658
- } else if (original instanceof HTMLVideoElement && original.readyState >= 2) {
659
- try {
660
- const replacement = document.createElement('img');
661
- replacement.src = imageToDataUrl(original);
662
- replacement.alt = original.getAttribute('aria-label') || '';
663
- replacement.setAttribute('style', original.getAttribute('style') || '');
664
- clone.replaceWith(replacement);
665
- } catch {
666
- clone.setAttribute('poster', original.poster || '');
636
+
637
+ if (original.classList.contains('swiper-initialized')) {
638
+ clone.setAttribute('data-responsive-swiper-width', String(original.clientWidth));
639
+ clone.setAttribute('data-responsive-swiper-height', String(original.clientHeight));
667
640
  }
668
- } else if (original instanceof HTMLImageElement && original.complete) {
669
- try {
670
- clone.setAttribute('src', imageToDataUrl(original) || original.currentSrc || original.src);
671
- clone.removeAttribute('srcset');
672
- } catch {
673
- clone.setAttribute('src', original.currentSrc || original.src);
641
+
642
+ const tagName = original.tagName?.toLowerCase();
643
+
644
+ if (tagName === 'input') {
645
+ clone.setAttribute('value', original.value);
646
+ clone.toggleAttribute('checked', original.checked);
647
+ } else if (tagName === 'textarea') {
648
+ clone.textContent = original.value;
649
+ } else if (tagName === 'select') {
650
+ for (let optionIndex = 0; optionIndex < original.options.length; optionIndex += 1) {
651
+ clone.options[optionIndex].selected = original.options[optionIndex].selected;
652
+ }
653
+ } else if (tagName === 'canvas') {
654
+ try {
655
+ clone.style.setProperty(
656
+ 'background-image',
657
+ 'url("' + original.toDataURL('image/png') + '")',
658
+ 'important',
659
+ );
660
+ clone.style.setProperty('background-position', 'center', 'important');
661
+ clone.style.setProperty('background-repeat', 'no-repeat', 'important');
662
+ clone.style.setProperty('background-size', '100% 100%', 'important');
663
+ } catch {
664
+ // Keep the canvas element when its pixels cannot be exported.
665
+ }
666
+ } else if (tagName === 'video' && original.readyState >= 2) {
667
+ try {
668
+ const replacement = sourceDocument.createElement('img');
669
+ replacement.src = imageToDataUrl(original);
670
+ replacement.alt = original.getAttribute('aria-label') || '';
671
+ replacement.setAttribute('style', original.getAttribute('style') || '');
672
+ clone.replaceWith(replacement);
673
+ } catch {
674
+ clone.setAttribute('poster', original.poster || '');
675
+ }
676
+ } else if (tagName === 'img' && original.complete) {
677
+ try {
678
+ clone.setAttribute('src', imageToDataUrl(original) || original.currentSrc || original.src);
679
+ clone.removeAttribute('srcset');
680
+ } catch {
681
+ clone.setAttribute('src', original.currentSrc || original.src);
682
+ }
683
+ } else if (tagName === 'iframe') {
684
+ try {
685
+ const frameDocument = original.contentDocument;
686
+
687
+ if (frameDocument?.documentElement) {
688
+ const frameHtml = await serializeDocument(frameDocument);
689
+ clone.removeAttribute('src');
690
+ clone.setAttribute('srcdoc', '<!doctype html>' + frameHtml);
691
+ }
692
+ } catch {
693
+ // Cross-origin frames cannot be serialized and keep their original source.
694
+ }
674
695
  }
675
696
  }
676
- }
677
697
 
678
- const stylesheetLinks = [...document.querySelectorAll('link[rel="stylesheet"]')];
679
- const clonedStylesheetLinks = [...clonedRoot.querySelectorAll('link[rel="stylesheet"]')];
698
+ const stylesheetLinks = [...sourceDocument.querySelectorAll('link[rel="stylesheet"]')];
699
+ const clonedStylesheetLinks = [...clonedRoot.querySelectorAll('link[rel="stylesheet"]')];
680
700
 
681
- await Promise.all(stylesheetLinks.map(async (link, index) => {
682
- try {
683
- const response = await fetch(link.href);
684
- if (!response.ok) return;
685
-
686
- const cssText = (await response.text()).replace(
687
- /url\\(\\s*(['"]?)(?!data:|blob:|#)([^'"\\)]+)\\1\\s*\\)/gi,
688
- (match, quote, resourceUrl) => {
689
- try {
690
- return 'url("' + new URL(resourceUrl.trim(), link.href).href + '")';
691
- } catch {
692
- return match;
693
- }
694
- },
695
- );
696
- const style = document.createElement('style');
697
- style.textContent = cssText;
698
- clonedStylesheetLinks[index].replaceWith(style);
699
- } catch {
700
- clonedStylesheetLinks[index].href = link.href;
701
+ await Promise.all(stylesheetLinks.map(async (link, index) => {
702
+ try {
703
+ const response = await fetch(link.href);
704
+ if (!response.ok) return;
705
+
706
+ const cssText = (await response.text()).replace(
707
+ /url\\(\\s*(['"]?)(?!data:|blob:|#)([^'"\\)]+)\\1\\s*\\)/gi,
708
+ (match, quote, resourceUrl) => {
709
+ try {
710
+ return 'url("' + new URL(resourceUrl.trim(), link.href).href + '")';
711
+ } catch {
712
+ return match;
713
+ }
714
+ },
715
+ );
716
+ const style = sourceDocument.createElement('style');
717
+ style.textContent = cssText;
718
+ clonedStylesheetLinks[index].replaceWith(style);
719
+ } catch {
720
+ clonedStylesheetLinks[index].href = link.href;
721
+ }
722
+ }));
723
+
724
+ clonedRoot.querySelectorAll('script, link[rel="modulepreload"]').forEach(
725
+ (element) => element.remove(),
726
+ );
727
+ clonedRoot.querySelectorAll('meta[http-equiv="Content-Security-Policy"]').forEach(
728
+ (element) => element.remove(),
729
+ );
730
+
731
+ const head = clonedRoot.querySelector('head');
732
+
733
+ if (head) {
734
+ const base = sourceDocument.createElement('base');
735
+ base.href = sourceDocument.location.href;
736
+ head.prepend(base);
737
+
738
+ const replayStyle = sourceDocument.createElement('style');
739
+ replayStyle.textContent = [
740
+ '*,*::before,*::after{animation:none!important;transition:none!important;}',
741
+ '*{scrollbar-width:none!important;}',
742
+ '*::-webkit-scrollbar{width:0!important;height:0!important;display:none!important;}',
743
+ ].join('');
744
+ head.append(replayStyle);
701
745
  }
702
- }));
703
746
 
704
- clonedRoot.querySelectorAll('script, link[rel="modulepreload"]').forEach((element) => element.remove());
705
- clonedRoot.querySelectorAll('meta[http-equiv="Content-Security-Policy"]').forEach(
706
- (element) => element.remove(),
707
- );
708
-
709
- const base = document.createElement('base');
710
- base.href = location.href;
711
- clonedRoot.querySelector('head').prepend(base);
747
+ return clonedRoot.outerHTML;
748
+ };
712
749
 
713
- const replayStyle = document.createElement('style');
714
- replayStyle.textContent = [
715
- '*,*::before,*::after{animation-play-state:paused!important;}',
716
- '*{scrollbar-width:none!important;}',
717
- '*::-webkit-scrollbar{width:0!important;height:0!important;display:none!important;}',
718
- ].join('');
719
- clonedRoot.querySelector('head').append(replayStyle);
750
+ const html = await serializeDocument(document);
720
751
 
721
752
  return {
722
- html: '<!doctype html>' + clonedRoot.outerHTML,
753
+ html: '<!doctype html>' + html,
723
754
  scrollY,
724
755
  };
725
756
  })()`,
@@ -876,7 +907,43 @@ async function closeReplayBrowser(browser) {
876
907
  async function waitForReplayLayout(client, snapshot) {
877
908
  const evaluation = await client.send('Runtime.evaluate', {
878
909
  expression: `(async () => {
879
- const assetWait = Promise.all([...document.images].map((image) => {
910
+ const collectDocuments = (rootDocument) => {
911
+ const documents = [rootDocument];
912
+
913
+ rootDocument.querySelectorAll('iframe').forEach((frame) => {
914
+ try {
915
+ if (frame.contentDocument?.documentElement) {
916
+ documents.push(...collectDocuments(frame.contentDocument));
917
+ }
918
+ } catch {
919
+ // Cross-origin frames cannot participate in replay layout restoration.
920
+ }
921
+ });
922
+
923
+ return documents;
924
+ };
925
+
926
+ const frameDeadline = Date.now() + 5000;
927
+
928
+ while (Date.now() < frameDeadline) {
929
+ const frames = [...document.querySelectorAll('iframe')];
930
+ const framesReady = frames.every((frame) => {
931
+ try {
932
+ return frame.contentDocument?.readyState === 'complete';
933
+ } catch {
934
+ return true;
935
+ }
936
+ });
937
+
938
+ if (framesReady) break;
939
+ await new Promise((resolve) => setTimeout(resolve, 50));
940
+ }
941
+
942
+ const replayDocuments = collectDocuments(document);
943
+ const replayImages = replayDocuments.flatMap((replayDocument) => (
944
+ [...replayDocument.images]
945
+ ));
946
+ const assetWait = Promise.all(replayImages.map((image) => {
880
947
  if (image.complete) return Promise.resolve();
881
948
  return new Promise((resolve) => {
882
949
  image.addEventListener('load', resolve, { once: true });
@@ -885,11 +952,18 @@ async function waitForReplayLayout(client, snapshot) {
885
952
  }));
886
953
 
887
954
  await Promise.race([
888
- Promise.all([document.fonts.ready, assetWait]),
955
+ Promise.all([
956
+ ...replayDocuments.map((replayDocument) => replayDocument.fonts.ready),
957
+ assetWait,
958
+ ]),
889
959
  new Promise((resolve) => setTimeout(resolve, 5000)),
890
960
  ]);
891
961
 
892
- document.querySelectorAll('[data-responsive-swiper-width]').forEach((swiper) => {
962
+ const replayElements = (selector) => replayDocuments.flatMap((replayDocument) => (
963
+ [...replayDocument.querySelectorAll(selector)]
964
+ ));
965
+
966
+ replayElements('[data-responsive-swiper-width]').forEach((swiper) => {
893
967
  const wrapper = [...swiper.children].find((element) => (
894
968
  element.classList.contains('swiper-wrapper')
895
969
  ));
@@ -947,10 +1021,17 @@ async function waitForReplayLayout(client, snapshot) {
947
1021
  }
948
1022
  });
949
1023
 
950
- document.querySelectorAll('[data-responsive-scroll-top]').forEach((element) => {
1024
+ replayElements('[data-responsive-scroll-top]').forEach((element) => {
951
1025
  element.scrollTop = Number(element.dataset.responsiveScrollTop);
952
1026
  element.scrollLeft = Number(element.dataset.responsiveScrollLeft);
953
1027
  });
1028
+
1029
+ replayDocuments.forEach((replayDocument) => {
1030
+ replayDocument.defaultView?.scrollTo(
1031
+ 0,
1032
+ Number(replayDocument.documentElement.dataset.responsiveWindowScrollY) || 0,
1033
+ );
1034
+ });
954
1035
  scrollTo(0, ${Number(snapshot.scrollY) || 0});
955
1036
 
956
1037
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeplay-common",
3
- "version": "4.5.6",
3
+ "version": "4.5.8",
4
4
  "description": "Common build scripts and files",
5
5
  "scripts": {
6
6
  "postinstall": "node scripts/sync-files.js",