framer-export 5.0.0-beta.1 → 5.0.0-beta.2
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/README.md +112 -208
- package/dist/cli/index.js +2377 -819
- package/dist/cli/index.js.map +1 -1
- package/package.json +3 -4
package/dist/cli/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "framer-export",
|
|
18
|
-
version: "5.0.0-beta.
|
|
18
|
+
version: "5.0.0-beta.2",
|
|
19
19
|
description: "Export 25+ website platforms (Framer, Webflow, Wix, Shopify, Notion, WordPress, Squarespace, Ghost, and more) into a fully working local mirror. Downloads all assets, strips badges, rewrites URLs, and pretty-prints JS.",
|
|
20
20
|
type: "module",
|
|
21
21
|
main: "dist/cli/index.js",
|
|
@@ -35,6 +35,7 @@ var init_package = __esm({
|
|
|
35
35
|
start: "tsx src/cli/index.ts",
|
|
36
36
|
dev: "tsx src/cli/index.ts",
|
|
37
37
|
build: "tsup",
|
|
38
|
+
test: "node --import tsx --test tests/unit/*.test.ts",
|
|
38
39
|
typecheck: "tsc --noEmit",
|
|
39
40
|
format: 'prettier --write "src/**/*.ts"',
|
|
40
41
|
prepublishOnly: "npm run build",
|
|
@@ -75,10 +76,8 @@ var init_package = __esm({
|
|
|
75
76
|
},
|
|
76
77
|
dependencies: {
|
|
77
78
|
chalk: "^5.3.0",
|
|
78
|
-
ora: "^8.0.0",
|
|
79
79
|
prettier: "^3.2.0",
|
|
80
|
-
puppeteer: "^22.0.0"
|
|
81
|
-
"readline-promise": "^1.0.5"
|
|
80
|
+
puppeteer: "^22.0.0"
|
|
82
81
|
},
|
|
83
82
|
devDependencies: {
|
|
84
83
|
"@types/node": "^22.0.0",
|
|
@@ -108,9 +107,6 @@ function softGradient(text) {
|
|
|
108
107
|
function chip(label) {
|
|
109
108
|
return `${ui.border("[")}${ui.primary(label)}${ui.border("]")}`;
|
|
110
109
|
}
|
|
111
|
-
function bullet(label = "\u2022") {
|
|
112
|
-
return ui.primary(label);
|
|
113
|
-
}
|
|
114
110
|
function centerText(text, width) {
|
|
115
111
|
const visible = stripAnsi(text).length;
|
|
116
112
|
if (visible >= width) return text;
|
|
@@ -239,10 +235,27 @@ var init_framer = __esm({
|
|
|
239
235
|
],
|
|
240
236
|
stripPatterns: [
|
|
241
237
|
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
242
|
-
/<script>try\{if\(localStorage\.
|
|
238
|
+
/<script>try\{if\(localStorage\.getItem\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
243
239
|
],
|
|
244
240
|
hydrationTimeout: 1e4,
|
|
245
241
|
needsHydrationCheck: true,
|
|
242
|
+
skipAssetUrls: [
|
|
243
|
+
/^https:\/\/(?:www\.)?framer\.com\/edit(?:[/?]|$)/,
|
|
244
|
+
/^https:\/\/(?:[^/]+\.)*(?:framer\.com|framerstatic\.com|framerusercontent\.com|framercanvas\.com)\/[^?]*\/init\.mjs(?:\?|$)/,
|
|
245
|
+
/^https:\/\/(?:[^/]+\.)*(?:framer\.com|framerstatic\.com|framerusercontent\.com|framercanvas\.com)\/[^?]*\/editorbar\.[^/]*\.mjs(?:\?|$)/,
|
|
246
|
+
/^https:\/\/(?:[^/]+\.)*(?:framer\.com|framerstatic\.com|framerusercontent\.com|framercanvas\.com)\/[^?]*\/EditButton-[^/]*\.mjs(?:\?|$)/
|
|
247
|
+
],
|
|
248
|
+
lazyChunkDirs: ["scripts/vendor", "scripts/modules"],
|
|
249
|
+
rewritePatterns: [
|
|
250
|
+
{
|
|
251
|
+
from: /https:\/\/(?:www\.)?framer\.com\/edit\/init\.mjs/g,
|
|
252
|
+
to: "data:text/javascript,export%20const%20createEditorBar=()=>()=>null"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
from: /\(function\(\)\{[\w$]+&&[\w$]+\(\(\)=>\{[\s\S]{0,200}?__framer-badge-container[\s\S]{0,300}?\}\)\}\)\(\)/g,
|
|
256
|
+
to: ""
|
|
257
|
+
}
|
|
258
|
+
],
|
|
246
259
|
mapAssetDir(host, pathname, ext) {
|
|
247
260
|
if (host.includes("framerusercontent.com")) {
|
|
248
261
|
if (pathname.startsWith("/images/")) return "assets/images";
|
|
@@ -363,9 +376,7 @@ var init_wix = __esm({
|
|
|
363
376
|
"panorama.wixapps.net"
|
|
364
377
|
],
|
|
365
378
|
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
366
|
-
stripPatterns: [
|
|
367
|
-
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
368
|
-
],
|
|
379
|
+
stripPatterns: [/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g],
|
|
369
380
|
hydrationTimeout: 3e3,
|
|
370
381
|
needsHydrationCheck: false,
|
|
371
382
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -412,17 +423,9 @@ var init_bubble = __esm({
|
|
|
412
423
|
detectByHtml(html) {
|
|
413
424
|
return html.includes("window.appquery = make_proxy(") || html.includes("window._bubble_page_load_data") || html.includes("window.bubble_session_uid") || html.includes("/package/pre_run_jquery_js/");
|
|
414
425
|
},
|
|
415
|
-
// Telemetry only (Plausible-based, on standard-plan apps). NEVER the asset CDN.
|
|
416
426
|
stripDomains: ["pluginpul.se"],
|
|
417
|
-
// Post-process removal of any telemetry <script> that survived into the DOM.
|
|
418
427
|
stripSelectors: ['script[src*="pluginpul.se"]'],
|
|
419
428
|
stripPatterns: [
|
|
420
|
-
// "Made with Bubble" badge — injected at runtime by run.js and gated by
|
|
421
|
-
// window._p.no_branding, so it never appears in the raw SSR HTML (only in
|
|
422
|
-
// the captured rendered DOM). Best-effort strip anchored on its bubble.io
|
|
423
|
-
// target: matches an <a> pointing at the bubble.io ROOT (optionally with a
|
|
424
|
-
// tracking query), which is tight enough to spare normal deep-links to
|
|
425
|
-
// bubble.io content pages.
|
|
426
429
|
/<a\b[^>]*href="https?:\/\/(?:www\.)?bubble\.io\/?(?:\?[^"]*)?"[^>]*>[\s\S]*?<\/a>/gi
|
|
427
430
|
],
|
|
428
431
|
needsHydrationCheck: true,
|
|
@@ -476,34 +479,14 @@ var init_carrd = __esm({
|
|
|
476
479
|
return /\.carrd\.co/i.test(url);
|
|
477
480
|
},
|
|
478
481
|
detectByHtml(html) {
|
|
479
|
-
return (
|
|
480
|
-
// Hallmark inline runtime IIFE, present on every Carrd site (new and old).
|
|
481
|
-
html.includes("$inner = $('.inner')") || // Free-plan "( Made with Carrd )" badge anchor.
|
|
482
|
-
html.includes('href="https://carrd.co"') || // Carrd's component-class scheme (…-component). Webflow uses w-* prefixes and
|
|
483
|
-
// Wix uses random ids, so this family does not collide with the frozen trio.
|
|
484
|
-
/class="[^"]*\b(?:text|image|container|buttons|icons|divider|links)-component\b/.test(html) || // Current-gen structural nesting: .site-wrapper > .site-main (both required, so
|
|
485
|
-
// neither generic-sounding class triggers detection on its own).
|
|
486
|
-
html.includes('class="site-wrapper"') && html.includes('class="site-main"')
|
|
487
|
-
);
|
|
482
|
+
return html.includes("$inner = $('.inner')") || html.includes('href="https://carrd.co"') || /class="[^"]*\b(?:text|image|container|buttons|icons|divider|links)-component\b/.test(html) || html.includes('class="site-wrapper"') && html.includes('class="site-main"');
|
|
488
483
|
},
|
|
489
|
-
// No third-party analytics/telemetry is injected by default (profile
|
|
490
|
-
// analyticsScripts is empty), and fonts.googleapis.com is a needed asset host,
|
|
491
|
-
// so nothing is blocked during capture.
|
|
492
484
|
stripDomains: [],
|
|
493
|
-
// The badge id sits directly on the <p> (there is no wrapper div and no
|
|
494
|
-
// class="credits"); removing #credits drops the whole "Made with Carrd" line.
|
|
495
485
|
stripSelectors: ["#credits"],
|
|
496
486
|
stripPatterns: [
|
|
497
|
-
// Free-plan badge paragraph: <p id="credits"><a href="https://carrd.co">…</a></p>
|
|
498
487
|
/<p[^>]*id="credits"[^>]*>[\s\S]*?<\/p>/gi,
|
|
499
|
-
// Belt-and-suspenders: the branded badge anchor itself (anchored on the exact
|
|
500
|
-
// "( Made with Carrd )" text so a genuine user link to carrd.co is never removed).
|
|
501
488
|
/<a[^>]*href="https:\/\/carrd\.co"[^>]*>\s*\(\s*Made with Carrd\s*\)\s*<\/a>/gi
|
|
502
489
|
],
|
|
503
|
-
// ssr-static: the raw SSR HTML is complete, so no hydration wait is required
|
|
504
|
-
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is Cloudflare
|
|
505
|
-
// but the pages fetched cleanly in research; per the authoring guide we keep a
|
|
506
|
-
// small capture buffer instead of the literal 0 ms.
|
|
507
490
|
hydrationTimeout: 2e3,
|
|
508
491
|
needsHydrationCheck: false,
|
|
509
492
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -514,7 +497,8 @@ var init_carrd = __esm({
|
|
|
514
497
|
if (VIDEO_EXTS4.includes(ext)) return "assets/videos";
|
|
515
498
|
if (pathname.includes("/assets/images/") || IMG_EXTS4.includes(ext)) return "assets/images";
|
|
516
499
|
if (pathname.includes("/assets/css/") || ext === ".css") return "styles";
|
|
517
|
-
if (pathname.includes("/assets/js/") || ext === ".js" || ext === ".mjs")
|
|
500
|
+
if (pathname.includes("/assets/js/") || ext === ".js" || ext === ".mjs")
|
|
501
|
+
return "scripts/vendor";
|
|
518
502
|
if (FONT_EXTS5.includes(ext)) return "assets/fonts";
|
|
519
503
|
return "assets/misc";
|
|
520
504
|
}
|
|
@@ -541,18 +525,11 @@ var init_duda = __esm({
|
|
|
541
525
|
return /\.(?:multiscreensite|dudaone)\.com/i.test(url);
|
|
542
526
|
},
|
|
543
527
|
detectByHtml(html) {
|
|
544
|
-
return html.includes("atob('RFVEQU9ORQ==')") ||
|
|
545
|
-
html.includes('id="dmRoot"') || html.includes("DM_DIRECT") || // window.Parameters productId
|
|
546
|
-
html.includes("d_track_campaign");
|
|
528
|
+
return html.includes("atob('RFVEQU9ORQ==')") || html.includes('id="dmRoot"') || html.includes("DM_DIRECT") || html.includes("d_track_campaign");
|
|
547
529
|
},
|
|
548
|
-
// No external telemetry/analytics hosts on Duda (tracking is inline, first-party).
|
|
549
530
|
stripDomains: [],
|
|
550
|
-
// Inline first-party tracking <script id="..."> elements (removed by id).
|
|
551
531
|
stripSelectors: ["#d_track_campaign", "#d_track_personalization", "#d_track_sp"],
|
|
552
|
-
stripPatterns: [
|
|
553
|
-
// "Made with Duda" badge → https://www.duda.co/inspiration
|
|
554
|
-
/<a[^>]*href="[^"]*duda\.co\/inspiration[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
555
|
-
],
|
|
532
|
+
stripPatterns: [/<a[^>]*href="[^"]*duda\.co\/inspiration[^"]*"[^>]*>[\s\S]*?<\/a>/g],
|
|
556
533
|
hydrationTimeout: 0,
|
|
557
534
|
needsHydrationCheck: false,
|
|
558
535
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -591,9 +568,7 @@ var init_squarespace = __esm({
|
|
|
591
568
|
detectByHtml(html) {
|
|
592
569
|
return html.includes("<!-- This is Squarespace. -->") || html.includes("Static.SQUARESPACE_CONTEXT") || html.includes("<!-- End of Squarespace Headers -->") || html.includes("images.squarespace-cdn.com");
|
|
593
570
|
},
|
|
594
|
-
// First-party analytics only; no default third-party telemetry host to block.
|
|
595
571
|
stripDomains: [],
|
|
596
|
-
// No editor chrome / badge in public SSR HTML on paid or template sites.
|
|
597
572
|
stripSelectors: [],
|
|
598
573
|
stripPatterns: [],
|
|
599
574
|
hydrationTimeout: 3e3,
|
|
@@ -656,21 +631,14 @@ var init_strikingly = __esm({
|
|
|
656
631
|
detectByHtml(html) {
|
|
657
632
|
return html.includes("strikingly-host-suffix") || html.includes('data-hypernova-key="SiteBootstrapper"') || html.includes('id="s-page-client-container"') || html.includes("strikinglycdn.com");
|
|
658
633
|
},
|
|
659
|
-
// Telemetry / error-monitoring only — NEVER the strikinglycdn asset CDN.
|
|
660
634
|
stripDomains: [
|
|
661
635
|
"google-analytics.com",
|
|
662
636
|
"www.google-analytics.com",
|
|
663
637
|
"notify.bugsnag.com",
|
|
664
638
|
"sessions.bugsnag.com"
|
|
665
639
|
],
|
|
666
|
-
// The "Powered by Strikingly" footer section carries the unique class
|
|
667
|
-
// `.show-strikingly-logo` (inside #s-footer-section-container).
|
|
668
640
|
stripSelectors: [".show-strikingly-logo"],
|
|
669
|
-
stripPatterns: [
|
|
670
|
-
// Hydration-rendered badge anchor → https://www.strikingly.com/?ref=logo…&utm_campaign=footer_pbs
|
|
671
|
-
/<a[^>]*href="[^"]*strikingly\.com\/\?ref=logo[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
672
|
-
],
|
|
673
|
-
// renderStrategy: react-hydration → use the rendered DOM as the HTML source.
|
|
641
|
+
stripPatterns: [/<a[^>]*href="[^"]*strikingly\.com\/\?ref=logo[^"]*"[^>]*>[\s\S]*?<\/a>/gi],
|
|
674
642
|
hydrationTimeout: 3e3,
|
|
675
643
|
needsHydrationCheck: false,
|
|
676
644
|
hydrationSelector: "#s-page-client-container",
|
|
@@ -727,15 +695,9 @@ var init_tilda = __esm({
|
|
|
727
695
|
detectByHtml(html) {
|
|
728
696
|
return html.includes("data-tilda-project-id") || html.includes("data-tilda-page-id") || html.includes("tildacdn") || html.includes('id="allrecords"');
|
|
729
697
|
},
|
|
730
|
-
// Third-party telemetry only. NEVER the tildacdn asset hosts (they carry the
|
|
731
|
-
// page's real images/CSS/JS); the Tilda stat script is stripped by selector.
|
|
732
698
|
stripDomains: ["www.google-analytics.com", "google-analytics.com"],
|
|
733
699
|
stripSelectors: ["#tildacopy", ".t-tildalabel", 'script[src*="tilda-stat"]'],
|
|
734
|
-
stripPatterns: [
|
|
735
|
-
// "Made on Tilda" badge link (free-plan only). No nested <a>, so the
|
|
736
|
-
// non-greedy </a> match is safe even though the anchor wraps inner <div>s.
|
|
737
|
-
/<a[^>]*href="[^"]*tilda\.cc[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
738
|
-
],
|
|
700
|
+
stripPatterns: [/<a[^>]*href="[^"]*tilda\.cc[^"]*"[^>]*>[\s\S]*?<\/a>/g],
|
|
739
701
|
hydrationTimeout: 0,
|
|
740
702
|
needsHydrationCheck: false,
|
|
741
703
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -776,15 +738,8 @@ var init_weebly = __esm({
|
|
|
776
738
|
detectByHtml(html) {
|
|
777
739
|
return html.includes("Weebly.footer.setupContainer") || html.includes("weebly-footer-signup-container-v3") || html.includes("wsite-content") || html.includes("window._W");
|
|
778
740
|
},
|
|
779
|
-
// Third-party telemetry only. NEVER list editmysite.com here — that host serves
|
|
780
|
-
// the platform CSS/JS/images (cdn1/cdn2/cdn11) and blocking it deletes assets.
|
|
781
|
-
// The Weebly _W.Analytics trackers (main.js, stl.js) also live on that CDN, so
|
|
782
|
-
// they are intentionally not blocked (blocking the host would kill assets too).
|
|
783
741
|
stripDomains: ["google-analytics.com"],
|
|
784
|
-
// Footer "Made with Weebly" signup badge: unique container id (empty in SSR
|
|
785
|
-
// HTML, populated client-side) plus the script that injects it.
|
|
786
742
|
stripSelectors: ["#weebly-footer-signup-container-v3", 'script[src*="footerSignup.js"]'],
|
|
787
|
-
// Best-effort removal of the rendered badge anchor should it appear inline.
|
|
788
743
|
stripPatterns: [/<a[^>]*href="[^"]*weebly\.com\/signup[^"]*"[^>]*>[\s\S]*?<\/a>/g],
|
|
789
744
|
hydrationTimeout: 0,
|
|
790
745
|
needsHydrationCheck: false,
|
|
@@ -822,21 +777,9 @@ var init_clickfunnels = __esm({
|
|
|
822
777
|
detectByHtml(html) {
|
|
823
778
|
return html.includes("cf-head-scripts") || html.includes("window.cfRootDomain") || html.includes("cf-lander-serialized-custom-fonts") || html.includes("data-page-element");
|
|
824
779
|
},
|
|
825
|
-
// Telemetry / analytics hosts blocked during capture. NOT the asset CDNs
|
|
826
|
-
// (statics.myclickfunnels.com / images.clickfunnels.com) and NOT the functional
|
|
827
|
-
// third-party libs (cdnjs / code.jquery.com / fontawesome) or Turnstile.
|
|
828
780
|
stripDomains: ["bam.nr-data.net", "js-agent.newrelic.com", "events.myclickfunnels.com"],
|
|
829
|
-
// No CF-specific badge/editor container has a static id/class in the delivered
|
|
830
|
-
// HTML, so there is nothing selector-strippable here.
|
|
831
781
|
stripSelectors: [],
|
|
832
|
-
// BADGE: The "Powered by ClickFunnels" badge is default-on per docs but was
|
|
833
|
-
// toggled OFF/HIDDEN on both verified samples (0 occurrences in the SSR HTML),
|
|
834
|
-
// and this handler does not capture the rendered DOM. No reliable static markup
|
|
835
|
-
// exists to anchor a regex on, so no badge stripPattern is emitted (a loose match
|
|
836
|
-
// on clickfunnels.com would risk removing legitimate links). If a future sample
|
|
837
|
-
// exposes the badge anchor, add a tight /<a[^>]*href="[^"]*clickfunnels\.com[^"]*"...>/ here.
|
|
838
782
|
stripPatterns: [],
|
|
839
|
-
// ssr-static: SSR body is complete, no hydration wait needed.
|
|
840
783
|
hydrationTimeout: 0,
|
|
841
784
|
needsHydrationCheck: false,
|
|
842
785
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -873,30 +816,15 @@ var init_elementor = __esm({
|
|
|
873
816
|
displayName: "Elementor",
|
|
874
817
|
category: "landing",
|
|
875
818
|
priority: 42,
|
|
876
|
-
// Only Elementor Cloud/Hosting uses a platform subdomain; self-hosted sites live
|
|
877
|
-
// on the owner's own domain and are caught by detectByHtml / detectGenerator.
|
|
878
819
|
detectByUrl(url) {
|
|
879
820
|
return /\.elementor\.cloud/i.test(url);
|
|
880
821
|
},
|
|
881
|
-
// OR of four independent, Elementor-specific markers (none match Framer/Webflow/Wix,
|
|
882
|
-
// and #2/#3 survive optimization plugins that strip the generator meta):
|
|
883
|
-
// - Elementor plugin asset directory
|
|
884
|
-
// - Elementor "kit" body class
|
|
885
|
-
// - root wrapper data-attribute
|
|
886
|
-
// - frontend bootstrap JS global
|
|
887
822
|
detectByHtml(html) {
|
|
888
823
|
return html.includes("/wp-content/plugins/elementor/") || html.includes("elementor-kit-") || html.includes("data-elementor-type=") || html.includes("elementorFrontendConfig");
|
|
889
824
|
},
|
|
890
|
-
// Matched against the <meta name="generator"> content only, e.g.
|
|
891
|
-
// "Elementor 3.31.3; features: ...". Anchored so it fires solely on Elementor's own
|
|
892
|
-
// generator string, never on the "WordPress 6.7.5" / "Site Kit by Google" siblings.
|
|
893
825
|
detectGenerator: /^Elementor\s+\d+\.\d+/i,
|
|
894
|
-
// No default third-party telemetry: googletagmanager / cdn.usefathom.com hosts seen
|
|
895
|
-
// on sample sites are site-owner additions, not an Elementor platform default.
|
|
896
826
|
stripDomains: [],
|
|
897
|
-
// No front-end editor chrome or badge element to remove.
|
|
898
827
|
stripSelectors: [],
|
|
899
|
-
// Verified: no "Made with Elementor" badge in the front-end DOM → nothing to strip.
|
|
900
828
|
stripPatterns: [],
|
|
901
829
|
hydrationTimeout: 0,
|
|
902
830
|
needsHydrationCheck: false,
|
|
@@ -935,8 +863,6 @@ var init_instapage = __esm({
|
|
|
935
863
|
detectByHtml(html) {
|
|
936
864
|
return html.includes("instapageSp") || html.includes("_instapageSnowplow") || html.includes("anthill.instapage.com") || html.includes("pageserver.page2");
|
|
937
865
|
},
|
|
938
|
-
// Telemetry / analytics hosts only — blocked during capture. The fastcdn.co
|
|
939
|
-
// asset CDN is deliberately NOT listed here (those assets are mirrored).
|
|
940
866
|
stripDomains: [
|
|
941
867
|
"anthill.instapage.com",
|
|
942
868
|
"cdn.instapagemetrics.com",
|
|
@@ -948,11 +874,7 @@ var init_instapage = __esm({
|
|
|
948
874
|
'script[src*="anthill.instapage.com"]',
|
|
949
875
|
'script[src*="heatmap.services"]'
|
|
950
876
|
],
|
|
951
|
-
stripPatterns: [
|
|
952
|
-
// Hidden local-storage helper iframe (runtime-injected on live pages; stripped
|
|
953
|
-
// here best-effort in case it appears in the captured markup).
|
|
954
|
-
/<iframe[^>]*d3mwhxgzltpnyp\.cloudfront\.net[^>]*>[\s\S]*?<\/iframe>/g
|
|
955
|
-
],
|
|
877
|
+
stripPatterns: [/<iframe[^>]*d3mwhxgzltpnyp\.cloudfront\.net[^>]*>[\s\S]*?<\/iframe>/g],
|
|
956
878
|
hydrationTimeout: 0,
|
|
957
879
|
needsHydrationCheck: false,
|
|
958
880
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -997,14 +919,10 @@ var init_systemeio = __esm({
|
|
|
997
919
|
detectByHtml(html) {
|
|
998
920
|
return html.includes("<!-- Created with https://systeme.io -->") || html.includes("d3fit27i5nzkqh.cloudfront.net") || html.includes("systeme.io/?sa=") || html.includes("d3syewzhvzylbl.cloudfront.net");
|
|
999
921
|
},
|
|
1000
|
-
// First-party click/funnel telemetry — blocked during capture, never downloaded.
|
|
1001
922
|
stripDomains: ["log.systeme.io"],
|
|
1002
923
|
stripSelectors: ['script[src*="log.systeme.io"]'],
|
|
1003
924
|
stripPatterns: [
|
|
1004
|
-
// Footer credit/affiliate badge: <a href="https://systeme.io/?sa=<code>" ...>SITE CREDIT</a>.
|
|
1005
|
-
// Match on the `systeme.io/?sa=` href affiliate param (visible label is owner-editable).
|
|
1006
925
|
/<a[^>]*href="[^"]*systeme\.io\/\?sa=[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1007
|
-
// Branding comment injected right after <!DOCTYPE html>.
|
|
1008
926
|
/<!--\s*Created with https:\/\/systeme\.io\s*-->/g
|
|
1009
927
|
],
|
|
1010
928
|
hydrationTimeout: 0,
|
|
@@ -1066,18 +984,9 @@ var init_unbounce = __esm({
|
|
|
1066
984
|
detectByHtml(html) {
|
|
1067
985
|
return html.includes("lp-pom-root") || html.includes("window.ub.page") || html.includes("builder-assets.unbounce.com") || html.includes("ubembed.com");
|
|
1068
986
|
},
|
|
1069
|
-
// No first-party telemetry/analytics hosts to block (profile analyticsScripts: []).
|
|
1070
|
-
// The asset CDNs (builder-assets / cloudfront / ubembed) must NOT be listed here.
|
|
1071
987
|
stripDomains: [],
|
|
1072
|
-
// The badge selector (#lp-pom-root a[href*="unbounce.com"]) is a descendant
|
|
1073
|
-
// combinator, unsupported by stripSelectors — handled via stripPatterns below.
|
|
1074
988
|
stripSelectors: [],
|
|
1075
|
-
stripPatterns: [
|
|
1076
|
-
// Best-effort removal of the "Made with Unbounce" branded footer link.
|
|
1077
|
-
// Anchored on the exact brand host so it never matches *.unbouncepages.com /
|
|
1078
|
-
// *.ubpages.com hosting links. Markup is approximate (badge markup UNVERIFIED).
|
|
1079
|
-
/<a[^>]*href="https?:\/\/(?:www\.)?unbounce\.com[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
1080
|
-
],
|
|
989
|
+
stripPatterns: [/<a[^>]*href="https?:\/\/(?:www\.)?unbounce\.com[^"]*"[^>]*>[\s\S]*?<\/a>/gi],
|
|
1081
990
|
hydrationTimeout: 0,
|
|
1082
991
|
needsHydrationCheck: false,
|
|
1083
992
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -1131,25 +1040,12 @@ var init_ghost = __esm({
|
|
|
1131
1040
|
detectByHtml(html) {
|
|
1132
1041
|
return html.includes("/assets/built/screen.css") || html.includes("/public/cards.min.css") || html.includes("cdn.jsdelivr.net/ghost/") || html.includes("gh-powered-by");
|
|
1133
1042
|
},
|
|
1134
|
-
// Matched against <meta name="generator"> content only. Version drifts per site
|
|
1135
|
-
// (observed 6.51 and 6.39), so match the pattern rather than a literal version.
|
|
1136
1043
|
detectGenerator: /Ghost [0-9.]+/i,
|
|
1137
|
-
// First-party ghost-stats is the only analytics; it POSTs to the site's own
|
|
1138
|
-
// origin, so there is no third-party telemetry host to block during capture.
|
|
1139
1044
|
stripDomains: [],
|
|
1140
|
-
// The "Powered by Ghost" badge's unique class sits on the WRAPPER <div>, whose
|
|
1141
|
-
// only child is the <a>. The post-processor's naive `.class` stripper is
|
|
1142
|
-
// non-greedy to the first close tag, so `.gh-powered-by` would delete only up to
|
|
1143
|
-
// the inner </a> and orphan a dangling </div>. Since selectors run before
|
|
1144
|
-
// patterns, that orphan cannot be cleaned up afterwards. The badge is therefore
|
|
1145
|
-
// removed cleanly (both tags) by the wrapper-div stripPattern below instead.
|
|
1146
1045
|
stripSelectors: [],
|
|
1147
1046
|
stripPatterns: [
|
|
1148
|
-
// <div class="gh-powered-by"><a href="https://ghost.org/" ...>Powered by Ghost</a></div>
|
|
1149
1047
|
/<div[^>]*class="[^"]*gh-powered-by[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1150
|
-
// Bare <a href="…ghost.org…">Powered by Ghost</a> fallback if the wrapper drops.
|
|
1151
1048
|
/<a[^>]*href="[^"]*ghost\.org[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1152
|
-
// Identifying <meta name="generator" content="Ghost X.Y">.
|
|
1153
1049
|
/<meta[^>]*name="generator"[^>]*content="Ghost[^"]*"[^>]*>/gi
|
|
1154
1050
|
],
|
|
1155
1051
|
hydrationTimeout: 0,
|
|
@@ -1196,18 +1092,9 @@ var init_notion = __esm({
|
|
|
1196
1092
|
detectByHtml(html) {
|
|
1197
1093
|
return html.includes('data-notion-html="web"') || html.includes('id="notion-app"') || html.includes("__notion_html_async") || html.includes("window.__notion_boot_data");
|
|
1198
1094
|
},
|
|
1199
|
-
// Notion's own Splunk telemetry collector — blocked during capture. NEVER add
|
|
1200
|
-
// the asset hosts (www.notion.so / *.amazonaws.com) here; they serve real files.
|
|
1201
1095
|
stripDomains: ["http-inputs-notion.splunkcloud.com"],
|
|
1202
|
-
// Pre-hydration loading chrome. captureRenderedDom normally replaces these once
|
|
1203
|
-
// #notion-app hydrates; stripping them is a defensive no-content cleanup in case
|
|
1204
|
-
// hydration is partial.
|
|
1205
1096
|
stripSelectors: ["#initial-loading-spinner", "#skeleton", "#skeleton-sidebar"],
|
|
1206
1097
|
stripPatterns: [
|
|
1207
|
-
// "Built with Notion" watermark is React-injected post-hydration (ABSENT from
|
|
1208
|
-
// raw HTML — selector/href unverified). Best-effort removal from the captured
|
|
1209
|
-
// rendered DOM: an <a> pointing at notion.so/notion.com that also carries the
|
|
1210
|
-
// badge's signature text. Both conditions keep this from matching real content.
|
|
1211
1098
|
/<a[^>]*href="[^"]*notion\.(so|com)[^"]*"[^>]*>[\s\S]*?Built with Notion[\s\S]*?<\/a>/gi
|
|
1212
1099
|
],
|
|
1213
1100
|
hydrationTimeout: 8e3,
|
|
@@ -1262,15 +1149,9 @@ var init_wordpress = __esm({
|
|
|
1262
1149
|
detectByHtml(html) {
|
|
1263
1150
|
return html.includes("/wp-includes/") || html.includes("/wp-content/") || html.includes("api.w.org") || html.includes("_wpemojiSettings");
|
|
1264
1151
|
},
|
|
1265
|
-
// Matched against the <meta name="generator"> content only. Covers both
|
|
1266
|
-
// "WordPress <version>" (self-hosted) and exact "WordPress.com" (hosted).
|
|
1267
1152
|
detectGenerator: /WordPress/i,
|
|
1268
|
-
// Jetpack / WP.com Stats trackers only — never the asset CDN.
|
|
1269
1153
|
stripDomains: ["stats.wp.com", "pixel.wp.com"],
|
|
1270
|
-
// Remove the leftover tracker <script> tags after their requests are blocked.
|
|
1271
1154
|
stripSelectors: ['script[src*="stats.wp.com"]', 'script[src*="pixel.wp.com"]'],
|
|
1272
|
-
// No badge to strip; drop the generator meta as an attribution cleanup
|
|
1273
|
-
// (shape mirrors webflow.ts). Matches "WordPress 6.9.4" and "WordPress.com".
|
|
1274
1155
|
stripPatterns: [
|
|
1275
1156
|
/<meta[^>]*name=["']generator["'][^>]*content=["'][^"']*WordPress[^"']*["'][^>]*>/gi
|
|
1276
1157
|
],
|
|
@@ -1318,37 +1199,17 @@ var init_kajabi = __esm({
|
|
|
1318
1199
|
detectByUrl(url) {
|
|
1319
1200
|
return /\.mykajabi\.com/i.test(url);
|
|
1320
1201
|
},
|
|
1321
|
-
// Four Kajabi-only, tier-independent tells. Badge markers are deliberately NOT
|
|
1322
|
-
// used here (the "Powered by Kajabi" badge is absent on paid plans). None of
|
|
1323
|
-
// these strings appear on Framer / Webflow / Wix output.
|
|
1324
1202
|
detectByHtml(html) {
|
|
1325
|
-
return html.includes("var Kajabi = Kajabi") ||
|
|
1326
|
-
html.includes("Kajabi.currentSiteUser") || // inline theme bootstrap global
|
|
1327
|
-
html.includes("kjb-settings-id=") || // Kajabi theme-editor data attribute
|
|
1328
|
-
html.includes("kajabi-cdn.com");
|
|
1203
|
+
return html.includes("var Kajabi = Kajabi") || html.includes("Kajabi.currentSiteUser") || html.includes("kjb-settings-id=") || html.includes("kajabi-cdn.com");
|
|
1329
1204
|
},
|
|
1330
|
-
// Telemetry / analytics hosts only — NEVER the kajabi-cdn.com asset hosts.
|
|
1331
1205
|
stripDomains: ["rs-dp.kajabi.com", "cdn.rudderlabs.com"],
|
|
1332
|
-
// The "Powered by Kajabi" badge wrapper carries a unique class — remove it via
|
|
1333
|
-
// selector (first-match). stripPatterns below covers repeats / rendered markup.
|
|
1334
1206
|
stripSelectors: [".footer__powered-by"],
|
|
1335
1207
|
stripPatterns: [
|
|
1336
|
-
// Whole badge wrapper: <div class="footer__powered-by"> … </div>
|
|
1337
|
-
// (real markup has indentation/newlines between the div and the anchor).
|
|
1338
1208
|
/<div[^>]*class="[^"]*footer__powered-by[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1339
|
-
// Belt-and-suspenders: the branded referral anchor itself.
|
|
1340
1209
|
/<a[^>]*href="[^"]*app\.kajabi\.com\/r\/[^"]*powered_by=true[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1341
1210
|
],
|
|
1342
|
-
// ssr-static: the raw SSR HTML is complete, so no hydration wait is needed
|
|
1343
|
-
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is
|
|
1344
|
-
// Cloudflare but the pages fetched cleanly in research; per the authoring
|
|
1345
|
-
// guide we keep a small capture buffer instead of the literal 0 ms.
|
|
1346
1211
|
hydrationTimeout: 2e3,
|
|
1347
1212
|
needsHydrationCheck: false,
|
|
1348
|
-
// Both cdnDomains live under *.kajabi-cdn.com
|
|
1349
|
-
// - kajabi-storefronts-production.kajabi-cdn.com
|
|
1350
|
-
// - kajabi-app-assets.kajabi-cdn.com
|
|
1351
|
-
// so a single host match covers every declared CDN host; classify by ext/path.
|
|
1352
1213
|
mapAssetDir(host, pathname, ext) {
|
|
1353
1214
|
if (host.includes("kajabi-cdn.com")) {
|
|
1354
1215
|
if (VIDEO_EXTS18.includes(ext)) return "assets/videos";
|
|
@@ -1382,28 +1243,15 @@ var init_podia = __esm({
|
|
|
1382
1243
|
return /\.podia\.com/i.test(url);
|
|
1383
1244
|
},
|
|
1384
1245
|
detectByHtml(html) {
|
|
1385
|
-
return (
|
|
1386
|
-
// Constant Turnstile site-key present on every Podia storefront (incl. custom domains).
|
|
1387
|
-
html.includes("0x4AAAAAAAJ5kwYW6AH1ybLx") || // Shared framework asset host (storefront-<hash>.css/.js, user-site-<hash>.js).
|
|
1388
|
-
html.includes("cdn.podia.com") || // React section-adapter islands unique to Podia's creator UI.
|
|
1389
|
-
html.includes('data-react-component="creator_ui/section_adapters/') || // Stimulus controller wiring the editable page sections.
|
|
1390
|
-
html.includes('data-controller="editor--page-section"')
|
|
1391
|
-
);
|
|
1246
|
+
return html.includes("0x4AAAAAAAJ5kwYW6AH1ybLx") || html.includes("cdn.podia.com") || html.includes('data-react-component="creator_ui/section_adapters/') || html.includes('data-controller="editor--page-section"');
|
|
1392
1247
|
},
|
|
1393
|
-
// Telemetry only — NEVER the asset CDN. Podia's default analytics is Google Tag Manager.
|
|
1394
1248
|
stripDomains: ["www.googletagmanager.com"],
|
|
1395
1249
|
stripSelectors: ['script[src*="googletagmanager.com"]'],
|
|
1396
1250
|
stripPatterns: [
|
|
1397
|
-
// "Powered by Podia" footer badge — React-rendered anchor in the captured DOM.
|
|
1398
|
-
// Its href is https://www.podia.com?utm_medium=poweredby&utm_source=footer
|
|
1399
|
-
// (the "&" may serialize as "&"). Anchor on the stable brand host + the
|
|
1400
|
-
// badge-unique utm_medium=poweredby token; tolerate single/double quotes and any
|
|
1401
|
-
// chars between host and token (e.g. a "/" before the query string).
|
|
1402
1251
|
/<a[^>]*href=['"][^'"]*podia\.com[^'"]*utm_medium=poweredby[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1403
1252
|
],
|
|
1404
1253
|
hydrationTimeout: 4e3,
|
|
1405
1254
|
needsHydrationCheck: true,
|
|
1406
|
-
// Content lives in these React islands rather than a #main root.
|
|
1407
1255
|
hydrationSelector: ".react-page-section",
|
|
1408
1256
|
captureRenderedDom: true,
|
|
1409
1257
|
mapAssetDir(host, pathname, ext) {
|
|
@@ -1452,17 +1300,9 @@ var init_teachable = __esm({
|
|
|
1452
1300
|
"www.recaptcha.net",
|
|
1453
1301
|
"fast.wistia.com"
|
|
1454
1302
|
],
|
|
1455
|
-
stripSelectors: [
|
|
1456
|
-
// Analytics <script src> tags left in the HTML (hosts also blocked at capture
|
|
1457
|
-
// via stripDomains). The powered-by badge is handled by stripPatterns instead
|
|
1458
|
-
// — a `.class` selector can't match its single-quoted, multi-child anchor.
|
|
1459
|
-
'script[src*="heapanalytics.com"]',
|
|
1460
|
-
'script[src*="nr-data.net"]'
|
|
1461
|
-
],
|
|
1303
|
+
stripSelectors: ['script[src*="heapanalytics.com"]', 'script[src*="nr-data.net"]'],
|
|
1462
1304
|
stripPatterns: [
|
|
1463
|
-
// Footer "Teach Online with" badge — anchored on its unique powered-by class.
|
|
1464
1305
|
/<a[^>]*class=['"][^'"]*powered-by[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi,
|
|
1465
|
-
// Fallback: anchored on the teachable.com ?src=school_footer badge href.
|
|
1466
1306
|
/<a[^>]*href=['"][^'"]*teachable\.com[^'"]*src=school_footer[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1467
1307
|
],
|
|
1468
1308
|
hydrationTimeout: 0,
|
|
@@ -1501,49 +1341,17 @@ var init_thinkific = __esm({
|
|
|
1501
1341
|
detectByUrl(url) {
|
|
1502
1342
|
return /\.thinkific\.com/i.test(url);
|
|
1503
1343
|
},
|
|
1504
|
-
// Four Thinkific-only tells that hold on both subdomain and custom-domain
|
|
1505
|
-
// sites. The "Powered by Thinkific" badge is deliberately NOT used here (it is
|
|
1506
|
-
// removable on paid plans). None of these strings appear on Framer / Webflow /
|
|
1507
|
-
// Wix output.
|
|
1508
1344
|
detectByHtml(html) {
|
|
1509
|
-
return html.includes("window.Thinkific") ||
|
|
1510
|
-
html.includes("assets.thinkific.com") || // theme CSS/JS bundle host
|
|
1511
|
-
html.includes("cdn.thinkific.com") || // toga-css icon-font host
|
|
1512
|
-
html.includes("thnc.current_user-initialized");
|
|
1345
|
+
return html.includes("window.Thinkific") || html.includes("assets.thinkific.com") || html.includes("cdn.thinkific.com") || html.includes("thnc.current_user-initialized");
|
|
1513
1346
|
},
|
|
1514
|
-
// Telemetry / third-party embed hosts only (profile.analyticsScripts) — NEVER
|
|
1515
|
-
// the *.thinkific.com asset CDN. These are matched against the request HOSTNAME
|
|
1516
|
-
// only (capture.ts: `host.includes(d)`), so each entry MUST be a bare hostname —
|
|
1517
|
-
// a path such as "www.google.com/recaptcha" can never match and would be dead.
|
|
1518
|
-
// `fast.wistia.net` = Wistia video player; `www.google.com` = sign-in reCAPTCHA
|
|
1519
|
-
// (api.js served from www.google.com/recaptcha; the whole host is safe to block
|
|
1520
|
-
// on a static mirror — no legit Thinkific asset is served from it).
|
|
1521
1347
|
stripDomains: ["fast.wistia.net", "www.google.com"],
|
|
1522
|
-
// The badge is removed via stripPatterns (below), NOT a `.class` stripSelector.
|
|
1523
|
-
// The class-based stripper (output.ts) is a non-greedy `…>[\s\S]*?<\/[^>]*>` that
|
|
1524
|
-
// stops at the FIRST closing tag, so on the nested badge markup
|
|
1525
|
-
// (<div class="footer__white-label"><a><svg><title>…</title>…</svg></a></div>) it
|
|
1526
|
-
// would cut at </title>, leaving orphan </svg></a></div> AND deleting the opening
|
|
1527
|
-
// <div>/<a> tags that the stripPatterns regexes anchor on (stripSelectors run
|
|
1528
|
-
// before stripPatterns). So no stripSelectors are used here.
|
|
1529
1348
|
stripSelectors: [],
|
|
1530
1349
|
stripPatterns: [
|
|
1531
|
-
// Whole badge wrapper: <div class="footer__white-label"> … </div>
|
|
1532
|
-
// (inner anchor + inline SVG titled "Teach online with Thinkific").
|
|
1533
1350
|
/<div[^>]*class="[^"]*footer__white-label[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1534
|
-
// Belt-and-suspenders: the branded powered-by referral anchor itself.
|
|
1535
1351
|
/<a[^>]*href="[^"]*thinkific\.com[^"]*utm_medium=powered-by[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1536
1352
|
],
|
|
1537
|
-
// ssr-static: the raw SSR HTML is complete, so no hydration wait is needed
|
|
1538
|
-
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is
|
|
1539
|
-
// Cloudflare (rocket-loader observed) but pages fetched cleanly in research;
|
|
1540
|
-
// per the authoring guide we keep a small capture buffer instead of literal 0.
|
|
1541
1353
|
hydrationTimeout: 2e3,
|
|
1542
1354
|
needsHydrationCheck: false,
|
|
1543
|
-
// cdnDomains: assets.thinkific.com, cdn.thinkific.com, cdn-themes.thinkific.com,
|
|
1544
|
-
// import.cdn.thinkific.com, files.cdn.thinkific.com (all *.thinkific.com), plus
|
|
1545
|
-
// the bare s3.amazonaws.com upload host. Theme bundles/fonts live on the
|
|
1546
|
-
// thinkific.com hosts; uploaded media on import.cdn / files.cdn (front S3).
|
|
1547
1355
|
mapAssetDir(host, pathname, ext) {
|
|
1548
1356
|
if (host.includes("thinkific.com")) {
|
|
1549
1357
|
if (VIDEO_EXTS21.includes(ext)) return "assets/videos";
|
|
@@ -1587,25 +1395,18 @@ var init_gumroad = __esm({
|
|
|
1587
1395
|
detectByHtml(html) {
|
|
1588
1396
|
return html.includes("cable.gumroad.com") || html.includes("assets.gumroad.com") || html.includes("ns/fb/gumroad#") || html.includes("gumroad:product");
|
|
1589
1397
|
},
|
|
1590
|
-
// Telemetry / analytics hosts only — never the asset CDN.
|
|
1591
|
-
// gumroad-analytics.com is Gumroad's first-party analytics proxy
|
|
1592
|
-
// (domain_settings.third_party_analytics_domain); GA / FB Pixel / TikTok Pixel
|
|
1593
|
-
// are injected client-side only when the creator enables them.
|
|
1594
1398
|
stripDomains: [
|
|
1595
1399
|
"gumroad-analytics.com",
|
|
1596
1400
|
"www.googletagmanager.com",
|
|
1597
1401
|
"connect.facebook.net",
|
|
1598
1402
|
"analytics.tiktok.com"
|
|
1599
1403
|
],
|
|
1600
|
-
// Defensively drop any surviving analytics <script> tags from the rendered DOM
|
|
1601
|
-
// (stripDomains already blocks them at the network layer during capture).
|
|
1602
1404
|
stripSelectors: [
|
|
1603
1405
|
'script[src*="gumroad-analytics.com"]',
|
|
1604
1406
|
'script[src*="googletagmanager.com"]',
|
|
1605
1407
|
'script[src*="connect.facebook.net"]',
|
|
1606
1408
|
'script[src*="analytics.tiktok.com"]'
|
|
1607
1409
|
],
|
|
1608
|
-
// No server-rendered badge to remove (badgePresent = false, confirmed).
|
|
1609
1410
|
stripPatterns: [],
|
|
1610
1411
|
hydrationTimeout: 8e3,
|
|
1611
1412
|
needsHydrationCheck: true,
|
|
@@ -1646,19 +1447,10 @@ var init_shopify = __esm({
|
|
|
1646
1447
|
detectByHtml(html) {
|
|
1647
1448
|
return html.includes("cdn.shopify.com") || html.includes("/cdn/shop/") || html.includes("Shopify.shop") || html.includes("ShopifyAnalytics");
|
|
1648
1449
|
},
|
|
1649
|
-
// Telemetry only — never the asset CDN.
|
|
1650
1450
|
stripDomains: ["monorail-edge.shopifysvc.com"],
|
|
1651
|
-
// The "Powered by Shopify" badge is a NESTED <span class="shopify-powered-by">
|
|
1652
|
-
// <a …>…</a></span>. The post-processor's `.class` strip only deletes up to the
|
|
1653
|
-
// FIRST closing tag (the inner </a>), which would leave an orphan </span>; worse,
|
|
1654
|
-
// stripSelectors run BEFORE stripPatterns, so it would consume the opening <span>
|
|
1655
|
-
// and stop the clean span regex below from matching. So the badge is removed
|
|
1656
|
-
// purely via stripPatterns — no `.class` selector.
|
|
1657
1451
|
stripSelectors: [],
|
|
1658
1452
|
stripPatterns: [
|
|
1659
|
-
// Whole badge container: <span class="shopify-powered-by" data-shopify="powered-by"><a href="…shopify.com?utm_campaign=poweredby…">Powered by Shopify</a></span>
|
|
1660
1453
|
/<span[^>]*class="[^"]*shopify-powered-by[^"]*"[^>]*>[\s\S]*?<\/span>/gi,
|
|
1661
|
-
// Fallback: the brand anchor itself (href carries ?utm_campaign=poweredby), in case the <span> wrapper is absent or customised.
|
|
1662
1454
|
/<a[^>]*href="[^"]*shopify\.com\?utm_campaign=poweredby[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
1663
1455
|
],
|
|
1664
1456
|
hydrationTimeout: 3500,
|
|
@@ -1711,9 +1503,7 @@ var init_gamma = __esm({
|
|
|
1711
1503
|
detectByHtml(html) {
|
|
1712
1504
|
return html.includes("assets.gammahosted.com") || html.includes("imgproxy.gamma.app") || html.includes("cdn.gamma.app") || html.includes("gamma-moveable-wrapper");
|
|
1713
1505
|
},
|
|
1714
|
-
// No telemetry/analytics hosts observed in the profile.
|
|
1715
1506
|
stripDomains: [],
|
|
1716
|
-
// No site badge and no editor chrome to strip on published Gamma sites.
|
|
1717
1507
|
stripSelectors: [],
|
|
1718
1508
|
stripPatterns: [],
|
|
1719
1509
|
hydrationTimeout: 8e3,
|
|
@@ -1766,7 +1556,10 @@ function platformsByCategory() {
|
|
|
1766
1556
|
}
|
|
1767
1557
|
return out;
|
|
1768
1558
|
}
|
|
1769
|
-
|
|
1559
|
+
function isBetaPlatform(name) {
|
|
1560
|
+
return !STABLE_PLATFORMS.has(name);
|
|
1561
|
+
}
|
|
1562
|
+
var PLATFORM_REGISTRY, CATEGORY_LABELS, CATEGORY_ORDER, STABLE_PLATFORMS;
|
|
1770
1563
|
var init_registry = __esm({
|
|
1771
1564
|
"src/platforms/registry.ts"() {
|
|
1772
1565
|
"use strict";
|
|
@@ -1796,11 +1589,9 @@ var init_registry = __esm({
|
|
|
1796
1589
|
init_shopify();
|
|
1797
1590
|
init_gamma();
|
|
1798
1591
|
PLATFORM_REGISTRY = [
|
|
1799
|
-
// frozen trio first (highest priority, unchanged behaviour)
|
|
1800
1592
|
framer,
|
|
1801
1593
|
webflow,
|
|
1802
1594
|
wix,
|
|
1803
|
-
// builders
|
|
1804
1595
|
bubble,
|
|
1805
1596
|
carrd,
|
|
1806
1597
|
duda,
|
|
@@ -1808,25 +1599,20 @@ var init_registry = __esm({
|
|
|
1808
1599
|
strikingly,
|
|
1809
1600
|
tilda,
|
|
1810
1601
|
weebly,
|
|
1811
|
-
// landing
|
|
1812
1602
|
clickfunnels,
|
|
1813
1603
|
elementor,
|
|
1814
1604
|
instapage,
|
|
1815
1605
|
systemeio,
|
|
1816
1606
|
unbounce,
|
|
1817
|
-
// cms
|
|
1818
1607
|
ghost,
|
|
1819
1608
|
notion,
|
|
1820
1609
|
wordpress,
|
|
1821
|
-
// course
|
|
1822
1610
|
kajabi,
|
|
1823
1611
|
podia,
|
|
1824
1612
|
teachable,
|
|
1825
1613
|
thinkific,
|
|
1826
|
-
// ecommerce
|
|
1827
1614
|
gumroad,
|
|
1828
1615
|
shopify,
|
|
1829
|
-
// ai
|
|
1830
1616
|
gamma
|
|
1831
1617
|
];
|
|
1832
1618
|
CATEGORY_LABELS = {
|
|
@@ -1845,6 +1631,7 @@ var init_registry = __esm({
|
|
|
1845
1631
|
"ecommerce",
|
|
1846
1632
|
"ai"
|
|
1847
1633
|
];
|
|
1634
|
+
STABLE_PLATFORMS = /* @__PURE__ */ new Set(["framer", "webflow", "wix"]);
|
|
1848
1635
|
}
|
|
1849
1636
|
});
|
|
1850
1637
|
|
|
@@ -1938,6 +1725,7 @@ __export(platforms_exports, {
|
|
|
1938
1725
|
CATEGORY_LABELS: () => CATEGORY_LABELS,
|
|
1939
1726
|
CATEGORY_ORDER: () => CATEGORY_ORDER,
|
|
1940
1727
|
PLATFORM_REGISTRY: () => PLATFORM_REGISTRY,
|
|
1728
|
+
STABLE_PLATFORMS: () => STABLE_PLATFORMS,
|
|
1941
1729
|
detectByDom: () => detectByDom,
|
|
1942
1730
|
detectByGenerator: () => detectByGenerator,
|
|
1943
1731
|
detectByHtml: () => detectByHtml,
|
|
@@ -1945,6 +1733,7 @@ __export(platforms_exports, {
|
|
|
1945
1733
|
detectPlatform: () => detectPlatform,
|
|
1946
1734
|
framer: () => framer,
|
|
1947
1735
|
getPlatformByName: () => getPlatformByName,
|
|
1736
|
+
isBetaPlatform: () => isBetaPlatform,
|
|
1948
1737
|
platformsByCategory: () => platformsByCategory,
|
|
1949
1738
|
readGenerator: () => readGenerator,
|
|
1950
1739
|
sortedByPriority: () => sortedByPriority,
|
|
@@ -2163,36 +1952,19 @@ function boxTop(w) {
|
|
|
2163
1952
|
const inner = w - 4;
|
|
2164
1953
|
return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
|
|
2165
1954
|
}
|
|
2166
|
-
function panelTop(w) {
|
|
2167
|
-
const inner = w - 4;
|
|
2168
|
-
return ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
|
|
2169
|
-
}
|
|
2170
1955
|
function boxBot(w) {
|
|
2171
1956
|
const inner = w - 4;
|
|
2172
1957
|
return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
|
|
2173
1958
|
}
|
|
2174
|
-
function panelBot(w) {
|
|
2175
|
-
const inner = w - 4;
|
|
2176
|
-
return ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
|
|
2177
|
-
}
|
|
2178
1959
|
function boxLine(w, text) {
|
|
2179
1960
|
const inner = w - 4;
|
|
2180
1961
|
const padded = padRight(text, inner);
|
|
2181
1962
|
return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
|
|
2182
1963
|
}
|
|
2183
|
-
function panelLine(w, text) {
|
|
2184
|
-
const inner = w - 4;
|
|
2185
|
-
const padded = padRight(text, inner);
|
|
2186
|
-
return ui.border("\u2502 ") + padded + ui.border(" \u2502");
|
|
2187
|
-
}
|
|
2188
1964
|
function boxSep(w) {
|
|
2189
1965
|
const inner = w - 4;
|
|
2190
1966
|
return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
|
|
2191
1967
|
}
|
|
2192
|
-
function panelSep(w) {
|
|
2193
|
-
const inner = w - 4;
|
|
2194
|
-
return ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
|
|
2195
|
-
}
|
|
2196
1968
|
function boxRow(w, label, value) {
|
|
2197
1969
|
const inner = w - 4;
|
|
2198
1970
|
const labelPlain = stripAnsi(chalk3.bold(label));
|
|
@@ -2208,18 +1980,116 @@ var init_box = __esm({
|
|
|
2208
1980
|
}
|
|
2209
1981
|
});
|
|
2210
1982
|
|
|
1983
|
+
// src/cli/backdrop.ts
|
|
1984
|
+
import chalk4 from "chalk";
|
|
1985
|
+
import { stdout } from "process";
|
|
1986
|
+
function hash(x, y, frame) {
|
|
1987
|
+
let h = x * 374761393 + y * 668265263 + frame * 2246822519;
|
|
1988
|
+
h = (h ^ h >> 13) * 1274126177;
|
|
1989
|
+
h = h ^ h >> 16;
|
|
1990
|
+
return Math.abs(h) % 1e3;
|
|
1991
|
+
}
|
|
1992
|
+
var GLYPHS, COLORS, FRAME_MS, DENSITY, Backdrop;
|
|
1993
|
+
var init_backdrop = __esm({
|
|
1994
|
+
"src/cli/backdrop.ts"() {
|
|
1995
|
+
"use strict";
|
|
1996
|
+
GLYPHS = ["\xB7", "\xB7", "\u2022", "\u25AA"];
|
|
1997
|
+
COLORS = ["#3d332a", "#4a3b2d", "#584535", "#33302c"];
|
|
1998
|
+
FRAME_MS = 160;
|
|
1999
|
+
DENSITY = 28;
|
|
2000
|
+
Backdrop = class {
|
|
2001
|
+
timer = null;
|
|
2002
|
+
frame = 0;
|
|
2003
|
+
prev = [];
|
|
2004
|
+
exclude = null;
|
|
2005
|
+
constructor() {
|
|
2006
|
+
if (process.env.FRAMER_EXPORT_NO_BG) this.frame = -1;
|
|
2007
|
+
}
|
|
2008
|
+
setExclude(rect) {
|
|
2009
|
+
this.exclude = rect;
|
|
2010
|
+
}
|
|
2011
|
+
start() {
|
|
2012
|
+
if (this.frame === -1 || !stdout.isTTY || this.timer) return;
|
|
2013
|
+
this.paint();
|
|
2014
|
+
this.timer = setInterval(() => this.paint(), FRAME_MS);
|
|
2015
|
+
}
|
|
2016
|
+
stop() {
|
|
2017
|
+
if (this.timer) clearInterval(this.timer);
|
|
2018
|
+
this.timer = null;
|
|
2019
|
+
this.erasePrev();
|
|
2020
|
+
this.prev = [];
|
|
2021
|
+
}
|
|
2022
|
+
inExclude(x, y) {
|
|
2023
|
+
const r = this.exclude;
|
|
2024
|
+
if (!r) return false;
|
|
2025
|
+
return y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width;
|
|
2026
|
+
}
|
|
2027
|
+
erasePrev() {
|
|
2028
|
+
if (this.prev.length === 0) return;
|
|
2029
|
+
let out = "";
|
|
2030
|
+
for (const [x, y] of this.prev) {
|
|
2031
|
+
out += `\x1B[${y};${x}H `;
|
|
2032
|
+
}
|
|
2033
|
+
stdout.write(out);
|
|
2034
|
+
}
|
|
2035
|
+
paint() {
|
|
2036
|
+
const rows = stdout.rows || 24;
|
|
2037
|
+
const columns = stdout.columns || 80;
|
|
2038
|
+
this.frame++;
|
|
2039
|
+
let out = "";
|
|
2040
|
+
for (const [x, y] of this.prev) {
|
|
2041
|
+
if (!this.inExclude(x, y)) out += `\x1B[${y};${x}H `;
|
|
2042
|
+
}
|
|
2043
|
+
const next = [];
|
|
2044
|
+
for (let y = 1; y <= rows; y += 2) {
|
|
2045
|
+
for (let x = 1; x <= columns; x += 2) {
|
|
2046
|
+
const h = hash(x, y, Math.floor(this.frame / 3) + Math.floor(x / 24) + Math.floor(y / 12));
|
|
2047
|
+
if (h >= DENSITY) continue;
|
|
2048
|
+
if (this.inExclude(x, y)) continue;
|
|
2049
|
+
const glyph = GLYPHS[h % GLYPHS.length];
|
|
2050
|
+
const color = COLORS[(h >> 2) % COLORS.length];
|
|
2051
|
+
out += `\x1B[${y};${x}H${chalk4.hex(color)(glyph)}`;
|
|
2052
|
+
next.push([x, y]);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
this.prev = next;
|
|
2056
|
+
stdout.write(out);
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2211
2062
|
// src/cli/select.ts
|
|
2212
2063
|
import readline from "readline";
|
|
2213
|
-
import
|
|
2064
|
+
import chalk5 from "chalk";
|
|
2065
|
+
import { stdin, stdout as stdout2 } from "process";
|
|
2066
|
+
function panelRow(width, content = "") {
|
|
2067
|
+
const visible = stripAnsi(content).length;
|
|
2068
|
+
const pad = Math.max(0, width - visible);
|
|
2069
|
+
return chalk5.bgHex(THEME.panel)(content + " ".repeat(pad));
|
|
2070
|
+
}
|
|
2071
|
+
function selectedRow(width, content) {
|
|
2072
|
+
const visible = content.length;
|
|
2073
|
+
const pad = Math.max(0, width - visible);
|
|
2074
|
+
return chalk5.bgHex(THEME.primary).hex(THEME.background).bold(content + " ".repeat(pad));
|
|
2075
|
+
}
|
|
2076
|
+
function titleRow(width, title) {
|
|
2077
|
+
const left = ` ${ui.text.bold(title)}`;
|
|
2078
|
+
const right = `${ui.muted("esc")} `;
|
|
2079
|
+
const visible = stripAnsi(left).length + stripAnsi(right).length;
|
|
2080
|
+
const gap = Math.max(1, width - visible);
|
|
2081
|
+
return chalk5.bgHex(THEME.panel)(left + " ".repeat(gap) + right);
|
|
2082
|
+
}
|
|
2214
2083
|
async function select(question, options, defaultIndex = 0, config = {}) {
|
|
2215
|
-
const isTTY = stdin.isTTY &&
|
|
2084
|
+
const isTTY = stdin.isTTY && stdout2.isTTY;
|
|
2216
2085
|
if (!isTTY) {
|
|
2217
|
-
|
|
2086
|
+
const flat = options.map((o) => o.heading ? { ...o, disabled: true } : o);
|
|
2087
|
+
return fallbackPrompt(question, flat, defaultIndex, config);
|
|
2218
2088
|
}
|
|
2219
2089
|
return arrowSelect(question, options, defaultIndex, config);
|
|
2220
2090
|
}
|
|
2221
2091
|
async function promptInput(question, defaultValue = "", config = {}) {
|
|
2222
|
-
if (!stdin.isTTY || !
|
|
2092
|
+
if (!stdin.isTTY || !stdout2.isTTY) {
|
|
2223
2093
|
return fallbackInput(question, defaultValue);
|
|
2224
2094
|
}
|
|
2225
2095
|
return fullscreenInput(question, defaultValue, config);
|
|
@@ -2227,73 +2097,154 @@ async function promptInput(question, defaultValue = "", config = {}) {
|
|
|
2227
2097
|
async function arrowSelect(question, options, defaultIndex, config) {
|
|
2228
2098
|
const actions = config.actions ?? [];
|
|
2229
2099
|
const headerLines = config.headerLines ?? [];
|
|
2230
|
-
const
|
|
2100
|
+
const searchable = config.searchable === true;
|
|
2101
|
+
const width = Math.max(50, Math.min(maxWidth(), 64));
|
|
2231
2102
|
const inner = width - 4;
|
|
2232
|
-
const actionLineOffset = 2 + headerLines.length;
|
|
2233
2103
|
const hasActions = actions.length > 0;
|
|
2234
|
-
const
|
|
2235
|
-
const lineCount = options.length + 4 + headerLines.length + (hasActions ? 1 : 0);
|
|
2104
|
+
const headerCount = headerLines.length;
|
|
2236
2105
|
const rows = process.stdout.rows || 24;
|
|
2237
2106
|
const columns = process.stdout.columns || 80;
|
|
2107
|
+
const searchRows = searchable ? 1 : 0;
|
|
2108
|
+
const optionStartOffset = 3 + searchRows + headerCount + (headerCount > 0 ? 1 : 0);
|
|
2109
|
+
const chromeLines = optionStartOffset + (hasActions ? 3 : 1) + 2;
|
|
2110
|
+
const maxVisible = Math.max(4, rows - chromeLines);
|
|
2111
|
+
const visibleCount = Math.min(options.length, maxVisible);
|
|
2112
|
+
const actionLineOffset = optionStartOffset + visibleCount + 1;
|
|
2113
|
+
const lineCount = actionLineOffset + (hasActions ? 2 : 1);
|
|
2238
2114
|
const panelTopRow = Math.max(2, Math.floor((rows - lineCount) / 2) + 1);
|
|
2239
2115
|
const panelLeftCol = Math.max(1, Math.floor((columns - width) / 2) + 1);
|
|
2240
2116
|
const footerRow = Math.min(rows, panelTopRow + lineCount + 1);
|
|
2241
2117
|
return new Promise((resolve) => {
|
|
2242
|
-
|
|
2243
|
-
|
|
2118
|
+
let query = "";
|
|
2119
|
+
const buildView = () => {
|
|
2120
|
+
if (!query) return options.map((_, i) => i);
|
|
2121
|
+
const q = query.toLowerCase();
|
|
2122
|
+
const matched = /* @__PURE__ */ new Set();
|
|
2123
|
+
options.forEach((option, i) => {
|
|
2124
|
+
if (!option.heading && stripAnsi(option.label).toLowerCase().includes(q)) matched.add(i);
|
|
2125
|
+
});
|
|
2126
|
+
const view2 = [];
|
|
2127
|
+
for (let i = 0; i < options.length; i++) {
|
|
2128
|
+
if (options[i].heading) {
|
|
2129
|
+
for (let j = i + 1; j < options.length && !options[j].heading; j++) {
|
|
2130
|
+
if (matched.has(j)) {
|
|
2131
|
+
view2.push(i);
|
|
2132
|
+
break;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
} else if (matched.has(i)) {
|
|
2136
|
+
view2.push(i);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return view2;
|
|
2140
|
+
};
|
|
2141
|
+
let view = buildView();
|
|
2142
|
+
const selectableAt = (pos) => {
|
|
2143
|
+
const option = options[view[pos]];
|
|
2144
|
+
return !option.disabled && !option.heading;
|
|
2145
|
+
};
|
|
2146
|
+
const firstSelectable = () => {
|
|
2147
|
+
for (let pos = 0; pos < view.length; pos++) if (selectableAt(pos)) return pos;
|
|
2148
|
+
return 0;
|
|
2149
|
+
};
|
|
2150
|
+
let selected = defaultIndex >= 0 && defaultIndex < options.length && view.indexOf(defaultIndex) >= 0 ? view.indexOf(defaultIndex) : firstSelectable();
|
|
2151
|
+
if (!view.length || !selectableAt(Math.min(selected, Math.max(0, view.length - 1)))) {
|
|
2152
|
+
selected = firstSelectable();
|
|
2153
|
+
}
|
|
2244
2154
|
let selectedAction = null;
|
|
2245
|
-
|
|
2155
|
+
let scrollOffset = 0;
|
|
2246
2156
|
const move = (direction) => {
|
|
2247
2157
|
let next = selected + direction;
|
|
2248
|
-
while (next >= 0 && next <
|
|
2249
|
-
if (
|
|
2158
|
+
while (next >= 0 && next < view.length) {
|
|
2159
|
+
if (selectableAt(next)) {
|
|
2250
2160
|
selected = next;
|
|
2251
2161
|
return;
|
|
2252
2162
|
}
|
|
2253
2163
|
next += direction;
|
|
2254
2164
|
}
|
|
2255
2165
|
};
|
|
2256
|
-
const
|
|
2257
|
-
if (
|
|
2258
|
-
|
|
2166
|
+
const syncScroll = () => {
|
|
2167
|
+
if (view.length <= visibleCount) {
|
|
2168
|
+
scrollOffset = 0;
|
|
2169
|
+
return;
|
|
2259
2170
|
}
|
|
2171
|
+
const anchor = selected > 0 && options[view[selected - 1]].heading ? selected - 1 : selected;
|
|
2172
|
+
if (anchor < scrollOffset) scrollOffset = anchor;
|
|
2173
|
+
if (selected >= scrollOffset + visibleCount) scrollOffset = selected - visibleCount + 1;
|
|
2174
|
+
scrollOffset = Math.max(0, Math.min(scrollOffset, view.length - visibleCount));
|
|
2175
|
+
};
|
|
2176
|
+
const backdrop = new Backdrop();
|
|
2177
|
+
backdrop.setExclude({
|
|
2178
|
+
top: panelTopRow - 1,
|
|
2179
|
+
left: panelLeftCol - 2,
|
|
2180
|
+
width: width + 4,
|
|
2181
|
+
height: lineCount + 3
|
|
2182
|
+
});
|
|
2183
|
+
const searchRow = () => {
|
|
2184
|
+
const shown = query ? `${chalk5.hex(THEME.text)(query)}${chalk5.hex(THEME.primary)("\u258C")}` : ui.muted("type to search");
|
|
2185
|
+
const content = ` ${chalk5.hex(THEME.text)("\u258F")} ${shown}`;
|
|
2186
|
+
const visible = stripAnsi(content).length;
|
|
2187
|
+
return chalk5.bgHex(THEME.element)(content + " ".repeat(Math.max(0, width - visible)));
|
|
2188
|
+
};
|
|
2189
|
+
const render = () => {
|
|
2260
2190
|
const lines = [];
|
|
2261
|
-
lines.push(
|
|
2262
|
-
lines.push(
|
|
2263
|
-
|
|
2264
|
-
);
|
|
2191
|
+
lines.push(panelRow(width));
|
|
2192
|
+
lines.push(titleRow(width, question));
|
|
2193
|
+
if (searchable) lines.push(searchRow());
|
|
2194
|
+
lines.push(panelRow(width));
|
|
2265
2195
|
for (const header of headerLines) {
|
|
2266
|
-
lines.push(
|
|
2267
|
-
}
|
|
2268
|
-
if (hasActions) {
|
|
2269
|
-
lines.push(
|
|
2270
|
-
panelLine(width, centerText(renderActions(actions, selectedAction, inner), inner))
|
|
2271
|
-
);
|
|
2196
|
+
lines.push(panelRow(width, ` ${ui.muted(truncatePlain(stripAnsi(header), inner))}`));
|
|
2272
2197
|
}
|
|
2273
|
-
lines.push(
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2198
|
+
if (headerCount > 0) lines.push(panelRow(width));
|
|
2199
|
+
syncScroll();
|
|
2200
|
+
for (let pos = scrollOffset; pos < scrollOffset + visibleCount; pos++) {
|
|
2201
|
+
if (pos < view.length) {
|
|
2202
|
+
lines.push(
|
|
2203
|
+
renderOption(
|
|
2204
|
+
options[view[pos]],
|
|
2205
|
+
selectedAction === null && pos === selected,
|
|
2206
|
+
view[pos] === defaultIndex,
|
|
2207
|
+
width,
|
|
2280
2208
|
inner
|
|
2281
2209
|
)
|
|
2282
|
-
)
|
|
2283
|
-
|
|
2210
|
+
);
|
|
2211
|
+
} else {
|
|
2212
|
+
lines.push(panelRow(width));
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (view.length === 0) {
|
|
2216
|
+
lines[optionStartOffset] = panelRow(width, ` ${ui.muted("No results found")}`);
|
|
2217
|
+
}
|
|
2218
|
+
lines.push(
|
|
2219
|
+
view.length > visibleCount ? panelRow(width, ` ${ui.muted(`${selected + 1}/${view.length} scroll for more`)}`) : panelRow(width)
|
|
2220
|
+
);
|
|
2221
|
+
if (hasActions) {
|
|
2222
|
+
lines.push(panelRow(width, renderActions(actions, selectedAction)));
|
|
2223
|
+
lines.push(panelRow(width));
|
|
2284
2224
|
}
|
|
2285
|
-
lines.push(panelBot(width));
|
|
2286
2225
|
lines.forEach((line, index) => writeAt(panelTopRow + index, panelLeftCol, line));
|
|
2287
2226
|
writeAt(
|
|
2288
2227
|
footerRow,
|
|
2289
2228
|
panelLeftCol,
|
|
2290
2229
|
centerText(
|
|
2291
|
-
ui.muted(
|
|
2230
|
+
ui.muted(
|
|
2231
|
+
config.footer || (searchable ? "type to search \u2191\u2193 move enter select esc close" : "\u2191\u2193 move enter select mouse click esc close")
|
|
2232
|
+
),
|
|
2292
2233
|
width
|
|
2293
2234
|
)
|
|
2294
2235
|
);
|
|
2295
2236
|
};
|
|
2296
|
-
const
|
|
2237
|
+
const refilter = () => {
|
|
2238
|
+
view = buildView();
|
|
2239
|
+
selected = firstSelectable();
|
|
2240
|
+
scrollOffset = 0;
|
|
2241
|
+
render();
|
|
2242
|
+
};
|
|
2243
|
+
const choose = (value) => {
|
|
2244
|
+
if (value === void 0) {
|
|
2245
|
+
if (!view.length || !selectableAt(selected)) return;
|
|
2246
|
+
value = options[view[selected]].value;
|
|
2247
|
+
}
|
|
2297
2248
|
cleanup();
|
|
2298
2249
|
console.log(
|
|
2299
2250
|
` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(labelForValue(value, options, actions))}
|
|
@@ -2324,10 +2275,12 @@ async function arrowSelect(question, options, defaultIndex, config) {
|
|
|
2324
2275
|
if (mouse.kind === "click") choose(actions[actionIdx].value);
|
|
2325
2276
|
return;
|
|
2326
2277
|
}
|
|
2327
|
-
const
|
|
2328
|
-
if (
|
|
2329
|
-
|
|
2330
|
-
|
|
2278
|
+
const relative = mouse.y - panelTopRow - optionStartOffset;
|
|
2279
|
+
if (relative < 0 || relative >= visibleCount) return;
|
|
2280
|
+
const pos = scrollOffset + relative;
|
|
2281
|
+
if (pos >= view.length || !selectableAt(pos)) return;
|
|
2282
|
+
if (selected !== pos) {
|
|
2283
|
+
selected = pos;
|
|
2331
2284
|
selectedAction = null;
|
|
2332
2285
|
render();
|
|
2333
2286
|
}
|
|
@@ -2336,6 +2289,7 @@ async function arrowSelect(question, options, defaultIndex, config) {
|
|
|
2336
2289
|
}
|
|
2337
2290
|
};
|
|
2338
2291
|
const cleanup = () => {
|
|
2292
|
+
backdrop.stop();
|
|
2339
2293
|
leaveInteractiveScreen();
|
|
2340
2294
|
stdin.setRawMode(false);
|
|
2341
2295
|
stdin.removeListener("keypress", onKeypress);
|
|
@@ -2345,14 +2299,15 @@ async function arrowSelect(question, options, defaultIndex, config) {
|
|
|
2345
2299
|
readline.emitKeypressEvents(stdin);
|
|
2346
2300
|
stdin.setRawMode(true);
|
|
2347
2301
|
enterInteractiveScreen(true);
|
|
2348
|
-
render(
|
|
2349
|
-
|
|
2302
|
+
render();
|
|
2303
|
+
backdrop.start();
|
|
2304
|
+
const onKeypress = (str, key) => {
|
|
2350
2305
|
if (!key) return;
|
|
2351
|
-
if (key.name === "up"
|
|
2306
|
+
if (key.name === "up") {
|
|
2352
2307
|
move(-1);
|
|
2353
2308
|
selectedAction = null;
|
|
2354
2309
|
render();
|
|
2355
|
-
} else if (key.name === "down"
|
|
2310
|
+
} else if (key.name === "down") {
|
|
2356
2311
|
move(1);
|
|
2357
2312
|
selectedAction = null;
|
|
2358
2313
|
render();
|
|
@@ -2360,10 +2315,25 @@ async function arrowSelect(question, options, defaultIndex, config) {
|
|
|
2360
2315
|
selectedAction = selectedAction === null ? 0 : null;
|
|
2361
2316
|
render();
|
|
2362
2317
|
} else if (key.name === "return") {
|
|
2363
|
-
|
|
2364
|
-
|
|
2318
|
+
if (selectedAction !== null) choose(actions[selectedAction].value);
|
|
2319
|
+
else choose();
|
|
2320
|
+
} else if (key.ctrl && key.name === "c") {
|
|
2365
2321
|
cleanup();
|
|
2366
2322
|
process.exit(0);
|
|
2323
|
+
} else if (key.name === "escape") {
|
|
2324
|
+
if (searchable && query) {
|
|
2325
|
+
query = "";
|
|
2326
|
+
refilter();
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
cleanup();
|
|
2330
|
+
process.exit(0);
|
|
2331
|
+
} else if (searchable && key.name === "backspace") {
|
|
2332
|
+
query = query.slice(0, -1);
|
|
2333
|
+
refilter();
|
|
2334
|
+
} else if (searchable && str && !key.ctrl && !key.meta && str >= " " && !isTerminalSequence(str, key)) {
|
|
2335
|
+
query += cleanInputValue(str.replace(/[\r\n]/g, ""));
|
|
2336
|
+
refilter();
|
|
2367
2337
|
}
|
|
2368
2338
|
};
|
|
2369
2339
|
stdin.resume();
|
|
@@ -2401,7 +2371,7 @@ async function fallbackPrompt(question, options, defaultIndex, config) {
|
|
|
2401
2371
|
}
|
|
2402
2372
|
}
|
|
2403
2373
|
return new Promise((resolve) => {
|
|
2404
|
-
const rl = readline.createInterface({ input: stdin, output:
|
|
2374
|
+
const rl = readline.createInterface({ input: stdin, output: stdout2 });
|
|
2405
2375
|
const firstEnabled = options.findIndex((option) => !option.disabled);
|
|
2406
2376
|
const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
|
|
2407
2377
|
printFallbackOptions(question, options, enabledDefault, config);
|
|
@@ -2451,9 +2421,10 @@ async function fallbackPrompt(question, options, defaultIndex, config) {
|
|
|
2451
2421
|
}
|
|
2452
2422
|
function fullscreenInput(question, defaultValue, config) {
|
|
2453
2423
|
const headerLines = config.headerLines ?? [];
|
|
2454
|
-
const width = Math.max(
|
|
2424
|
+
const width = Math.max(50, Math.min(maxWidth(), 64));
|
|
2455
2425
|
const inner = width - 4;
|
|
2456
|
-
const
|
|
2426
|
+
const headerCount = headerLines.length;
|
|
2427
|
+
const lineCount = 3 + headerCount + (headerCount > 0 ? 1 : 0) + 2;
|
|
2457
2428
|
const rows = process.stdout.rows || 24;
|
|
2458
2429
|
const columns = process.stdout.columns || 80;
|
|
2459
2430
|
const panelTopRow = Math.max(2, Math.floor((rows - lineCount) / 2) + 1);
|
|
@@ -2461,30 +2432,40 @@ function fullscreenInput(question, defaultValue, config) {
|
|
|
2461
2432
|
const footerRow = Math.min(rows, panelTopRow + lineCount + 1);
|
|
2462
2433
|
return new Promise((resolve) => {
|
|
2463
2434
|
let value = defaultValue;
|
|
2435
|
+
const backdrop = new Backdrop();
|
|
2436
|
+
backdrop.setExclude({
|
|
2437
|
+
top: panelTopRow - 1,
|
|
2438
|
+
left: panelLeftCol - 2,
|
|
2439
|
+
width: width + 4,
|
|
2440
|
+
height: lineCount + 3
|
|
2441
|
+
});
|
|
2464
2442
|
const render = () => {
|
|
2465
|
-
stdout.write("\x1B[2J");
|
|
2466
2443
|
const shown = value || "";
|
|
2467
|
-
const clipped = truncatePlain(shown, Math.max(12, inner -
|
|
2468
|
-
const
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
panelLine(width, centerText(`${ui.primary("\u25CF")} ${ui.text.bold(question)}`, inner))
|
|
2444
|
+
const clipped = truncatePlain(shown, Math.max(12, inner - 6));
|
|
2445
|
+
const inputContent = ` ${chalk5.hex(THEME.text)("\u258F")} ${chalk5.hex(THEME.text)(clipped)}${chalk5.hex(THEME.primary)("\u258C")}`;
|
|
2446
|
+
const inputVisible = stripAnsi(inputContent).length;
|
|
2447
|
+
const inputRow = chalk5.bgHex(THEME.element)(
|
|
2448
|
+
inputContent + " ".repeat(Math.max(0, width - inputVisible))
|
|
2473
2449
|
);
|
|
2450
|
+
const lines = [];
|
|
2451
|
+
lines.push(panelRow(width));
|
|
2452
|
+
lines.push(titleRow(width, question));
|
|
2453
|
+
lines.push(panelRow(width));
|
|
2474
2454
|
for (const header of headerLines) {
|
|
2475
|
-
lines.push(
|
|
2455
|
+
lines.push(panelRow(width, ` ${ui.muted(truncatePlain(stripAnsi(header), inner))}`));
|
|
2476
2456
|
}
|
|
2477
|
-
lines.push(
|
|
2478
|
-
lines.push(
|
|
2479
|
-
lines.push(
|
|
2457
|
+
if (headerCount > 0) lines.push(panelRow(width));
|
|
2458
|
+
lines.push(inputRow);
|
|
2459
|
+
lines.push(panelRow(width));
|
|
2480
2460
|
lines.forEach((line, index) => writeAt(panelTopRow + index, panelLeftCol, line));
|
|
2481
2461
|
writeAt(
|
|
2482
2462
|
footerRow,
|
|
2483
2463
|
panelLeftCol,
|
|
2484
|
-
centerText(ui.muted(config.footer || "type value
|
|
2464
|
+
centerText(ui.muted(config.footer || "type value enter confirm esc close"), width)
|
|
2485
2465
|
);
|
|
2486
2466
|
};
|
|
2487
2467
|
const cleanup = () => {
|
|
2468
|
+
backdrop.stop();
|
|
2488
2469
|
leaveInteractiveScreen();
|
|
2489
2470
|
stdin.setRawMode(false);
|
|
2490
2471
|
stdin.removeListener("keypress", onKeypress);
|
|
@@ -2523,6 +2504,7 @@ function fullscreenInput(question, defaultValue, config) {
|
|
|
2523
2504
|
stdin.setRawMode(true);
|
|
2524
2505
|
enterInteractiveScreen(false);
|
|
2525
2506
|
render();
|
|
2507
|
+
backdrop.start();
|
|
2526
2508
|
stdin.resume();
|
|
2527
2509
|
stdin.on("keypress", onKeypress);
|
|
2528
2510
|
});
|
|
@@ -2535,7 +2517,7 @@ async function fallbackInput(question, defaultValue) {
|
|
|
2535
2517
|
return cleanInputValue(answer.trim() || defaultValue);
|
|
2536
2518
|
}
|
|
2537
2519
|
return new Promise((resolve) => {
|
|
2538
|
-
const rl = readline.createInterface({ input: stdin, output:
|
|
2520
|
+
const rl = readline.createInterface({ input: stdin, output: stdout2 });
|
|
2539
2521
|
const suffix = defaultValue ? ui.muted(` (${defaultValue})`) : "";
|
|
2540
2522
|
rl.question(` ${ui.primary(">")} ${ui.text.bold(question)}${suffix}: `, (answer) => {
|
|
2541
2523
|
rl.close();
|
|
@@ -2582,17 +2564,15 @@ function readPipedLine() {
|
|
|
2582
2564
|
}
|
|
2583
2565
|
return pipedLinesPromise.then((lines) => lines[pipedLineIndex++] ?? "");
|
|
2584
2566
|
}
|
|
2585
|
-
function renderActions(actions, active
|
|
2586
|
-
|
|
2587
|
-
const label =
|
|
2588
|
-
|
|
2589
|
-
Math.max(8, Math.floor(width / actions.length) - 8)
|
|
2590
|
-
);
|
|
2591
|
-
if (action.disabled) return `${ui.border("[")} ${ui.muted(label)} ${ui.border("]")}`;
|
|
2567
|
+
function renderActions(actions, active) {
|
|
2568
|
+
const rendered = actions.map((action, index) => {
|
|
2569
|
+
const label = stripAnsi(action.label);
|
|
2570
|
+
if (action.disabled) return ui.muted(label);
|
|
2592
2571
|
if (active === index)
|
|
2593
|
-
return
|
|
2594
|
-
return
|
|
2595
|
-
}).join(
|
|
2572
|
+
return chalk5.bgHex(THEME.primary).hex(THEME.background).bold(` ${label} `);
|
|
2573
|
+
return ui.text(label);
|
|
2574
|
+
}).join(" ");
|
|
2575
|
+
return ` ${rendered}`;
|
|
2596
2576
|
}
|
|
2597
2577
|
function actionIndexAtX(actions, mouseX, panelLeftCol, inner) {
|
|
2598
2578
|
if (actions.length === 0) return null;
|
|
@@ -2606,24 +2586,30 @@ function labelForValue(value, options, actions) {
|
|
|
2606
2586
|
options.find((option) => option.value === value)?.label || actions.find((action) => action.value === value)?.label || value
|
|
2607
2587
|
);
|
|
2608
2588
|
}
|
|
2609
|
-
function renderOption(option,
|
|
2610
|
-
const plain = truncatePlain(stripAnsi(option.label), Math.max(10,
|
|
2589
|
+
function renderOption(option, isSelected, isDefault, width, inner) {
|
|
2590
|
+
const plain = truncatePlain(stripAnsi(option.label), Math.max(10, inner - 2));
|
|
2591
|
+
if (option.heading) {
|
|
2592
|
+
return panelRow(width, ` ${ui.accent.bold(plain)}`);
|
|
2593
|
+
}
|
|
2594
|
+
if (isSelected && !option.disabled) {
|
|
2595
|
+
return selectedRow(width, ` ${isDefault ? "\u25CF" : " "} ${plain}`);
|
|
2596
|
+
}
|
|
2611
2597
|
if (option.disabled) {
|
|
2612
|
-
return
|
|
2598
|
+
return panelRow(width, ` ${ui.muted(plain)}`);
|
|
2613
2599
|
}
|
|
2614
|
-
if (
|
|
2615
|
-
return
|
|
2600
|
+
if (isDefault) {
|
|
2601
|
+
return panelRow(width, ` ${ui.primary("\u25CF")} ${ui.primary(plain)}`);
|
|
2616
2602
|
}
|
|
2617
|
-
return
|
|
2603
|
+
return panelRow(width, ` ${ui.text(plain)}`);
|
|
2618
2604
|
}
|
|
2619
2605
|
function enterInteractiveScreen(enableMouse) {
|
|
2620
|
-
|
|
2606
|
+
stdout2.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?25l");
|
|
2621
2607
|
if (enableMouse) {
|
|
2622
|
-
|
|
2608
|
+
stdout2.write("\x1B[?1006h\x1B[?1000h\x1B[?1002h\x1B[?1003h");
|
|
2623
2609
|
}
|
|
2624
2610
|
}
|
|
2625
2611
|
function leaveInteractiveScreen() {
|
|
2626
|
-
|
|
2612
|
+
stdout2.write(
|
|
2627
2613
|
"\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?25h\x1B[?1049l"
|
|
2628
2614
|
);
|
|
2629
2615
|
}
|
|
@@ -2634,10 +2620,10 @@ function parseMouseEvent(chunk) {
|
|
|
2634
2620
|
const code = Number(match[1]);
|
|
2635
2621
|
const x = Number(match[2]);
|
|
2636
2622
|
const y = Number(match[3]);
|
|
2637
|
-
const
|
|
2623
|
+
const state2 = match[4];
|
|
2638
2624
|
if (code === 64) return { kind: "wheel-up", x, y };
|
|
2639
2625
|
if (code === 65) return { kind: "wheel-down", x, y };
|
|
2640
|
-
if (
|
|
2626
|
+
if (state2 === "m") return { kind: "click", x, y };
|
|
2641
2627
|
if ((code & 32) === 32 || code === 35) return { kind: "hover", x, y };
|
|
2642
2628
|
if ((code & 3) === 0) return { kind: "hover", x, y };
|
|
2643
2629
|
return null;
|
|
@@ -2654,8 +2640,8 @@ function parseLegacyMouseEvent(text) {
|
|
|
2654
2640
|
if ((code & 32) === 32) return { kind: "hover", x, y };
|
|
2655
2641
|
return { kind: "hover", x, y };
|
|
2656
2642
|
}
|
|
2657
|
-
function writeAt(
|
|
2658
|
-
|
|
2643
|
+
function writeAt(row2, col, text) {
|
|
2644
|
+
stdout2.write(`\x1B[${row2};${col}H${text}`);
|
|
2659
2645
|
}
|
|
2660
2646
|
function isTerminalSequence(str, key) {
|
|
2661
2647
|
return str.includes("\x1B") || !!key.sequence?.includes("\x1B") || /^(?:\d+;){2}\d+[mM]$/.test(str);
|
|
@@ -2668,16 +2654,1012 @@ var init_select = __esm({
|
|
|
2668
2654
|
"src/cli/select.ts"() {
|
|
2669
2655
|
"use strict";
|
|
2670
2656
|
init_box();
|
|
2657
|
+
init_backdrop();
|
|
2671
2658
|
init_theme();
|
|
2672
2659
|
pipedLinesPromise = null;
|
|
2673
2660
|
pipedLineIndex = 0;
|
|
2674
2661
|
}
|
|
2675
2662
|
});
|
|
2676
2663
|
|
|
2664
|
+
// src/ui/page.ts
|
|
2665
|
+
var UI_HTML;
|
|
2666
|
+
var init_page = __esm({
|
|
2667
|
+
"src/ui/page.ts"() {
|
|
2668
|
+
"use strict";
|
|
2669
|
+
UI_HTML = `<!doctype html>
|
|
2670
|
+
<html lang="en">
|
|
2671
|
+
<head>
|
|
2672
|
+
<meta charset="utf-8" />
|
|
2673
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2674
|
+
<title>Framer Export</title>
|
|
2675
|
+
<style>
|
|
2676
|
+
:root {
|
|
2677
|
+
--bg: #0a0a0a;
|
|
2678
|
+
--bg-panel: #141414;
|
|
2679
|
+
--bg-element: #1e1e1e;
|
|
2680
|
+
--bg-hover: #232323;
|
|
2681
|
+
--text: #eeeeee;
|
|
2682
|
+
--muted: #808080;
|
|
2683
|
+
--primary: #fab283;
|
|
2684
|
+
--secondary: #5c9cf5;
|
|
2685
|
+
--accent: #9d7cd8;
|
|
2686
|
+
--success: #7fd88f;
|
|
2687
|
+
--warning: #f5a742;
|
|
2688
|
+
--error: #e06c75;
|
|
2689
|
+
--info: #56b6c2;
|
|
2690
|
+
--border: rgba(255, 255, 255, 0.10);
|
|
2691
|
+
--border-strong: rgba(255, 255, 255, 0.20);
|
|
2692
|
+
--radius: 5px;
|
|
2693
|
+
--font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
2694
|
+
}
|
|
2695
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2696
|
+
html, body { height: 100%; }
|
|
2697
|
+
body {
|
|
2698
|
+
background: var(--bg);
|
|
2699
|
+
color: var(--text);
|
|
2700
|
+
font-family: var(--font);
|
|
2701
|
+
font-size: 14px;
|
|
2702
|
+
line-height: 1.55;
|
|
2703
|
+
overflow-x: hidden;
|
|
2704
|
+
}
|
|
2705
|
+
::selection { background: #f2f7c8; color: #131010; }
|
|
2706
|
+
#bg {
|
|
2707
|
+
position: fixed;
|
|
2708
|
+
inset: 0;
|
|
2709
|
+
width: 100%;
|
|
2710
|
+
height: 100%;
|
|
2711
|
+
z-index: 0;
|
|
2712
|
+
opacity: 0.5;
|
|
2713
|
+
pointer-events: none;
|
|
2714
|
+
}
|
|
2715
|
+
.shell { position: relative; z-index: 1; min-height: 100%; display: flex; flex-direction: column; }
|
|
2716
|
+
header {
|
|
2717
|
+
position: sticky;
|
|
2718
|
+
top: 0;
|
|
2719
|
+
z-index: 5;
|
|
2720
|
+
display: flex;
|
|
2721
|
+
align-items: center;
|
|
2722
|
+
justify-content: space-between;
|
|
2723
|
+
height: 64px;
|
|
2724
|
+
padding: 0 2.5rem;
|
|
2725
|
+
background: rgba(10, 10, 10, 0.82);
|
|
2726
|
+
backdrop-filter: blur(10px);
|
|
2727
|
+
border-bottom: 1px solid var(--border);
|
|
2728
|
+
}
|
|
2729
|
+
.brand { font-size: 15px; }
|
|
2730
|
+
.brand .dim { color: var(--muted); }
|
|
2731
|
+
.brand .strong { color: var(--text); font-weight: 700; }
|
|
2732
|
+
.status { color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 8px; }
|
|
2733
|
+
.status .dot { color: var(--muted); }
|
|
2734
|
+
.status.running .dot { color: var(--warning); }
|
|
2735
|
+
.status.done .dot { color: var(--success); }
|
|
2736
|
+
.status.error .dot { color: var(--error); }
|
|
2737
|
+
main { flex: 1; padding: 2.5rem; max-width: 1180px; width: 100%; margin: 0 auto; }
|
|
2738
|
+
.screen { display: none; }
|
|
2739
|
+
.screen.active { display: block; }
|
|
2740
|
+
h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.01em; }
|
|
2741
|
+
.sub { color: var(--muted); margin-top: 0.4rem; margin-bottom: 2rem; }
|
|
2742
|
+
.sub .star { color: var(--primary); }
|
|
2743
|
+
.grid {
|
|
2744
|
+
display: grid;
|
|
2745
|
+
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
|
2746
|
+
gap: 1rem;
|
|
2747
|
+
padding-bottom: 3rem;
|
|
2748
|
+
}
|
|
2749
|
+
.card {
|
|
2750
|
+
position: relative;
|
|
2751
|
+
background: rgba(20, 20, 20, 0.86);
|
|
2752
|
+
border: 1px solid var(--border);
|
|
2753
|
+
border-radius: var(--radius);
|
|
2754
|
+
padding: 1.1rem 1.2rem 1rem;
|
|
2755
|
+
cursor: pointer;
|
|
2756
|
+
transition: border-color .15s ease, background .15s ease, transform .1s ease;
|
|
2757
|
+
}
|
|
2758
|
+
.card:hover { border-color: var(--border-strong); background: rgba(30, 30, 30, 0.92); }
|
|
2759
|
+
.card:active { transform: scale(0.98); }
|
|
2760
|
+
.card .cat { font-size: 11px; color: var(--muted); display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
|
|
2761
|
+
.card .cat .dot { font-size: 12px; }
|
|
2762
|
+
.card .name { font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 8px; }
|
|
2763
|
+
.badge-beta {
|
|
2764
|
+
font-size: 10px;
|
|
2765
|
+
font-weight: 700;
|
|
2766
|
+
color: var(--warning);
|
|
2767
|
+
border: 1px solid var(--warning);
|
|
2768
|
+
border-radius: 3px;
|
|
2769
|
+
padding: 1px 5px;
|
|
2770
|
+
letter-spacing: 0.04em;
|
|
2771
|
+
}
|
|
2772
|
+
.search-wrap { max-width: 420px; margin-bottom: 1.6rem; }
|
|
2773
|
+
.card .hint { font-size: 12px; color: var(--muted); margin-top: 8px; opacity: 0; transition: opacity .15s ease; }
|
|
2774
|
+
.card:hover .hint { opacity: 1; }
|
|
2775
|
+
.card.auto { border-style: dashed; }
|
|
2776
|
+
.cat-heading {
|
|
2777
|
+
grid-column: 1 / -1;
|
|
2778
|
+
margin-top: 1.2rem;
|
|
2779
|
+
color: var(--accent);
|
|
2780
|
+
font-weight: 700;
|
|
2781
|
+
font-size: 13px;
|
|
2782
|
+
}
|
|
2783
|
+
.cat-heading:first-child { margin-top: 0; }
|
|
2784
|
+
.panel {
|
|
2785
|
+
background: rgba(20, 20, 20, 0.9);
|
|
2786
|
+
border: 1px solid var(--border);
|
|
2787
|
+
border-radius: var(--radius);
|
|
2788
|
+
padding: 2rem;
|
|
2789
|
+
max-width: 640px;
|
|
2790
|
+
}
|
|
2791
|
+
.field-label { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
|
|
2792
|
+
input[type="text"] {
|
|
2793
|
+
width: 100%;
|
|
2794
|
+
background: var(--bg-element);
|
|
2795
|
+
color: var(--text);
|
|
2796
|
+
border: 1px solid var(--border);
|
|
2797
|
+
border-left: 3px solid var(--primary);
|
|
2798
|
+
border-radius: 3px;
|
|
2799
|
+
font-family: var(--font);
|
|
2800
|
+
font-size: 14px;
|
|
2801
|
+
padding: 12px 14px;
|
|
2802
|
+
outline: none;
|
|
2803
|
+
}
|
|
2804
|
+
input[type="text"]:focus { border-color: var(--border-strong); border-left-color: var(--primary); }
|
|
2805
|
+
.error-line { color: var(--error); font-size: 12px; min-height: 18px; margin-top: 8px; }
|
|
2806
|
+
.row { display: flex; align-items: center; gap: 12px; margin-top: 1.6rem; }
|
|
2807
|
+
.btn {
|
|
2808
|
+
font-family: var(--font);
|
|
2809
|
+
font-size: 13px;
|
|
2810
|
+
padding: 9px 18px;
|
|
2811
|
+
border-radius: 4px;
|
|
2812
|
+
border: none;
|
|
2813
|
+
cursor: pointer;
|
|
2814
|
+
background: var(--bg-element);
|
|
2815
|
+
color: var(--text);
|
|
2816
|
+
box-shadow: 0 0 0 1px rgba(255,255,255,.10);
|
|
2817
|
+
transition: all .2s ease;
|
|
2818
|
+
}
|
|
2819
|
+
.btn:hover { box-shadow: 0 0 0 1px rgba(255,255,255,.22); }
|
|
2820
|
+
.btn:active { transform: scale(0.98); }
|
|
2821
|
+
.btn.primary { background: #f0efef; color: #131010; font-weight: 700; box-shadow: none; }
|
|
2822
|
+
.btn.primary:hover { background: #ffffff; }
|
|
2823
|
+
.btn.ghost { background: transparent; color: var(--muted); box-shadow: none; }
|
|
2824
|
+
.btn.ghost:hover { color: var(--text); }
|
|
2825
|
+
.chip {
|
|
2826
|
+
display: inline-flex;
|
|
2827
|
+
align-items: center;
|
|
2828
|
+
gap: 8px;
|
|
2829
|
+
background: var(--bg-element);
|
|
2830
|
+
border: 1px solid var(--border);
|
|
2831
|
+
border-radius: 3px;
|
|
2832
|
+
padding: 4px 10px;
|
|
2833
|
+
font-size: 12px;
|
|
2834
|
+
color: var(--muted);
|
|
2835
|
+
margin-bottom: 1.4rem;
|
|
2836
|
+
}
|
|
2837
|
+
.chip b { color: var(--primary); font-weight: 700; }
|
|
2838
|
+
.opt {
|
|
2839
|
+
display: flex;
|
|
2840
|
+
align-items: center;
|
|
2841
|
+
gap: 12px;
|
|
2842
|
+
padding: 13px 4px;
|
|
2843
|
+
border-bottom: 1px solid var(--border);
|
|
2844
|
+
cursor: pointer;
|
|
2845
|
+
user-select: none;
|
|
2846
|
+
}
|
|
2847
|
+
.opt:last-of-type { border-bottom: none; }
|
|
2848
|
+
.opt .box {
|
|
2849
|
+
width: 18px;
|
|
2850
|
+
height: 18px;
|
|
2851
|
+
flex: none;
|
|
2852
|
+
border: 1.5px solid var(--border-strong);
|
|
2853
|
+
border-radius: 3px;
|
|
2854
|
+
display: grid;
|
|
2855
|
+
place-items: center;
|
|
2856
|
+
color: transparent;
|
|
2857
|
+
font-size: 13px;
|
|
2858
|
+
font-weight: 700;
|
|
2859
|
+
transition: all .15s ease;
|
|
2860
|
+
}
|
|
2861
|
+
.opt.on .box { background: var(--primary); border-color: var(--primary); color: #131010; }
|
|
2862
|
+
.opt .label b { display: block; font-size: 14px; font-weight: 600; }
|
|
2863
|
+
.opt .label span { font-size: 12px; color: var(--muted); }
|
|
2864
|
+
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
|
|
2865
|
+
.seg button {
|
|
2866
|
+
font-family: var(--font);
|
|
2867
|
+
font-size: 12px;
|
|
2868
|
+
background: transparent;
|
|
2869
|
+
color: var(--muted);
|
|
2870
|
+
border: none;
|
|
2871
|
+
padding: 7px 16px;
|
|
2872
|
+
cursor: pointer;
|
|
2873
|
+
}
|
|
2874
|
+
.seg button.on { background: var(--primary); color: #131010; font-weight: 700; }
|
|
2875
|
+
.export-layout { display: flex; gap: 1.25rem; align-items: stretch; min-height: 62vh; }
|
|
2876
|
+
.term {
|
|
2877
|
+
flex: 1;
|
|
2878
|
+
min-width: 0;
|
|
2879
|
+
background: rgba(20, 20, 20, 0.94);
|
|
2880
|
+
border: 1px solid var(--border);
|
|
2881
|
+
border-left: 3px solid var(--primary);
|
|
2882
|
+
border-radius: var(--radius);
|
|
2883
|
+
padding: 1rem 1.2rem;
|
|
2884
|
+
overflow-y: auto;
|
|
2885
|
+
max-height: 72vh;
|
|
2886
|
+
font-size: 12.5px;
|
|
2887
|
+
line-height: 1.7;
|
|
2888
|
+
}
|
|
2889
|
+
.term .ln { white-space: pre-wrap; word-break: break-all; }
|
|
2890
|
+
.term .t { color: var(--muted); }
|
|
2891
|
+
.term .lv-log { color: var(--primary); }
|
|
2892
|
+
.term .lv-info { color: var(--info); font-weight: 700; }
|
|
2893
|
+
.term .lv-warn { color: var(--warning); font-weight: 700; }
|
|
2894
|
+
.term .lv-ok { color: var(--success); }
|
|
2895
|
+
.term .lv-error { color: var(--error); font-weight: 700; }
|
|
2896
|
+
.term .msg { color: var(--text); }
|
|
2897
|
+
.side {
|
|
2898
|
+
width: 300px;
|
|
2899
|
+
flex: none;
|
|
2900
|
+
background: rgba(20, 20, 20, 0.94);
|
|
2901
|
+
border: 1px solid var(--border);
|
|
2902
|
+
border-radius: var(--radius);
|
|
2903
|
+
padding: 1.2rem 1.3rem;
|
|
2904
|
+
align-self: flex-start;
|
|
2905
|
+
position: sticky;
|
|
2906
|
+
top: 84px;
|
|
2907
|
+
}
|
|
2908
|
+
.side h3 { font-size: 13px; font-weight: 700; margin-bottom: 4px; }
|
|
2909
|
+
.side .sec { margin-bottom: 1.3rem; }
|
|
2910
|
+
.side .m { color: var(--muted); font-size: 12px; }
|
|
2911
|
+
.side .m.err { color: var(--error); }
|
|
2912
|
+
.side .file { color: var(--muted); font-size: 11.5px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; direction: rtl; text-align: left; }
|
|
2913
|
+
.side .sumrow { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); }
|
|
2914
|
+
.side .sumrow b { color: var(--text); font-weight: 600; }
|
|
2915
|
+
.donebar { display: none; margin-top: 1.1rem; gap: 10px; flex-wrap: wrap; }
|
|
2916
|
+
.donebar.visible { display: flex; }
|
|
2917
|
+
.toast {
|
|
2918
|
+
position: fixed;
|
|
2919
|
+
top: 76px;
|
|
2920
|
+
right: 20px;
|
|
2921
|
+
z-index: 20;
|
|
2922
|
+
background: var(--bg-panel);
|
|
2923
|
+
border-left: 3px solid var(--success);
|
|
2924
|
+
border-right: 3px solid var(--success);
|
|
2925
|
+
padding: 10px 16px;
|
|
2926
|
+
font-size: 13px;
|
|
2927
|
+
border-radius: 3px;
|
|
2928
|
+
opacity: 0;
|
|
2929
|
+
transition: opacity .2s ease;
|
|
2930
|
+
pointer-events: none;
|
|
2931
|
+
}
|
|
2932
|
+
.toast.show { opacity: 1; }
|
|
2933
|
+
footer {
|
|
2934
|
+
padding: 1.2rem 2.5rem;
|
|
2935
|
+
border-top: 1px solid var(--border);
|
|
2936
|
+
color: var(--muted);
|
|
2937
|
+
font-size: 12px;
|
|
2938
|
+
display: flex;
|
|
2939
|
+
justify-content: space-between;
|
|
2940
|
+
background: rgba(10, 10, 10, 0.82);
|
|
2941
|
+
}
|
|
2942
|
+
@media (max-width: 860px) {
|
|
2943
|
+
main { padding: 1.4rem; }
|
|
2944
|
+
.export-layout { flex-direction: column; }
|
|
2945
|
+
.side { width: 100%; position: static; }
|
|
2946
|
+
}
|
|
2947
|
+
</style>
|
|
2948
|
+
</head>
|
|
2949
|
+
<body>
|
|
2950
|
+
<canvas id="bg"></canvas>
|
|
2951
|
+
<div class="shell">
|
|
2952
|
+
<header>
|
|
2953
|
+
<div class="brand"><span class="dim">framer</span><span class="strong">export</span></div>
|
|
2954
|
+
<div class="status" id="status"><span class="dot">\u25CF</span><span id="statusText">idle</span></div>
|
|
2955
|
+
</header>
|
|
2956
|
+
<main>
|
|
2957
|
+
<section class="screen active" id="screen-gallery">
|
|
2958
|
+
<h1>Select a tool</h1>
|
|
2959
|
+
<div class="sub"><span class="star">[*]</span> 25+ platforms supported. Click one, paste the URL, export.</div>
|
|
2960
|
+
<div class="search-wrap">
|
|
2961
|
+
<input type="text" id="searchInput" placeholder="search a platform..." spellcheck="false" autocomplete="off" />
|
|
2962
|
+
</div>
|
|
2963
|
+
<div class="grid" id="gallery"></div>
|
|
2964
|
+
</section>
|
|
2965
|
+
|
|
2966
|
+
<section class="screen" id="screen-url">
|
|
2967
|
+
<div class="chip">tool <b id="urlTool"></b></div>
|
|
2968
|
+
<div class="panel">
|
|
2969
|
+
<h1>Site URL</h1>
|
|
2970
|
+
<div class="sub">The public URL of the site to export.</div>
|
|
2971
|
+
<div class="field-label">URL</div>
|
|
2972
|
+
<input type="text" id="urlInput" placeholder="https://mysite.framer.app" spellcheck="false" autocomplete="off" />
|
|
2973
|
+
<div class="error-line" id="urlError"></div>
|
|
2974
|
+
<div class="row">
|
|
2975
|
+
<button class="btn primary" id="urlNext">Continue</button>
|
|
2976
|
+
<button class="btn ghost" id="urlBack">Back</button>
|
|
2977
|
+
</div>
|
|
2978
|
+
</div>
|
|
2979
|
+
</section>
|
|
2980
|
+
|
|
2981
|
+
<section class="screen" id="screen-options">
|
|
2982
|
+
<div class="chip">tool <b id="optTool"></b> url <b id="optUrl"></b></div>
|
|
2983
|
+
<div class="panel">
|
|
2984
|
+
<h1>Options</h1>
|
|
2985
|
+
<div class="sub">Everything has a sensible default.</div>
|
|
2986
|
+
<div class="opt on" id="optPretty">
|
|
2987
|
+
<div class="box">\u2713</div>
|
|
2988
|
+
<div class="label"><b>Pretty-print JS files</b><span>Reformat downloaded scripts for readability</span></div>
|
|
2989
|
+
</div>
|
|
2990
|
+
<div class="opt" id="optSubpages">
|
|
2991
|
+
<div class="box">\u2713</div>
|
|
2992
|
+
<div class="label"><b>Export sub-pages</b><span>Crawl internal links and mirror every page</span></div>
|
|
2993
|
+
</div>
|
|
2994
|
+
<div class="opt" style="cursor: default;">
|
|
2995
|
+
<div class="label" style="flex:1;"><b>Download concurrency</b><span>Parallel downloads</span></div>
|
|
2996
|
+
<div class="seg" id="optConcurrency">
|
|
2997
|
+
<button data-v="6">6</button>
|
|
2998
|
+
<button data-v="12" class="on">12</button>
|
|
2999
|
+
<button data-v="20">20</button>
|
|
3000
|
+
</div>
|
|
3001
|
+
</div>
|
|
3002
|
+
<div style="margin-top: 1.4rem;">
|
|
3003
|
+
<div class="field-label">Output directory</div>
|
|
3004
|
+
<input type="text" id="outInput" spellcheck="false" autocomplete="off" />
|
|
3005
|
+
</div>
|
|
3006
|
+
<div class="row">
|
|
3007
|
+
<button class="btn primary" id="startBtn">Start export</button>
|
|
3008
|
+
<button class="btn ghost" id="optBack">Back</button>
|
|
3009
|
+
</div>
|
|
3010
|
+
</div>
|
|
3011
|
+
</section>
|
|
3012
|
+
|
|
3013
|
+
<section class="screen" id="screen-export">
|
|
3014
|
+
<div class="chip">tool <b id="runTool"></b> url <b id="runUrl"></b></div>
|
|
3015
|
+
<div class="export-layout">
|
|
3016
|
+
<div class="term" id="term"></div>
|
|
3017
|
+
<aside class="side">
|
|
3018
|
+
<div class="sec">
|
|
3019
|
+
<h3>Export</h3>
|
|
3020
|
+
<div class="m" id="sidePhase">starting</div>
|
|
3021
|
+
<div class="m" id="sideElapsed">0:00 elapsed</div>
|
|
3022
|
+
</div>
|
|
3023
|
+
<div class="sec">
|
|
3024
|
+
<h3>Assets</h3>
|
|
3025
|
+
<div class="m" id="sideAssets">0 downloaded</div>
|
|
3026
|
+
<div class="m err" id="sideFailed" style="display:none;"></div>
|
|
3027
|
+
<div class="m" id="sideWritten">0 files written</div>
|
|
3028
|
+
</div>
|
|
3029
|
+
<div class="sec">
|
|
3030
|
+
<h3>Files</h3>
|
|
3031
|
+
<div id="sideFiles"><div class="m">waiting for output...</div></div>
|
|
3032
|
+
</div>
|
|
3033
|
+
<div class="sec" id="sideSummarySec" style="display:none;">
|
|
3034
|
+
<h3>Summary</h3>
|
|
3035
|
+
<div id="sideSummary"></div>
|
|
3036
|
+
</div>
|
|
3037
|
+
<div class="donebar" id="donebar">
|
|
3038
|
+
<button class="btn" id="copyLogs">Copy logs</button>
|
|
3039
|
+
<button class="btn" id="copyServe">Copy serve command</button>
|
|
3040
|
+
<button class="btn ghost" id="newExport">New export</button>
|
|
3041
|
+
</div>
|
|
3042
|
+
</aside>
|
|
3043
|
+
</div>
|
|
3044
|
+
</section>
|
|
3045
|
+
</main>
|
|
3046
|
+
<footer>
|
|
3047
|
+
<span id="footLeft">framer-export</span>
|
|
3048
|
+
<span>[*] served locally \xB7 nothing leaves your machine</span>
|
|
3049
|
+
</footer>
|
|
3050
|
+
</div>
|
|
3051
|
+
<div class="toast" id="toast"></div>
|
|
3052
|
+
<script src="/pixel-blast.js"></script>
|
|
3053
|
+
<script src="/app.js"></script>
|
|
3054
|
+
</body>
|
|
3055
|
+
</html>
|
|
3056
|
+
`;
|
|
3057
|
+
}
|
|
3058
|
+
});
|
|
3059
|
+
|
|
3060
|
+
// src/ui/app-js.ts
|
|
3061
|
+
var APP_JS;
|
|
3062
|
+
var init_app_js = __esm({
|
|
3063
|
+
"src/ui/app-js.ts"() {
|
|
3064
|
+
"use strict";
|
|
3065
|
+
APP_JS = `(function () {
|
|
3066
|
+
var CAT_COLORS = {
|
|
3067
|
+
builder: 'var(--primary)',
|
|
3068
|
+
landing: 'var(--warning)',
|
|
3069
|
+
cms: 'var(--info)',
|
|
3070
|
+
course: 'var(--accent)',
|
|
3071
|
+
ecommerce: 'var(--success)',
|
|
3072
|
+
ai: 'var(--secondary)'
|
|
3073
|
+
};
|
|
3074
|
+
var state = {
|
|
3075
|
+
tool: null,
|
|
3076
|
+
toolLabel: 'Auto-detect',
|
|
3077
|
+
url: '',
|
|
3078
|
+
pretty: true,
|
|
3079
|
+
subpages: false,
|
|
3080
|
+
concurrency: 12,
|
|
3081
|
+
outDir: '',
|
|
3082
|
+
running: false,
|
|
3083
|
+
serveCommand: '',
|
|
3084
|
+
startedAt: 0
|
|
3085
|
+
};
|
|
3086
|
+
var es = null;
|
|
3087
|
+
var elapsedTimer = null;
|
|
3088
|
+
|
|
3089
|
+
function $(id) { return document.getElementById(id); }
|
|
3090
|
+
function show(name) {
|
|
3091
|
+
var screens = document.querySelectorAll('.screen');
|
|
3092
|
+
for (var i = 0; i < screens.length; i++) screens[i].classList.remove('active');
|
|
3093
|
+
$('screen-' + name).classList.add('active');
|
|
3094
|
+
window.scrollTo(0, 0);
|
|
3095
|
+
}
|
|
3096
|
+
function setStatus(kind, text) {
|
|
3097
|
+
var el = $('status');
|
|
3098
|
+
el.className = 'status ' + kind;
|
|
3099
|
+
$('statusText').textContent = text;
|
|
3100
|
+
}
|
|
3101
|
+
function toast(msg) {
|
|
3102
|
+
var el = $('toast');
|
|
3103
|
+
el.textContent = msg;
|
|
3104
|
+
el.classList.add('show');
|
|
3105
|
+
setTimeout(function () { el.classList.remove('show'); }, 1800);
|
|
3106
|
+
}
|
|
3107
|
+
function copyText(text, okMsg) {
|
|
3108
|
+
function fallback() {
|
|
3109
|
+
var ta = document.createElement('textarea');
|
|
3110
|
+
ta.value = text;
|
|
3111
|
+
document.body.appendChild(ta);
|
|
3112
|
+
ta.select();
|
|
3113
|
+
try { document.execCommand('copy'); toast(okMsg); } catch (e) { toast('copy failed'); }
|
|
3114
|
+
document.body.removeChild(ta);
|
|
3115
|
+
}
|
|
3116
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
3117
|
+
navigator.clipboard.writeText(text).then(function () { toast(okMsg); }, fallback);
|
|
3118
|
+
} else fallback();
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
if (window.PixelBlast) {
|
|
3122
|
+
window.PixelBlast.mount($('bg'), {
|
|
3123
|
+
variant: 'circle',
|
|
3124
|
+
pixelSize: 5,
|
|
3125
|
+
color: '#FAB283',
|
|
3126
|
+
patternScale: 3,
|
|
3127
|
+
patternDensity: 1.1,
|
|
3128
|
+
pixelSizeJitter: 0.4,
|
|
3129
|
+
rippleIntensityScale: 1.4,
|
|
3130
|
+
rippleSpeed: 0.4,
|
|
3131
|
+
rippleThickness: 0.12,
|
|
3132
|
+
speed: 0.5,
|
|
3133
|
+
edgeFade: 0.18
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
var galleryData = null;
|
|
3138
|
+
|
|
3139
|
+
function renderGallery(filter) {
|
|
3140
|
+
if (!galleryData) return;
|
|
3141
|
+
var q = (filter || '').trim().toLowerCase();
|
|
3142
|
+
var grid = $('gallery');
|
|
3143
|
+
grid.innerHTML = '';
|
|
3144
|
+
if (!q) {
|
|
3145
|
+
var auto = document.createElement('div');
|
|
3146
|
+
auto.className = 'card auto';
|
|
3147
|
+
auto.innerHTML = '<div class="cat"><span class="dot" style="color: var(--muted)">\u25CF</span>smart</div>' +
|
|
3148
|
+
'<div class="name">Auto-detect</div>' +
|
|
3149
|
+
'<div class="hint">figure out the platform from the URL</div>';
|
|
3150
|
+
auto.onclick = function () { pickTool(null, 'Auto-detect'); };
|
|
3151
|
+
grid.appendChild(auto);
|
|
3152
|
+
}
|
|
3153
|
+
var any = false;
|
|
3154
|
+
galleryData.categories.forEach(function (cat) {
|
|
3155
|
+
var matches = cat.platforms.filter(function (p) {
|
|
3156
|
+
return !q || p.displayName.toLowerCase().indexOf(q) !== -1 || cat.label.toLowerCase().indexOf(q) !== -1;
|
|
3157
|
+
});
|
|
3158
|
+
if (!matches.length) return;
|
|
3159
|
+
any = true;
|
|
3160
|
+
var heading = document.createElement('div');
|
|
3161
|
+
heading.className = 'cat-heading';
|
|
3162
|
+
heading.textContent = cat.label;
|
|
3163
|
+
grid.appendChild(heading);
|
|
3164
|
+
matches.forEach(function (p) {
|
|
3165
|
+
var card = document.createElement('div');
|
|
3166
|
+
card.className = 'card';
|
|
3167
|
+
var color = CAT_COLORS[cat.id] || 'var(--muted)';
|
|
3168
|
+
var badge = p.beta ? '<span class="badge-beta">beta</span>' : '';
|
|
3169
|
+
card.innerHTML = '<div class="cat"><span class="dot" style="color: ' + color + '">\u25CF</span>' + cat.label + '</div>' +
|
|
3170
|
+
'<div class="name">' + p.displayName + badge + '</div>' +
|
|
3171
|
+
'<div class="hint">export a ' + p.displayName + ' site</div>';
|
|
3172
|
+
card.onclick = function () { pickTool(p.name, p.displayName); };
|
|
3173
|
+
grid.appendChild(card);
|
|
3174
|
+
});
|
|
3175
|
+
});
|
|
3176
|
+
if (!any && q) {
|
|
3177
|
+
var empty = document.createElement('div');
|
|
3178
|
+
empty.className = 'cat-heading';
|
|
3179
|
+
empty.style.color = 'var(--muted)';
|
|
3180
|
+
empty.textContent = 'No results found';
|
|
3181
|
+
grid.appendChild(empty);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
function loadGallery() {
|
|
3186
|
+
fetch('/api/platforms').then(function (r) { return r.json(); }).then(function (data) {
|
|
3187
|
+
galleryData = data;
|
|
3188
|
+
renderGallery('');
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
$('searchInput').addEventListener('input', function () {
|
|
3193
|
+
renderGallery(this.value);
|
|
3194
|
+
});
|
|
3195
|
+
|
|
3196
|
+
function pickTool(name, label) {
|
|
3197
|
+
state.tool = name;
|
|
3198
|
+
state.toolLabel = label;
|
|
3199
|
+
$('urlTool').textContent = label;
|
|
3200
|
+
$('urlError').textContent = '';
|
|
3201
|
+
show('url');
|
|
3202
|
+
$('urlInput').focus();
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
function validUrl(value) {
|
|
3206
|
+
try { new URL(value); return true; } catch (e) { return false; }
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
$('urlBack').onclick = function () { show('gallery'); };
|
|
3210
|
+
$('urlNext').onclick = function () {
|
|
3211
|
+
var value = $('urlInput').value.trim();
|
|
3212
|
+
if (!validUrl(value)) {
|
|
3213
|
+
$('urlError').textContent = 'Invalid URL. Enter a valid URL (https://...)';
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
state.url = value;
|
|
3217
|
+
$('optTool').textContent = state.toolLabel;
|
|
3218
|
+
$('optUrl').textContent = value;
|
|
3219
|
+
var q = '/api/derive?url=' + encodeURIComponent(value) + (state.tool ? '&platform=' + encodeURIComponent(state.tool) : '');
|
|
3220
|
+
fetch(q).then(function (r) { return r.json(); }).then(function (d) {
|
|
3221
|
+
$('outInput').value = './' + d.name;
|
|
3222
|
+
state.outDir = './' + d.name;
|
|
3223
|
+
});
|
|
3224
|
+
show('options');
|
|
3225
|
+
};
|
|
3226
|
+
$('urlInput').addEventListener('keydown', function (e) {
|
|
3227
|
+
if (e.key === 'Enter') $('urlNext').click();
|
|
3228
|
+
});
|
|
3229
|
+
|
|
3230
|
+
$('optPretty').onclick = function () {
|
|
3231
|
+
state.pretty = !state.pretty;
|
|
3232
|
+
this.classList.toggle('on', state.pretty);
|
|
3233
|
+
};
|
|
3234
|
+
$('optSubpages').onclick = function () {
|
|
3235
|
+
state.subpages = !state.subpages;
|
|
3236
|
+
this.classList.toggle('on', state.subpages);
|
|
3237
|
+
};
|
|
3238
|
+
var segButtons = $('optConcurrency').querySelectorAll('button');
|
|
3239
|
+
for (var i = 0; i < segButtons.length; i++) {
|
|
3240
|
+
segButtons[i].onclick = function () {
|
|
3241
|
+
for (var j = 0; j < segButtons.length; j++) segButtons[j].classList.remove('on');
|
|
3242
|
+
this.classList.add('on');
|
|
3243
|
+
state.concurrency = parseInt(this.getAttribute('data-v'), 10);
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
$('optBack').onclick = function () { show('url'); };
|
|
3247
|
+
|
|
3248
|
+
$('startBtn').onclick = function () {
|
|
3249
|
+
if (state.running) return;
|
|
3250
|
+
state.outDir = $('outInput').value.trim() || state.outDir;
|
|
3251
|
+
state.running = true;
|
|
3252
|
+
state.startedAt = Date.now();
|
|
3253
|
+
$('runTool').textContent = state.toolLabel;
|
|
3254
|
+
$('runUrl').textContent = state.url;
|
|
3255
|
+
$('term').innerHTML = '';
|
|
3256
|
+
$('donebar').classList.remove('visible');
|
|
3257
|
+
$('sideSummarySec').style.display = 'none';
|
|
3258
|
+
setStatus('running', 'exporting');
|
|
3259
|
+
show('export');
|
|
3260
|
+
startElapsed();
|
|
3261
|
+
connectEvents();
|
|
3262
|
+
fetch('/api/export', {
|
|
3263
|
+
method: 'POST',
|
|
3264
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3265
|
+
body: JSON.stringify({
|
|
3266
|
+
url: state.url,
|
|
3267
|
+
platform: state.tool,
|
|
3268
|
+
outDir: state.outDir,
|
|
3269
|
+
subpages: state.subpages,
|
|
3270
|
+
prettyPrint: state.pretty,
|
|
3271
|
+
concurrency: state.concurrency
|
|
3272
|
+
})
|
|
3273
|
+
}).then(function (r) {
|
|
3274
|
+
if (!r.ok) return r.json().then(function (d) { throw new Error(d.error || 'export failed'); });
|
|
3275
|
+
}).catch(function (e) {
|
|
3276
|
+
appendLog({ time: '', level: 'error', message: e.message });
|
|
3277
|
+
setStatus('error', 'error');
|
|
3278
|
+
state.running = false;
|
|
3279
|
+
stopElapsed();
|
|
3280
|
+
});
|
|
3281
|
+
};
|
|
3282
|
+
|
|
3283
|
+
function startElapsed() {
|
|
3284
|
+
stopElapsed();
|
|
3285
|
+
elapsedTimer = setInterval(function () {
|
|
3286
|
+
var s = Math.floor((Date.now() - state.startedAt) / 1000);
|
|
3287
|
+
var m = Math.floor(s / 60);
|
|
3288
|
+
var r = s % 60;
|
|
3289
|
+
$('sideElapsed').textContent = m + ':' + (r < 10 ? '0' : '') + r + ' elapsed';
|
|
3290
|
+
}, 1000);
|
|
3291
|
+
}
|
|
3292
|
+
function stopElapsed() {
|
|
3293
|
+
if (elapsedTimer) clearInterval(elapsedTimer);
|
|
3294
|
+
elapsedTimer = null;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
function appendLog(rec) {
|
|
3298
|
+
var term = $('term');
|
|
3299
|
+
var nearBottom = term.scrollTop + term.clientHeight >= term.scrollHeight - 60;
|
|
3300
|
+
var line = document.createElement('div');
|
|
3301
|
+
line.className = 'ln';
|
|
3302
|
+
var t = rec.time ? '[' + rec.time + '] ' : '';
|
|
3303
|
+
line.innerHTML = '<span class="t">' + t + '</span>' +
|
|
3304
|
+
'<span class="lv-' + rec.level + '">[' + rec.level + ']</span> ' +
|
|
3305
|
+
'<span class="msg"></span>';
|
|
3306
|
+
line.querySelector('.msg').textContent = rec.message;
|
|
3307
|
+
term.appendChild(line);
|
|
3308
|
+
while (term.childNodes.length > 3000) term.removeChild(term.firstChild);
|
|
3309
|
+
if (nearBottom) term.scrollTop = term.scrollHeight;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
function renderProgress(p) {
|
|
3313
|
+
$('sidePhase').textContent = p.phase || 'starting';
|
|
3314
|
+
$('sideAssets').textContent = p.downloaded + (p.totalAssets ? '/' + p.totalAssets : '') + ' downloaded';
|
|
3315
|
+
if (p.failed > 0) {
|
|
3316
|
+
$('sideFailed').style.display = 'block';
|
|
3317
|
+
$('sideFailed').textContent = p.failed + ' failed';
|
|
3318
|
+
} else {
|
|
3319
|
+
$('sideFailed').style.display = 'none';
|
|
3320
|
+
}
|
|
3321
|
+
var written = p.written + ' files written';
|
|
3322
|
+
if (p.subpages > 0) written += ' \xB7 ' + p.subpages + ' sub-pages';
|
|
3323
|
+
$('sideWritten').textContent = written;
|
|
3324
|
+
var files = $('sideFiles');
|
|
3325
|
+
files.innerHTML = '';
|
|
3326
|
+
if (!p.recentFiles || p.recentFiles.length === 0) {
|
|
3327
|
+
files.innerHTML = '<div class="m">waiting for output...</div>';
|
|
3328
|
+
} else {
|
|
3329
|
+
p.recentFiles.forEach(function (f) {
|
|
3330
|
+
var d = document.createElement('div');
|
|
3331
|
+
d.className = 'file';
|
|
3332
|
+
d.textContent = f;
|
|
3333
|
+
files.appendChild(d);
|
|
3334
|
+
});
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
function renderSummary(entries) {
|
|
3339
|
+
var el = $('sideSummary');
|
|
3340
|
+
el.innerHTML = '';
|
|
3341
|
+
entries.forEach(function (e) {
|
|
3342
|
+
if (!e.count) return;
|
|
3343
|
+
var row = document.createElement('div');
|
|
3344
|
+
row.className = 'sumrow';
|
|
3345
|
+
row.innerHTML = '<span>' + e.dir + '</span><b>' + e.count + ' ' + e.kind + '</b>';
|
|
3346
|
+
el.appendChild(row);
|
|
3347
|
+
});
|
|
3348
|
+
$('sideSummarySec').style.display = 'block';
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
function connectEvents() {
|
|
3352
|
+
if (es) return;
|
|
3353
|
+
es = new EventSource('/api/events');
|
|
3354
|
+
es.addEventListener('log', function (e) { appendLog(JSON.parse(e.data)); });
|
|
3355
|
+
es.addEventListener('progress', function (e) { renderProgress(JSON.parse(e.data)); });
|
|
3356
|
+
es.addEventListener('status', function (e) {
|
|
3357
|
+
var d = JSON.parse(e.data);
|
|
3358
|
+
if (d.state === 'done') {
|
|
3359
|
+
state.running = false;
|
|
3360
|
+
state.serveCommand = d.serveCommand || '';
|
|
3361
|
+
setStatus('done', 'done');
|
|
3362
|
+
stopElapsed();
|
|
3363
|
+
if (d.summary) renderSummary(d.summary);
|
|
3364
|
+
$('donebar').classList.add('visible');
|
|
3365
|
+
$('footLeft').textContent = d.outDir || 'framer-export';
|
|
3366
|
+
} else if (d.state === 'error') {
|
|
3367
|
+
state.running = false;
|
|
3368
|
+
setStatus('error', 'error');
|
|
3369
|
+
stopElapsed();
|
|
3370
|
+
appendLog({ time: '', level: 'error', message: d.error || 'export failed' });
|
|
3371
|
+
$('donebar').classList.add('visible');
|
|
3372
|
+
} else if (d.state === 'running') {
|
|
3373
|
+
setStatus('running', 'exporting');
|
|
3374
|
+
}
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
$('copyLogs').onclick = function () {
|
|
3379
|
+
fetch('/api/log').then(function (r) { return r.text(); }).then(function (text) {
|
|
3380
|
+
copyText(text, 'logs copied');
|
|
3381
|
+
});
|
|
3382
|
+
};
|
|
3383
|
+
$('copyServe').onclick = function () {
|
|
3384
|
+
copyText(state.serveCommand || 'node serve.js', 'serve command copied');
|
|
3385
|
+
};
|
|
3386
|
+
$('newExport').onclick = function () {
|
|
3387
|
+
setStatus('idle', 'idle');
|
|
3388
|
+
show('gallery');
|
|
3389
|
+
};
|
|
3390
|
+
|
|
3391
|
+
loadGallery();
|
|
3392
|
+
})();
|
|
3393
|
+
`;
|
|
3394
|
+
}
|
|
3395
|
+
});
|
|
3396
|
+
|
|
3397
|
+
// src/ui/pixel-blast.ts
|
|
3398
|
+
var FRAGMENT_LINES, VERTEX_LINES, CLIENT_LINES, PIXEL_BLAST_JS;
|
|
3399
|
+
var init_pixel_blast = __esm({
|
|
3400
|
+
"src/ui/pixel-blast.ts"() {
|
|
3401
|
+
"use strict";
|
|
3402
|
+
FRAGMENT_LINES = [
|
|
3403
|
+
"#version 300 es",
|
|
3404
|
+
"precision highp float;",
|
|
3405
|
+
"uniform vec3 uColor;",
|
|
3406
|
+
"uniform vec2 uResolution;",
|
|
3407
|
+
"uniform float uTime;",
|
|
3408
|
+
"uniform float uPixelSize;",
|
|
3409
|
+
"uniform float uScale;",
|
|
3410
|
+
"uniform float uDensity;",
|
|
3411
|
+
"uniform float uPixelJitter;",
|
|
3412
|
+
"uniform int uEnableRipples;",
|
|
3413
|
+
"uniform float uRippleSpeed;",
|
|
3414
|
+
"uniform float uRippleThickness;",
|
|
3415
|
+
"uniform float uRippleIntensity;",
|
|
3416
|
+
"uniform float uEdgeFade;",
|
|
3417
|
+
"uniform int uShapeType;",
|
|
3418
|
+
"const int SHAPE_SQUARE = 0;",
|
|
3419
|
+
"const int SHAPE_CIRCLE = 1;",
|
|
3420
|
+
"const int SHAPE_TRIANGLE = 2;",
|
|
3421
|
+
"const int SHAPE_DIAMOND = 3;",
|
|
3422
|
+
"const int MAX_CLICKS = 10;",
|
|
3423
|
+
"uniform vec2 uClickPos [MAX_CLICKS];",
|
|
3424
|
+
"uniform float uClickTimes[MAX_CLICKS];",
|
|
3425
|
+
"out vec4 fragColor;",
|
|
3426
|
+
"float Bayer2(vec2 a) { a = floor(a); return fract(a.x / 2. + a.y * a.y * .75); }",
|
|
3427
|
+
"#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))",
|
|
3428
|
+
"#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))",
|
|
3429
|
+
"#define FBM_OCTAVES 5",
|
|
3430
|
+
"#define FBM_LACUNARITY 1.25",
|
|
3431
|
+
"#define FBM_GAIN 1.0",
|
|
3432
|
+
"float hash11(float n){ return fract(sin(n)*43758.5453); }",
|
|
3433
|
+
"float vnoise(vec3 p){",
|
|
3434
|
+
" vec3 ip = floor(p);",
|
|
3435
|
+
" vec3 fp = fract(p);",
|
|
3436
|
+
" float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));",
|
|
3437
|
+
" float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));",
|
|
3438
|
+
" float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));",
|
|
3439
|
+
" float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));",
|
|
3440
|
+
" float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));",
|
|
3441
|
+
" float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));",
|
|
3442
|
+
" float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));",
|
|
3443
|
+
" float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));",
|
|
3444
|
+
" vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);",
|
|
3445
|
+
" float x00 = mix(n000, n100, w.x);",
|
|
3446
|
+
" float x10 = mix(n010, n110, w.x);",
|
|
3447
|
+
" float x01 = mix(n001, n101, w.x);",
|
|
3448
|
+
" float x11 = mix(n011, n111, w.x);",
|
|
3449
|
+
" float y0 = mix(x00, x10, w.y);",
|
|
3450
|
+
" float y1 = mix(x01, x11, w.y);",
|
|
3451
|
+
" return mix(y0, y1, w.z) * 2.0 - 1.0;",
|
|
3452
|
+
"}",
|
|
3453
|
+
"float fbm2(vec2 uv, float t){",
|
|
3454
|
+
" vec3 p = vec3(uv * uScale, t);",
|
|
3455
|
+
" float amp = 1.0;",
|
|
3456
|
+
" float freq = 1.0;",
|
|
3457
|
+
" float sum = 1.0;",
|
|
3458
|
+
" for (int i = 0; i < FBM_OCTAVES; ++i){",
|
|
3459
|
+
" sum += amp * vnoise(p * freq);",
|
|
3460
|
+
" freq *= FBM_LACUNARITY;",
|
|
3461
|
+
" amp *= FBM_GAIN;",
|
|
3462
|
+
" }",
|
|
3463
|
+
" return sum * 0.5 + 0.5;",
|
|
3464
|
+
"}",
|
|
3465
|
+
"float maskCircle(vec2 p, float cov){",
|
|
3466
|
+
" float r = sqrt(cov) * .25;",
|
|
3467
|
+
" float d = length(p - 0.5) - r;",
|
|
3468
|
+
" float aa = 0.5 * fwidth(d);",
|
|
3469
|
+
" return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));",
|
|
3470
|
+
"}",
|
|
3471
|
+
"float maskTriangle(vec2 p, vec2 id, float cov){",
|
|
3472
|
+
" bool flip = mod(id.x + id.y, 2.0) > 0.5;",
|
|
3473
|
+
" if (flip) p.x = 1.0 - p.x;",
|
|
3474
|
+
" float r = sqrt(cov);",
|
|
3475
|
+
" float d = p.y - r*(1.0 - p.x);",
|
|
3476
|
+
" float aa = fwidth(d);",
|
|
3477
|
+
" return cov * clamp(0.5 - d/aa, 0.0, 1.0);",
|
|
3478
|
+
"}",
|
|
3479
|
+
"float maskDiamond(vec2 p, float cov){",
|
|
3480
|
+
" float r = sqrt(cov) * 0.564;",
|
|
3481
|
+
" return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);",
|
|
3482
|
+
"}",
|
|
3483
|
+
"void main(){",
|
|
3484
|
+
" float pixelSize = uPixelSize;",
|
|
3485
|
+
" vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;",
|
|
3486
|
+
" float aspectRatio = uResolution.x / uResolution.y;",
|
|
3487
|
+
" vec2 pixelId = floor(fragCoord / pixelSize);",
|
|
3488
|
+
" vec2 pixelUV = fract(fragCoord / pixelSize);",
|
|
3489
|
+
" float cellPixelSize = 8.0 * pixelSize;",
|
|
3490
|
+
" vec2 cellId = floor(fragCoord / cellPixelSize);",
|
|
3491
|
+
" vec2 cellCoord = cellId * cellPixelSize;",
|
|
3492
|
+
" vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);",
|
|
3493
|
+
" float base = fbm2(uv, uTime * 0.05);",
|
|
3494
|
+
" base = base * 0.5 - 0.65;",
|
|
3495
|
+
" float feed = base + (uDensity - 0.5) * 0.3;",
|
|
3496
|
+
" float speed = uRippleSpeed;",
|
|
3497
|
+
" float thickness = uRippleThickness;",
|
|
3498
|
+
" const float dampT = 1.0;",
|
|
3499
|
+
" const float dampR = 10.0;",
|
|
3500
|
+
" if (uEnableRipples == 1) {",
|
|
3501
|
+
" for (int i = 0; i < MAX_CLICKS; ++i){",
|
|
3502
|
+
" vec2 pos = uClickPos[i];",
|
|
3503
|
+
" if (pos.x < 0.0) continue;",
|
|
3504
|
+
" vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);",
|
|
3505
|
+
" float t = max(uTime - uClickTimes[i], 0.0);",
|
|
3506
|
+
" float r = distance(uv, cuv);",
|
|
3507
|
+
" float waveR = speed * t;",
|
|
3508
|
+
" float ring = exp(-pow((r - waveR) / thickness, 2.0));",
|
|
3509
|
+
" float atten = exp(-dampT * t) * exp(-dampR * r);",
|
|
3510
|
+
" feed = max(feed, ring * atten * uRippleIntensity);",
|
|
3511
|
+
" }",
|
|
3512
|
+
" }",
|
|
3513
|
+
" float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;",
|
|
3514
|
+
" float bw = step(0.5, feed + bayer);",
|
|
3515
|
+
" float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);",
|
|
3516
|
+
" float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;",
|
|
3517
|
+
" float coverage = bw * jitterScale;",
|
|
3518
|
+
" float M;",
|
|
3519
|
+
" if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);",
|
|
3520
|
+
" else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);",
|
|
3521
|
+
" else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);",
|
|
3522
|
+
" else M = coverage;",
|
|
3523
|
+
" if (uEdgeFade > 0.0) {",
|
|
3524
|
+
" vec2 norm = gl_FragCoord.xy / uResolution;",
|
|
3525
|
+
" float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));",
|
|
3526
|
+
" float fade = smoothstep(0.0, uEdgeFade, edge);",
|
|
3527
|
+
" M *= fade;",
|
|
3528
|
+
" }",
|
|
3529
|
+
" vec3 color = uColor;",
|
|
3530
|
+
" vec3 srgbColor = mix(",
|
|
3531
|
+
" color * 12.92,",
|
|
3532
|
+
" 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,",
|
|
3533
|
+
" step(0.0031308, color)",
|
|
3534
|
+
" );",
|
|
3535
|
+
" fragColor = vec4(srgbColor, M);",
|
|
3536
|
+
"}"
|
|
3537
|
+
];
|
|
3538
|
+
VERTEX_LINES = [
|
|
3539
|
+
"#version 300 es",
|
|
3540
|
+
"in vec2 position;",
|
|
3541
|
+
"void main() {",
|
|
3542
|
+
" gl_Position = vec4(position, 0.0, 1.0);",
|
|
3543
|
+
"}"
|
|
3544
|
+
];
|
|
3545
|
+
CLIENT_LINES = [
|
|
3546
|
+
"(function () {",
|
|
3547
|
+
" var SHAPES = { square: 0, circle: 1, triangle: 2, diamond: 3 };",
|
|
3548
|
+
" var MAX_CLICKS = 10;",
|
|
3549
|
+
" function hexToRgb(hex) {",
|
|
3550
|
+
" var value = hex.replace('#', '');",
|
|
3551
|
+
" var n = parseInt(value, 16);",
|
|
3552
|
+
" return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];",
|
|
3553
|
+
" }",
|
|
3554
|
+
" function compile(gl, type, source) {",
|
|
3555
|
+
" var shader = gl.createShader(type);",
|
|
3556
|
+
" gl.shaderSource(shader, source);",
|
|
3557
|
+
" gl.compileShader(shader);",
|
|
3558
|
+
" if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {",
|
|
3559
|
+
' throw new Error(gl.getShaderInfoLog(shader) || "shader compile failed");',
|
|
3560
|
+
" }",
|
|
3561
|
+
" return shader;",
|
|
3562
|
+
" }",
|
|
3563
|
+
" function mount(canvas, opts) {",
|
|
3564
|
+
" opts = opts || {};",
|
|
3565
|
+
' var gl = canvas.getContext("webgl2", { alpha: true, antialias: false });',
|
|
3566
|
+
" if (!gl) return null;",
|
|
3567
|
+
" var program = gl.createProgram();",
|
|
3568
|
+
" gl.attachShader(program, compile(gl, gl.VERTEX_SHADER, window.__PB_VERT__));",
|
|
3569
|
+
" gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, window.__PB_FRAG__));",
|
|
3570
|
+
" gl.linkProgram(program);",
|
|
3571
|
+
" if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {",
|
|
3572
|
+
' throw new Error(gl.getProgramInfoLog(program) || "link failed");',
|
|
3573
|
+
" }",
|
|
3574
|
+
" gl.useProgram(program);",
|
|
3575
|
+
" var buffer = gl.createBuffer();",
|
|
3576
|
+
" gl.bindBuffer(gl.ARRAY_BUFFER, buffer);",
|
|
3577
|
+
" gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);",
|
|
3578
|
+
' var loc = gl.getAttribLocation(program, "position");',
|
|
3579
|
+
" gl.enableVertexAttribArray(loc);",
|
|
3580
|
+
" gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);",
|
|
3581
|
+
" gl.enable(gl.BLEND);",
|
|
3582
|
+
" gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);",
|
|
3583
|
+
" var U = {};",
|
|
3584
|
+
' ["uColor","uResolution","uTime","uPixelSize","uScale","uDensity","uPixelJitter","uEnableRipples","uRippleSpeed","uRippleThickness","uRippleIntensity","uEdgeFade","uShapeType","uClickPos","uClickTimes"].forEach(function (name) {',
|
|
3585
|
+
" U[name] = gl.getUniformLocation(program, name);",
|
|
3586
|
+
" });",
|
|
3587
|
+
' var color = hexToRgb(opts.color || "#FAB283");',
|
|
3588
|
+
" var pixelSize = opts.pixelSize || 5;",
|
|
3589
|
+
" var dpr = Math.min(window.devicePixelRatio || 1, 2);",
|
|
3590
|
+
" var clicks = new Float32Array(MAX_CLICKS * 2).fill(-1);",
|
|
3591
|
+
" var clickTimes = new Float32Array(MAX_CLICKS);",
|
|
3592
|
+
" var clickIx = 0;",
|
|
3593
|
+
" var timeOffset = Math.random() * 1000;",
|
|
3594
|
+
" var speed = opts.speed || 0.5;",
|
|
3595
|
+
" var start = performance.now();",
|
|
3596
|
+
" function resize() {",
|
|
3597
|
+
" var w = canvas.clientWidth || window.innerWidth;",
|
|
3598
|
+
" var h = canvas.clientHeight || window.innerHeight;",
|
|
3599
|
+
" canvas.width = Math.floor(w * dpr);",
|
|
3600
|
+
" canvas.height = Math.floor(h * dpr);",
|
|
3601
|
+
" gl.viewport(0, 0, canvas.width, canvas.height);",
|
|
3602
|
+
" }",
|
|
3603
|
+
" resize();",
|
|
3604
|
+
' window.addEventListener("resize", resize);',
|
|
3605
|
+
' document.addEventListener("pointerdown", function (e) {',
|
|
3606
|
+
" var rect = canvas.getBoundingClientRect();",
|
|
3607
|
+
" var fx = (e.clientX - rect.left) * (canvas.width / rect.width);",
|
|
3608
|
+
" var fy = (rect.height - (e.clientY - rect.top)) * (canvas.height / rect.height);",
|
|
3609
|
+
" clicks[clickIx * 2] = fx;",
|
|
3610
|
+
" clicks[clickIx * 2 + 1] = fy;",
|
|
3611
|
+
" clickTimes[clickIx] = timeOffset + ((performance.now() - start) / 1000) * speed;",
|
|
3612
|
+
" clickIx = (clickIx + 1) % MAX_CLICKS;",
|
|
3613
|
+
" }, { passive: true });",
|
|
3614
|
+
" function frame() {",
|
|
3615
|
+
" var t = timeOffset + ((performance.now() - start) / 1000) * speed;",
|
|
3616
|
+
" gl.uniform3f(U.uColor, color[0], color[1], color[2]);",
|
|
3617
|
+
" gl.uniform2f(U.uResolution, canvas.width, canvas.height);",
|
|
3618
|
+
" gl.uniform1f(U.uTime, t);",
|
|
3619
|
+
" gl.uniform1f(U.uPixelSize, pixelSize * dpr);",
|
|
3620
|
+
" gl.uniform1f(U.uScale, opts.patternScale || 3);",
|
|
3621
|
+
" gl.uniform1f(U.uDensity, opts.patternDensity || 1.2);",
|
|
3622
|
+
" gl.uniform1f(U.uPixelJitter, opts.pixelSizeJitter || 0.5);",
|
|
3623
|
+
" gl.uniform1i(U.uEnableRipples, opts.enableRipples === false ? 0 : 1);",
|
|
3624
|
+
" gl.uniform1f(U.uRippleSpeed, opts.rippleSpeed || 0.4);",
|
|
3625
|
+
" gl.uniform1f(U.uRippleThickness, opts.rippleThickness || 0.12);",
|
|
3626
|
+
" gl.uniform1f(U.uRippleIntensity, opts.rippleIntensityScale || 1.5);",
|
|
3627
|
+
" gl.uniform1f(U.uEdgeFade, opts.edgeFade == null ? 0.25 : opts.edgeFade);",
|
|
3628
|
+
' gl.uniform1i(U.uShapeType, SHAPES[opts.variant || "circle"] || 0);',
|
|
3629
|
+
" gl.uniform2fv(U.uClickPos, clicks);",
|
|
3630
|
+
" gl.uniform1fv(U.uClickTimes, clickTimes);",
|
|
3631
|
+
" gl.clearColor(0, 0, 0, 0);",
|
|
3632
|
+
" gl.clear(gl.COLOR_BUFFER_BIT);",
|
|
3633
|
+
" gl.drawArrays(gl.TRIANGLES, 0, 3);",
|
|
3634
|
+
" requestAnimationFrame(frame);",
|
|
3635
|
+
" }",
|
|
3636
|
+
" requestAnimationFrame(frame);",
|
|
3637
|
+
" return { gl: gl };",
|
|
3638
|
+
" }",
|
|
3639
|
+
" window.PixelBlast = { mount: mount };",
|
|
3640
|
+
"})();"
|
|
3641
|
+
];
|
|
3642
|
+
PIXEL_BLAST_JS = [
|
|
3643
|
+
"window.__PB_VERT__ = " + JSON.stringify(VERTEX_LINES.join("\n")) + ";",
|
|
3644
|
+
"window.__PB_FRAG__ = " + JSON.stringify(FRAGMENT_LINES.join("\n")) + ";",
|
|
3645
|
+
CLIENT_LINES.join("\n")
|
|
3646
|
+
].join("\n");
|
|
3647
|
+
}
|
|
3648
|
+
});
|
|
3649
|
+
|
|
2677
3650
|
// src/assets/asset-map.ts
|
|
2678
3651
|
import path from "path";
|
|
2679
3652
|
import crypto from "crypto";
|
|
2680
3653
|
import { URL as URL2 } from "url";
|
|
3654
|
+
function replaceUrl(text, url, rel) {
|
|
3655
|
+
const parts = text.split(url);
|
|
3656
|
+
if (parts.length === 1) return text;
|
|
3657
|
+
let out = parts[0];
|
|
3658
|
+
for (let i = 1; i < parts.length; i++) {
|
|
3659
|
+
out += (parts[i].startsWith("/") ? url : rel) + parts[i];
|
|
3660
|
+
}
|
|
3661
|
+
return out;
|
|
3662
|
+
}
|
|
2681
3663
|
var AssetMap;
|
|
2682
3664
|
var init_asset_map = __esm({
|
|
2683
3665
|
"src/assets/asset-map.ts"() {
|
|
@@ -2705,12 +3687,12 @@ var init_asset_map = __esm({
|
|
|
2705
3687
|
}
|
|
2706
3688
|
let filename;
|
|
2707
3689
|
const baseName = path.basename(pathname.split("?")[0]);
|
|
2708
|
-
const
|
|
3690
|
+
const hash2 = crypto.createHash("md5").update(urlStr).digest("hex").slice(0, 6);
|
|
2709
3691
|
if (baseName && baseName.length > 1 && baseName !== "/") {
|
|
2710
3692
|
const clean = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2711
|
-
filename = clean.includes(".") ? clean : `${clean}-${
|
|
3693
|
+
filename = clean.includes(".") ? clean : `${clean}-${hash2}`;
|
|
2712
3694
|
} else {
|
|
2713
|
-
filename = `asset-${
|
|
3695
|
+
filename = `asset-${hash2}${ext || ""}`;
|
|
2714
3696
|
}
|
|
2715
3697
|
if (ext === ".mjs" || ext === ".js") {
|
|
2716
3698
|
filename = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -2727,10 +3709,11 @@ var init_asset_map = __esm({
|
|
|
2727
3709
|
const sorted = [...this.entries.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
2728
3710
|
let out = text;
|
|
2729
3711
|
for (const [url, { localPath }] of sorted) {
|
|
2730
|
-
|
|
2731
|
-
|
|
3712
|
+
let rel = fromDir ? path.posix.relative(fromDir, localPath) : localPath;
|
|
3713
|
+
if (fromDir && !rel.startsWith(".")) rel = "./" + rel;
|
|
3714
|
+
out = replaceUrl(out, url, rel);
|
|
2732
3715
|
if (url.includes("&")) {
|
|
2733
|
-
out = out
|
|
3716
|
+
out = replaceUrl(out, url.replace(/&/g, "&"), rel);
|
|
2734
3717
|
}
|
|
2735
3718
|
}
|
|
2736
3719
|
return out;
|
|
@@ -2755,7 +3738,7 @@ var init_config = __esm({
|
|
|
2755
3738
|
"src/config/index.ts"() {
|
|
2756
3739
|
"use strict";
|
|
2757
3740
|
CFG = {
|
|
2758
|
-
viewport: { width: 1440, height: 900 },
|
|
3741
|
+
viewport: { width: 1440, height: 900, deviceScaleFactor: 1 },
|
|
2759
3742
|
timeout: 9e4,
|
|
2760
3743
|
scrollStep: 250,
|
|
2761
3744
|
scrollDelay: 60,
|
|
@@ -2810,7 +3793,7 @@ var init_download = __esm({
|
|
|
2810
3793
|
});
|
|
2811
3794
|
|
|
2812
3795
|
// src/logger/index.ts
|
|
2813
|
-
import
|
|
3796
|
+
import chalk6 from "chalk";
|
|
2814
3797
|
function setCooking(spinner) {
|
|
2815
3798
|
_cooking = spinner;
|
|
2816
3799
|
}
|
|
@@ -2818,69 +3801,268 @@ function trunc(text, max) {
|
|
|
2818
3801
|
if (text.length <= max) return text;
|
|
2819
3802
|
return text.slice(0, max - 2) + "..";
|
|
2820
3803
|
}
|
|
2821
|
-
function
|
|
3804
|
+
function onLog(listener) {
|
|
3805
|
+
_listeners.add(listener);
|
|
3806
|
+
return () => _listeners.delete(listener);
|
|
3807
|
+
}
|
|
3808
|
+
function getLogHistory() {
|
|
3809
|
+
return _history;
|
|
3810
|
+
}
|
|
3811
|
+
function clearLogHistory() {
|
|
3812
|
+
_history.length = 0;
|
|
3813
|
+
}
|
|
3814
|
+
function setMessageWidth(width) {
|
|
3815
|
+
_messageWidth = width ?? 120;
|
|
3816
|
+
}
|
|
3817
|
+
function setRenderHook(hook) {
|
|
3818
|
+
_renderHook = hook;
|
|
3819
|
+
}
|
|
3820
|
+
function record(level, message) {
|
|
3821
|
+
const entry = { time: T(), level, message };
|
|
3822
|
+
_history.push(entry);
|
|
3823
|
+
if (_history.length > HISTORY_LIMIT) _history.shift();
|
|
3824
|
+
for (const listener of _listeners) listener(entry);
|
|
3825
|
+
return entry;
|
|
3826
|
+
}
|
|
3827
|
+
function output(line, channel = "log") {
|
|
2822
3828
|
if (_cooking) _cooking.log(line);
|
|
3829
|
+
else if (channel === "warn") console.warn(line);
|
|
3830
|
+
else if (channel === "error") console.error(line);
|
|
2823
3831
|
else console.log(line);
|
|
3832
|
+
_renderHook?.();
|
|
2824
3833
|
}
|
|
2825
|
-
var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
3834
|
+
var _cooking, T, _history, _listeners, HISTORY_LIMIT, _messageWidth, _renderHook, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
2826
3835
|
var init_logger = __esm({
|
|
2827
3836
|
"src/logger/index.ts"() {
|
|
2828
3837
|
"use strict";
|
|
2829
3838
|
init_theme();
|
|
2830
3839
|
_cooking = null;
|
|
2831
3840
|
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
3841
|
+
_history = [];
|
|
3842
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
3843
|
+
HISTORY_LIMIT = 5e3;
|
|
3844
|
+
_messageWidth = 120;
|
|
3845
|
+
_renderHook = null;
|
|
2832
3846
|
LOG_PALETTE = [
|
|
2833
|
-
(s) =>
|
|
2834
|
-
(s) =>
|
|
2835
|
-
(s) =>
|
|
2836
|
-
(s) =>
|
|
2837
|
-
(s) =>
|
|
2838
|
-
(s) =>
|
|
3847
|
+
(s) => chalk6.hex(THEME.primary)(s),
|
|
3848
|
+
(s) => chalk6.hex(THEME.primarySoft)(s),
|
|
3849
|
+
(s) => chalk6.hex(THEME.secondary)(s),
|
|
3850
|
+
(s) => chalk6.hex(THEME.accent)(s),
|
|
3851
|
+
(s) => chalk6.hex(THEME.info)(s),
|
|
3852
|
+
(s) => chalk6.hex(THEME.text)(s)
|
|
2839
3853
|
];
|
|
2840
3854
|
_li = 0;
|
|
2841
3855
|
log = (m) => {
|
|
3856
|
+
const { time } = record("log", m);
|
|
2842
3857
|
_li++;
|
|
2843
3858
|
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
2844
3859
|
output(
|
|
2845
|
-
`${
|
|
3860
|
+
`${chalk6.hex(THEME.muted)(`[${time}]`)} ${chalk6.hex(THEME.primary)("[log]")} ${c(trunc(m, _messageWidth))}`
|
|
2846
3861
|
);
|
|
2847
3862
|
};
|
|
2848
3863
|
INFO_PALETTE = [
|
|
2849
|
-
(s) =>
|
|
2850
|
-
(s) =>
|
|
2851
|
-
(s) =>
|
|
3864
|
+
(s) => chalk6.hex(THEME.info)(s),
|
|
3865
|
+
(s) => chalk6.hex(THEME.secondary)(s),
|
|
3866
|
+
(s) => chalk6.hex(THEME.primarySoft)(s)
|
|
2852
3867
|
];
|
|
2853
3868
|
_ii = 0;
|
|
2854
3869
|
info = (m) => {
|
|
3870
|
+
const { time } = record("info", m);
|
|
2855
3871
|
_ii++;
|
|
2856
3872
|
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
2857
3873
|
output(
|
|
2858
|
-
`${
|
|
3874
|
+
`${chalk6.hex(THEME.muted)(`[${time}]`)} ${chalk6.hex(THEME.info).bold("[info]")} ${c(trunc(m, _messageWidth))}`
|
|
2859
3875
|
);
|
|
2860
3876
|
};
|
|
2861
3877
|
warn = (m) => {
|
|
2862
|
-
const
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
];
|
|
2866
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
2867
|
-
const line = `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
2868
|
-
if (_cooking) _cooking.log(line);
|
|
2869
|
-
else console.warn(line);
|
|
3878
|
+
const { time } = record("warn", m);
|
|
3879
|
+
const line = `${chalk6.hex(THEME.muted)(`[${time}]`)} ${chalk6.hex(THEME.warning).bold("[warn]")} ${chalk6.hex(THEME.warning)(trunc(m, _messageWidth))}`;
|
|
3880
|
+
output(line, "warn");
|
|
2870
3881
|
};
|
|
2871
3882
|
success = (m) => {
|
|
2872
|
-
const
|
|
2873
|
-
(s) => chalk4.hex(THEME.success)(s),
|
|
2874
|
-
(s) => chalk4.hex(THEME.info)(s)
|
|
2875
|
-
];
|
|
2876
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
3883
|
+
const { time } = record("ok", m);
|
|
2877
3884
|
output(
|
|
2878
|
-
`${
|
|
3885
|
+
`${chalk6.hex(THEME.muted)(`[${time}]`)} ${chalk6.hex(THEME.success)("[ok]")} ${chalk6.hex(THEME.success)(trunc(m, _messageWidth))}`
|
|
2879
3886
|
);
|
|
2880
3887
|
};
|
|
2881
3888
|
}
|
|
2882
3889
|
});
|
|
2883
3890
|
|
|
3891
|
+
// src/exporter/progress.ts
|
|
3892
|
+
function emit() {
|
|
3893
|
+
for (const listener of listeners) listener(state);
|
|
3894
|
+
}
|
|
3895
|
+
function onProgress(listener) {
|
|
3896
|
+
listeners.add(listener);
|
|
3897
|
+
return () => listeners.delete(listener);
|
|
3898
|
+
}
|
|
3899
|
+
function getProgress() {
|
|
3900
|
+
return state;
|
|
3901
|
+
}
|
|
3902
|
+
function resetProgress() {
|
|
3903
|
+
state.phase = "";
|
|
3904
|
+
state.startedAt = Date.now();
|
|
3905
|
+
state.totalAssets = 0;
|
|
3906
|
+
state.downloaded = 0;
|
|
3907
|
+
state.failed = 0;
|
|
3908
|
+
state.written = 0;
|
|
3909
|
+
state.subpages = 0;
|
|
3910
|
+
state.recentFiles = [];
|
|
3911
|
+
emit();
|
|
3912
|
+
}
|
|
3913
|
+
function setPhase(phase) {
|
|
3914
|
+
state.phase = phase;
|
|
3915
|
+
emit();
|
|
3916
|
+
}
|
|
3917
|
+
function setTotalAssets(total) {
|
|
3918
|
+
state.totalAssets = total;
|
|
3919
|
+
emit();
|
|
3920
|
+
}
|
|
3921
|
+
function noteDownload(ok) {
|
|
3922
|
+
if (ok) state.downloaded++;
|
|
3923
|
+
else state.failed++;
|
|
3924
|
+
emit();
|
|
3925
|
+
}
|
|
3926
|
+
function noteFile(relPath) {
|
|
3927
|
+
state.written++;
|
|
3928
|
+
state.recentFiles.push(relPath.replace(/\\/g, "/"));
|
|
3929
|
+
if (state.recentFiles.length > RECENT_LIMIT) state.recentFiles.shift();
|
|
3930
|
+
emit();
|
|
3931
|
+
}
|
|
3932
|
+
function noteSubpage() {
|
|
3933
|
+
state.subpages++;
|
|
3934
|
+
emit();
|
|
3935
|
+
}
|
|
3936
|
+
var RECENT_LIMIT, state, listeners;
|
|
3937
|
+
var init_progress = __esm({
|
|
3938
|
+
"src/exporter/progress.ts"() {
|
|
3939
|
+
"use strict";
|
|
3940
|
+
RECENT_LIMIT = 10;
|
|
3941
|
+
state = {
|
|
3942
|
+
phase: "",
|
|
3943
|
+
startedAt: 0,
|
|
3944
|
+
totalAssets: 0,
|
|
3945
|
+
downloaded: 0,
|
|
3946
|
+
failed: 0,
|
|
3947
|
+
written: 0,
|
|
3948
|
+
subpages: 0,
|
|
3949
|
+
recentFiles: []
|
|
3950
|
+
};
|
|
3951
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3952
|
+
}
|
|
3953
|
+
});
|
|
3954
|
+
|
|
3955
|
+
// src/cli/sidebar.ts
|
|
3956
|
+
import chalk7 from "chalk";
|
|
3957
|
+
import { stdout as stdout3 } from "process";
|
|
3958
|
+
function leftTruncate(text, max) {
|
|
3959
|
+
if (text.length <= max) return text;
|
|
3960
|
+
return "\u2026" + text.slice(-(max - 1));
|
|
3961
|
+
}
|
|
3962
|
+
function row(content = "") {
|
|
3963
|
+
const visible = stripAnsi(content).length;
|
|
3964
|
+
const pad = Math.max(0, SIDEBAR_WIDTH - visible);
|
|
3965
|
+
return chalk7.bgHex(THEME.panel)(content + " ".repeat(pad));
|
|
3966
|
+
}
|
|
3967
|
+
function formatElapsed(startedAt) {
|
|
3968
|
+
const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
|
|
3969
|
+
const m = Math.floor(seconds / 60);
|
|
3970
|
+
const s = seconds % 60;
|
|
3971
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
3972
|
+
}
|
|
3973
|
+
var SIDEBAR_WIDTH, MIN_COLUMNS, TOP_ROW, ExportSidebar;
|
|
3974
|
+
var init_sidebar = __esm({
|
|
3975
|
+
"src/cli/sidebar.ts"() {
|
|
3976
|
+
"use strict";
|
|
3977
|
+
init_theme();
|
|
3978
|
+
init_progress();
|
|
3979
|
+
init_logger();
|
|
3980
|
+
SIDEBAR_WIDTH = 40;
|
|
3981
|
+
MIN_COLUMNS = 100;
|
|
3982
|
+
TOP_ROW = 2;
|
|
3983
|
+
ExportSidebar = class {
|
|
3984
|
+
active = false;
|
|
3985
|
+
unsubscribeProgress = null;
|
|
3986
|
+
timer = null;
|
|
3987
|
+
lastDraw = 0;
|
|
3988
|
+
height = 0;
|
|
3989
|
+
start() {
|
|
3990
|
+
const columns = stdout3.columns || 0;
|
|
3991
|
+
if (!stdout3.isTTY || columns < MIN_COLUMNS) return;
|
|
3992
|
+
this.active = true;
|
|
3993
|
+
setMessageWidth(Math.max(40, columns - SIDEBAR_WIDTH - 22));
|
|
3994
|
+
setRenderHook(() => this.draw(true));
|
|
3995
|
+
this.unsubscribeProgress = onProgress(() => this.draw(false));
|
|
3996
|
+
this.timer = setInterval(() => this.draw(true), 1e3);
|
|
3997
|
+
this.draw(true);
|
|
3998
|
+
}
|
|
3999
|
+
stop() {
|
|
4000
|
+
if (!this.active) return;
|
|
4001
|
+
this.active = false;
|
|
4002
|
+
setMessageWidth(null);
|
|
4003
|
+
setRenderHook(null);
|
|
4004
|
+
this.unsubscribeProgress?.();
|
|
4005
|
+
if (this.timer) clearInterval(this.timer);
|
|
4006
|
+
this.timer = null;
|
|
4007
|
+
this.clear();
|
|
4008
|
+
}
|
|
4009
|
+
draw(force) {
|
|
4010
|
+
if (!this.active) return;
|
|
4011
|
+
const now = Date.now();
|
|
4012
|
+
if (!force && now - this.lastDraw < 80) return;
|
|
4013
|
+
this.lastDraw = now;
|
|
4014
|
+
const columns = stdout3.columns || 0;
|
|
4015
|
+
if (columns < MIN_COLUMNS) return;
|
|
4016
|
+
const col = columns - SIDEBAR_WIDTH + 1;
|
|
4017
|
+
const p = getProgress();
|
|
4018
|
+
const lines = [];
|
|
4019
|
+
lines.push(row());
|
|
4020
|
+
lines.push(row(` ${ui.text.bold("Export")}`));
|
|
4021
|
+
lines.push(row(` ${ui.muted(leftTruncate(p.phase || "starting", SIDEBAR_WIDTH - 4))}`));
|
|
4022
|
+
lines.push(row(` ${ui.muted(formatElapsed(p.startedAt) + " elapsed")}`));
|
|
4023
|
+
lines.push(row());
|
|
4024
|
+
lines.push(row(` ${ui.text.bold("Assets")}`));
|
|
4025
|
+
lines.push(
|
|
4026
|
+
row(
|
|
4027
|
+
` ${ui.muted(`${p.downloaded}${p.totalAssets ? "/" + p.totalAssets : ""} downloaded`)}` + (p.failed > 0 ? ` ${ui.error(`${p.failed} failed`)}` : "")
|
|
4028
|
+
)
|
|
4029
|
+
);
|
|
4030
|
+
lines.push(row(` ${ui.muted(`${p.written} files written`)}`));
|
|
4031
|
+
if (p.subpages > 0) lines.push(row(` ${ui.muted(`${p.subpages} sub-pages`)}`));
|
|
4032
|
+
lines.push(row());
|
|
4033
|
+
lines.push(row(` ${ui.text.bold("Files")}`));
|
|
4034
|
+
if (p.recentFiles.length === 0) {
|
|
4035
|
+
lines.push(row(` ${ui.muted("waiting for output...")}`));
|
|
4036
|
+
} else {
|
|
4037
|
+
for (const file of p.recentFiles) {
|
|
4038
|
+
lines.push(row(` ${ui.muted(leftTruncate(file, SIDEBAR_WIDTH - 4))}`));
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
lines.push(row());
|
|
4042
|
+
this.height = Math.max(this.height, lines.length);
|
|
4043
|
+
stdout3.write("\x1B7");
|
|
4044
|
+
lines.forEach((line, index) => {
|
|
4045
|
+
stdout3.write(`\x1B[${TOP_ROW + index};${col}H${line}`);
|
|
4046
|
+
});
|
|
4047
|
+
for (let extra = lines.length; extra < this.height; extra++) {
|
|
4048
|
+
stdout3.write(`\x1B[${TOP_ROW + extra};${col}H${" ".repeat(SIDEBAR_WIDTH)}`);
|
|
4049
|
+
}
|
|
4050
|
+
stdout3.write("\x1B8");
|
|
4051
|
+
}
|
|
4052
|
+
clear() {
|
|
4053
|
+
const columns = stdout3.columns || 0;
|
|
4054
|
+
if (columns < MIN_COLUMNS) return;
|
|
4055
|
+
const col = columns - SIDEBAR_WIDTH + 1;
|
|
4056
|
+
stdout3.write("\x1B7");
|
|
4057
|
+
for (let index = 0; index < this.height; index++) {
|
|
4058
|
+
stdout3.write(`\x1B[${TOP_ROW + index};${col}H${" ".repeat(SIDEBAR_WIDTH)}`);
|
|
4059
|
+
}
|
|
4060
|
+
stdout3.write("\x1B8");
|
|
4061
|
+
}
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
});
|
|
4065
|
+
|
|
2884
4066
|
// src/exporter/anti-bot.ts
|
|
2885
4067
|
var anti_bot_exports = {};
|
|
2886
4068
|
__export(anti_bot_exports, {
|
|
@@ -2973,34 +4155,42 @@ async function launchAndCapture(exporter) {
|
|
|
2973
4155
|
});
|
|
2974
4156
|
success("Chromium launched");
|
|
2975
4157
|
exporter.page = await exporter.browser.newPage();
|
|
2976
|
-
|
|
2977
|
-
|
|
4158
|
+
const viewport = {
|
|
4159
|
+
...CFG.viewport,
|
|
4160
|
+
deviceScaleFactor: exporter.deviceScaleFactor ?? CFG.viewport.deviceScaleFactor ?? 1
|
|
4161
|
+
};
|
|
4162
|
+
await exporter.page.setViewport(viewport);
|
|
4163
|
+
log(
|
|
4164
|
+
"Viewport set to " + viewport.width + "x" + viewport.height + " at " + viewport.deviceScaleFactor + "x DPR"
|
|
4165
|
+
);
|
|
2978
4166
|
await exporter.page.setUserAgent(
|
|
2979
4167
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
2980
4168
|
);
|
|
2981
4169
|
log("User agent set to Chrome 131");
|
|
2982
|
-
const allStripDomains = [
|
|
2983
|
-
...CFG.sharedStripDomains,
|
|
2984
|
-
...exporter.platform.stripDomains
|
|
2985
|
-
];
|
|
4170
|
+
const allStripDomains = [...CFG.sharedStripDomains, ...exporter.platform.stripDomains];
|
|
2986
4171
|
log("Blocking " + allStripDomains.length + " tracking domains:");
|
|
2987
4172
|
for (const domain of allStripDomains) {
|
|
2988
4173
|
log(" - " + domain);
|
|
2989
4174
|
}
|
|
2990
4175
|
let intercepted = 0;
|
|
2991
|
-
let
|
|
4176
|
+
let trackingBlocked = 0;
|
|
4177
|
+
let platformSkipped = 0;
|
|
2992
4178
|
exporter.page.on("response", async (res) => {
|
|
2993
4179
|
const url = res.url();
|
|
2994
4180
|
if (url.startsWith("data:") || url.startsWith("blob:")) return;
|
|
2995
4181
|
try {
|
|
2996
4182
|
const host = new URL(url).hostname;
|
|
2997
4183
|
if (allStripDomains.some((d) => host.includes(d))) {
|
|
2998
|
-
|
|
4184
|
+
trackingBlocked++;
|
|
2999
4185
|
return;
|
|
3000
4186
|
}
|
|
3001
4187
|
} catch {
|
|
3002
4188
|
return;
|
|
3003
4189
|
}
|
|
4190
|
+
if (exporter.platform.skipAssetUrls?.some((re) => re.test(url))) {
|
|
4191
|
+
platformSkipped++;
|
|
4192
|
+
return;
|
|
4193
|
+
}
|
|
3004
4194
|
exporter.assets.localPathFor(url, exporter.platform);
|
|
3005
4195
|
try {
|
|
3006
4196
|
exporter.assets.buffers.set(url, await res.buffer());
|
|
@@ -3016,11 +4206,15 @@ async function launchAndCapture(exporter) {
|
|
|
3016
4206
|
timeout: CFG.timeout
|
|
3017
4207
|
});
|
|
3018
4208
|
success("Page loaded (networkidle2)");
|
|
3019
|
-
log(
|
|
4209
|
+
log(
|
|
4210
|
+
"Intercepted " + intercepted + " resources, blocked " + trackingBlocked + " tracking requests, skipped " + platformSkipped + " platform assets"
|
|
4211
|
+
);
|
|
3020
4212
|
log("Checking DOM-based platform detection...");
|
|
3021
4213
|
const domDetected = await detectByDom(exporter.page);
|
|
3022
4214
|
if (domDetected && domDetected.name !== exporter.platform.name) {
|
|
3023
|
-
log(
|
|
4215
|
+
log(
|
|
4216
|
+
"Platform refined from DOM: " + domDetected.displayName + " (override: " + exporter.platform.name + ")"
|
|
4217
|
+
);
|
|
3024
4218
|
exporter.platform = domDetected;
|
|
3025
4219
|
}
|
|
3026
4220
|
const antiBot = await detectAntiBotPage(exporter.page);
|
|
@@ -3065,13 +4259,17 @@ async function launchAndCapture(exporter) {
|
|
|
3065
4259
|
log("Scroll skipped (scrollStrategy: none)");
|
|
3066
4260
|
} else {
|
|
3067
4261
|
exporter.cooking?.update("Scrolling page...");
|
|
3068
|
-
log(
|
|
4262
|
+
log(
|
|
4263
|
+
"Starting full-page scroll (step: " + CFG.scrollStep + "px, delay: " + CFG.scrollDelay + "ms)"
|
|
4264
|
+
);
|
|
3069
4265
|
const scrollStep = CFG.scrollStep;
|
|
3070
4266
|
const scrollDelay = CFG.scrollDelay;
|
|
3071
4267
|
const pageHeight = await exporter.page.evaluate(`
|
|
3072
4268
|
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
3073
4269
|
`);
|
|
3074
|
-
log(
|
|
4270
|
+
log(
|
|
4271
|
+
"Page height: " + pageHeight + "px (" + Math.ceil(pageHeight / scrollStep) + " scroll steps)"
|
|
4272
|
+
);
|
|
3075
4273
|
const passes = scrollStrategy === "infinite" ? 3 : 1;
|
|
3076
4274
|
for (let pass = 0; pass < passes; pass++) {
|
|
3077
4275
|
await exporter.page.evaluate(`
|
|
@@ -3109,7 +4307,9 @@ async function launchAndCapture(exporter) {
|
|
|
3109
4307
|
);
|
|
3110
4308
|
if (rendered && rendered.length > 200) {
|
|
3111
4309
|
exporter.ssrHTML = rendered;
|
|
3112
|
-
log(
|
|
4310
|
+
log(
|
|
4311
|
+
"Captured rendered DOM as HTML source (" + (rendered.length / 1024).toFixed(1) + " KB)"
|
|
4312
|
+
);
|
|
3113
4313
|
}
|
|
3114
4314
|
} catch (e) {
|
|
3115
4315
|
log("Rendered DOM capture failed (keeping SSR HTML): " + e.message);
|
|
@@ -3117,11 +4317,21 @@ async function launchAndCapture(exporter) {
|
|
|
3117
4317
|
}
|
|
3118
4318
|
const totalCaptured = exporter.assets.buffers.size;
|
|
3119
4319
|
success("Captured " + totalCaptured + " network resources total");
|
|
3120
|
-
const cssCount = [...exporter.assets.entries.values()].filter(
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
const
|
|
3124
|
-
|
|
4320
|
+
const cssCount = [...exporter.assets.entries.values()].filter(
|
|
4321
|
+
(e) => e.localPath.endsWith(".css")
|
|
4322
|
+
).length;
|
|
4323
|
+
const jsCount = [...exporter.assets.entries.values()].filter(
|
|
4324
|
+
(e) => e.localPath.endsWith(".js") || e.localPath.endsWith(".mjs")
|
|
4325
|
+
).length;
|
|
4326
|
+
const imgCount = [...exporter.assets.entries.values()].filter(
|
|
4327
|
+
(e) => e.localPath.startsWith("assets/images")
|
|
4328
|
+
).length;
|
|
4329
|
+
const fontCount = [...exporter.assets.entries.values()].filter(
|
|
4330
|
+
(e) => e.localPath.startsWith("assets/fonts")
|
|
4331
|
+
).length;
|
|
4332
|
+
log(
|
|
4333
|
+
" CSS: " + cssCount + " | JS: " + jsCount + " | Images: " + imgCount + " | Fonts: " + fontCount
|
|
4334
|
+
);
|
|
3125
4335
|
}
|
|
3126
4336
|
async function closeBrowser(exporter) {
|
|
3127
4337
|
if (exporter.browser) {
|
|
@@ -3134,8 +4344,7 @@ async function captureSubpage(page, url, platform) {
|
|
|
3134
4344
|
log(" Navigating to sub-page: " + url);
|
|
3135
4345
|
await page.goto(url, { waitUntil: "networkidle2", timeout: CFG.timeout });
|
|
3136
4346
|
if (platform.needsHydrationCheck) {
|
|
3137
|
-
await page.evaluate(
|
|
3138
|
-
`new Promise(function(r) {
|
|
4347
|
+
await page.evaluate(`new Promise(function(r) {
|
|
3139
4348
|
var t = Date.now();
|
|
3140
4349
|
(function tick() {
|
|
3141
4350
|
var m = document.getElementById('main') || document.body;
|
|
@@ -3143,12 +4352,13 @@ async function captureSubpage(page, url, platform) {
|
|
|
3143
4352
|
else if (Date.now() - t > ${platform.hydrationTimeout}) r();
|
|
3144
4353
|
else setTimeout(tick, 200);
|
|
3145
4354
|
})();
|
|
3146
|
-
})`
|
|
3147
|
-
);
|
|
4355
|
+
})`);
|
|
3148
4356
|
} else {
|
|
3149
4357
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
3150
4358
|
}
|
|
3151
|
-
const html = await page.evaluate(
|
|
4359
|
+
const html = await page.evaluate(
|
|
4360
|
+
() => document.documentElement.outerHTML || document.body.innerHTML
|
|
4361
|
+
);
|
|
3152
4362
|
log(" Sub-page fetched: " + (html.length / 1024).toFixed(1) + " KB");
|
|
3153
4363
|
return html;
|
|
3154
4364
|
}
|
|
@@ -3165,13 +4375,13 @@ var init_capture = __esm({
|
|
|
3165
4375
|
// src/network/pool.ts
|
|
3166
4376
|
async function pool(tasks, n) {
|
|
3167
4377
|
let i = 0;
|
|
3168
|
-
const
|
|
4378
|
+
const run2 = async () => {
|
|
3169
4379
|
while (i < tasks.length) {
|
|
3170
4380
|
const j = i++;
|
|
3171
4381
|
await tasks[j]();
|
|
3172
4382
|
}
|
|
3173
4383
|
};
|
|
3174
|
-
await Promise.all(Array.from({ length: Math.min(n, tasks.length) }, () =>
|
|
4384
|
+
await Promise.all(Array.from({ length: Math.min(n, tasks.length) }, () => run2()));
|
|
3175
4385
|
}
|
|
3176
4386
|
var init_pool = __esm({
|
|
3177
4387
|
"src/network/pool.ts"() {
|
|
@@ -3191,6 +4401,7 @@ async function downloadAll(exporter) {
|
|
|
3191
4401
|
toDownload.push({ url, localPath });
|
|
3192
4402
|
}
|
|
3193
4403
|
const total = toDownload.length;
|
|
4404
|
+
setTotalAssets(total);
|
|
3194
4405
|
log("Starting download of " + total + " unique assets");
|
|
3195
4406
|
log("Concurrency: " + CFG.concurrency + " parallel downloads");
|
|
3196
4407
|
log("Retry policy: " + CFG.retries + " attempts, " + CFG.dlTimeout + "ms timeout");
|
|
@@ -3214,8 +4425,11 @@ async function downloadAll(exporter) {
|
|
|
3214
4425
|
await fs.writeFile(dest, data);
|
|
3215
4426
|
ok++;
|
|
3216
4427
|
}
|
|
4428
|
+
noteDownload(true);
|
|
4429
|
+
noteFile(localPath);
|
|
3217
4430
|
} catch (e) {
|
|
3218
4431
|
fail++;
|
|
4432
|
+
noteDownload(false);
|
|
3219
4433
|
if (!url.includes("framer.com/edit") && !url.includes("framerstatic.com/editorbar")) {
|
|
3220
4434
|
warn("Download failed: " + path2.basename(localPath) + " - " + e.message);
|
|
3221
4435
|
}
|
|
@@ -3230,19 +4444,77 @@ async function downloadAll(exporter) {
|
|
|
3230
4444
|
}
|
|
3231
4445
|
);
|
|
3232
4446
|
await pool(tasks, CFG.concurrency);
|
|
3233
|
-
success(
|
|
4447
|
+
success(
|
|
4448
|
+
"Downloads complete: " + ok + " succeeded, " + cached + " from cache, " + fail + " failed"
|
|
4449
|
+
);
|
|
3234
4450
|
const totalBytes = [...exporter.assets.entries.values()].length;
|
|
3235
4451
|
log("Total unique assets written to disk: " + totalBytes);
|
|
3236
4452
|
exporter.assets.buffers.clear();
|
|
3237
4453
|
log("Network buffer cache cleared");
|
|
3238
4454
|
}
|
|
4455
|
+
async function downloadLazyChunks(exporter) {
|
|
4456
|
+
const chunkDirs = exporter.platform.lazyChunkDirs;
|
|
4457
|
+
if (!chunkDirs?.length) return;
|
|
4458
|
+
const sourceOf = /* @__PURE__ */ new Map();
|
|
4459
|
+
for (const [url, { localPath }] of exporter.assets.entries) {
|
|
4460
|
+
if (!sourceOf.has(localPath)) sourceOf.set(localPath, url);
|
|
4461
|
+
}
|
|
4462
|
+
let frontier = [...sourceOf.keys()].filter(
|
|
4463
|
+
(p) => chunkDirs.some((dir) => p.startsWith(dir + "/"))
|
|
4464
|
+
);
|
|
4465
|
+
let added = 0;
|
|
4466
|
+
for (let depth = 0; depth < MAX_CHUNK_DEPTH && frontier.length > 0; depth++) {
|
|
4467
|
+
const next = [];
|
|
4468
|
+
const tasks = frontier.map((localPath) => async () => {
|
|
4469
|
+
let code;
|
|
4470
|
+
try {
|
|
4471
|
+
code = await fs.readFile(path2.join(exporter.outDir, localPath), "utf-8");
|
|
4472
|
+
} catch {
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
for (const match of code.matchAll(SIBLING_IMPORT)) {
|
|
4476
|
+
let chunkUrl;
|
|
4477
|
+
try {
|
|
4478
|
+
chunkUrl = new URL("./" + match[1], sourceOf.get(localPath)).href;
|
|
4479
|
+
} catch {
|
|
4480
|
+
continue;
|
|
4481
|
+
}
|
|
4482
|
+
if (exporter.assets.entries.has(chunkUrl)) continue;
|
|
4483
|
+
const dest = exporter.assets.localPathFor(chunkUrl, exporter.platform);
|
|
4484
|
+
if (!dest) continue;
|
|
4485
|
+
sourceOf.set(dest, chunkUrl);
|
|
4486
|
+
try {
|
|
4487
|
+
await fs.writeFile(path2.join(exporter.outDir, dest), await dlBuffer(chunkUrl));
|
|
4488
|
+
next.push(dest);
|
|
4489
|
+
added++;
|
|
4490
|
+
noteDownload(true);
|
|
4491
|
+
noteFile(dest);
|
|
4492
|
+
} catch (e) {
|
|
4493
|
+
warn("Lazy chunk failed: " + match[1] + " - " + e.message);
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
});
|
|
4497
|
+
await pool(tasks, CFG.concurrency);
|
|
4498
|
+
frontier = next;
|
|
4499
|
+
}
|
|
4500
|
+
if (frontier.length > 0) {
|
|
4501
|
+
warn(
|
|
4502
|
+
"Lazy chunk traversal stopped at depth " + MAX_CHUNK_DEPTH + " with " + frontier.length + " chunks left to inspect"
|
|
4503
|
+
);
|
|
4504
|
+
}
|
|
4505
|
+
if (added > 0) success("Lazy-loaded chunks resolved: " + added);
|
|
4506
|
+
}
|
|
4507
|
+
var SIBLING_IMPORT, MAX_CHUNK_DEPTH;
|
|
3239
4508
|
var init_download2 = __esm({
|
|
3240
4509
|
"src/exporter/download.ts"() {
|
|
3241
4510
|
"use strict";
|
|
3242
4511
|
init_config();
|
|
3243
4512
|
init_logger();
|
|
4513
|
+
init_progress();
|
|
3244
4514
|
init_download();
|
|
3245
4515
|
init_pool();
|
|
4516
|
+
SIBLING_IMPORT = /(?:import|from)\s*\(?\s*["'`]\.\/([A-Za-z0-9_.-]+\.m?js)["'`]/g;
|
|
4517
|
+
MAX_CHUNK_DEPTH = 5;
|
|
3246
4518
|
}
|
|
3247
4519
|
});
|
|
3248
4520
|
|
|
@@ -3320,13 +4592,10 @@ const server = http.createServer((req, res) => {
|
|
|
3320
4592
|
return;
|
|
3321
4593
|
}
|
|
3322
4594
|
|
|
3323
|
-
const serveFile = (pathToFile) => {
|
|
4595
|
+
const serveFile = (pathToFile, onMissing) => {
|
|
3324
4596
|
fs.readFile(pathToFile, (err, data) => {
|
|
3325
4597
|
if (err) {
|
|
3326
|
-
|
|
3327
|
-
if (url !== '/index.html' && !path.extname(url)) {
|
|
3328
|
-
return serveFile(path.join(ROOT, 'index.html'));
|
|
3329
|
-
}
|
|
4598
|
+
if (onMissing) return onMissing();
|
|
3330
4599
|
res.writeHead(404);
|
|
3331
4600
|
res.end('Not found');
|
|
3332
4601
|
return;
|
|
@@ -3342,6 +4611,15 @@ const server = http.createServer((req, res) => {
|
|
|
3342
4611
|
});
|
|
3343
4612
|
};
|
|
3344
4613
|
|
|
4614
|
+
if (url !== '/index.html' && !path.extname(url)) {
|
|
4615
|
+
const slug = url.replace(/^\\/+|\\/+$/g, '').replace(/[^a-zA-Z0-9_-]/g, '_').replace(/_+/g, '_');
|
|
4616
|
+
const serveIndex = () => serveFile(path.join(ROOT, 'index.html'));
|
|
4617
|
+
if (slug) {
|
|
4618
|
+
return serveFile(path.join(ROOT, 'subpages', slug + '.html'), serveIndex);
|
|
4619
|
+
}
|
|
4620
|
+
return serveIndex();
|
|
4621
|
+
}
|
|
4622
|
+
|
|
3345
4623
|
serveFile(filePath);
|
|
3346
4624
|
});
|
|
3347
4625
|
|
|
@@ -3535,94 +4813,134 @@ function stripSrcsetCdnUrls(html) {
|
|
|
3535
4813
|
});
|
|
3536
4814
|
return html;
|
|
3537
4815
|
}
|
|
3538
|
-
|
|
3539
|
-
exporter.
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
success("SEO meta tags optimized");
|
|
3556
|
-
}
|
|
3557
|
-
log("Stripping " + exporter.platform.stripSelectors.length + " selectors:");
|
|
3558
|
-
for (const sel of exporter.platform.stripSelectors) {
|
|
3559
|
-
const before = html.length;
|
|
3560
|
-
html = stripBySelector(html, sel);
|
|
3561
|
-
const removed = before - html.length;
|
|
3562
|
-
if (removed > 0) {
|
|
3563
|
-
log(" Stripped " + sel + " (" + removed + " chars removed)");
|
|
4816
|
+
function rewritePatterns(exporter) {
|
|
4817
|
+
return exporter.platform.rewritePatterns ?? exporter.platform.rewriteUrlPatterns ?? [];
|
|
4818
|
+
}
|
|
4819
|
+
function applyRewritePatterns(text, report) {
|
|
4820
|
+
report.patterns.forEach(({ from, to }, index) => {
|
|
4821
|
+
const pattern = new RegExp(from.source, from.flags);
|
|
4822
|
+
if (new RegExp(from.source, from.flags).test(text)) report.matched.add(index);
|
|
4823
|
+
text = text.replace(pattern, to);
|
|
4824
|
+
});
|
|
4825
|
+
return text;
|
|
4826
|
+
}
|
|
4827
|
+
function subpageRoutes(exporter) {
|
|
4828
|
+
const routes = /* @__PURE__ */ new Map();
|
|
4829
|
+
for (const [url, filename] of exporter.subpages) {
|
|
4830
|
+
try {
|
|
4831
|
+
routes.set(new URL(url).pathname.replace(/\/+$/, ""), filename);
|
|
4832
|
+
} catch {
|
|
3564
4833
|
}
|
|
3565
4834
|
}
|
|
3566
|
-
|
|
4835
|
+
return routes;
|
|
4836
|
+
}
|
|
4837
|
+
function rewriteInternalLinks(html, siteUrl, routes, fromSubpages) {
|
|
4838
|
+
const source = new URL(siteUrl);
|
|
4839
|
+
const sourceHost = source.hostname.replace(/^www\./, "");
|
|
4840
|
+
const rootPath = source.pathname.replace(/\/+$/, "") || "/";
|
|
4841
|
+
return html.replace(
|
|
4842
|
+
/<(?!link\b)([^>]*?\bhref\s*=\s*)(["'])(.*?)\2/gi,
|
|
4843
|
+
(match, prefix, quote, href) => {
|
|
4844
|
+
if (/^(javascript:|mailto:|tel:|#|data:)/i.test(href)) return match;
|
|
4845
|
+
let target;
|
|
4846
|
+
try {
|
|
4847
|
+
target = new URL(href, siteUrl);
|
|
4848
|
+
} catch {
|
|
4849
|
+
return match;
|
|
4850
|
+
}
|
|
4851
|
+
if (target.hostname.replace(/^www\./, "") !== sourceHost) return match;
|
|
4852
|
+
const targetPath = target.pathname.replace(/\/+$/, "") || "/";
|
|
4853
|
+
if (fromSubpages && targetPath === rootPath) {
|
|
4854
|
+
return `<${prefix}${quote}../index.html${target.hash}${quote}`;
|
|
4855
|
+
}
|
|
4856
|
+
const filename = routes.get(targetPath);
|
|
4857
|
+
if (!filename) return match;
|
|
4858
|
+
const local = (fromSubpages ? filename : "subpages/" + filename) + target.hash;
|
|
4859
|
+
return `<${prefix}${quote}${local}${quote}`;
|
|
4860
|
+
}
|
|
4861
|
+
);
|
|
4862
|
+
}
|
|
4863
|
+
function processHtml(html, exporter, pageUrl, fromDir, routes, fromSubpages, report) {
|
|
4864
|
+
html = stripIntegrityAndCors(html);
|
|
4865
|
+
html = processSEO(html, pageUrl);
|
|
4866
|
+
for (const sel of exporter.platform.stripSelectors) html = stripBySelector(html, sel);
|
|
3567
4867
|
for (const pattern of exporter.platform.stripPatterns) {
|
|
3568
|
-
const before = html.length;
|
|
3569
4868
|
html = html.replace(new RegExp(pattern.source, pattern.flags), "");
|
|
3570
|
-
const removed = before - html.length;
|
|
3571
|
-
if (removed > 0) {
|
|
3572
|
-
log(" Pattern removed " + removed + " chars");
|
|
3573
|
-
}
|
|
3574
4869
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
for (const pattern of exporter.platform.stripScripts) {
|
|
3578
|
-
const before = html.length;
|
|
3579
|
-
html = html.replace(new RegExp(pattern.source, pattern.flags), "");
|
|
3580
|
-
const removed = before - html.length;
|
|
3581
|
-
if (removed > 0) log(" Script pattern removed " + removed + " chars");
|
|
3582
|
-
}
|
|
4870
|
+
for (const pattern of exporter.platform.stripScripts || []) {
|
|
4871
|
+
html = html.replace(new RegExp(pattern.source, pattern.flags), "");
|
|
3583
4872
|
}
|
|
3584
|
-
success("Platform badges and tracking stripped");
|
|
3585
4873
|
if (exporter.platform.postCapture) {
|
|
3586
4874
|
try {
|
|
3587
|
-
const before = html.length;
|
|
3588
4875
|
html = exporter.platform.postCapture(html, exporter);
|
|
3589
|
-
log("postCapture hook applied (delta " + (html.length - before) + " chars)");
|
|
3590
4876
|
} catch (e) {
|
|
3591
|
-
warn("postCapture hook failed: " + e.message);
|
|
4877
|
+
warn("postCapture hook failed for " + pageUrl + ": " + e.message);
|
|
3592
4878
|
}
|
|
3593
4879
|
}
|
|
3594
|
-
exporter.
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
html
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
4880
|
+
html = exporter.assets.rewrite(html, fromDir);
|
|
4881
|
+
html = applyRewritePatterns(html, report);
|
|
4882
|
+
html = stripSrcsetCdnUrls(html);
|
|
4883
|
+
return rewriteInternalLinks(html, exporter.siteUrl, routes, fromSubpages);
|
|
4884
|
+
}
|
|
4885
|
+
async function buildSubpages(exporter, routes, report) {
|
|
4886
|
+
if (exporter.subpages.size === 0) return;
|
|
4887
|
+
let built = 0;
|
|
4888
|
+
for (const [pageUrl, filename] of exporter.subpages) {
|
|
4889
|
+
const filePath = path3.join(exporter.outDir, "subpages", filename);
|
|
4890
|
+
try {
|
|
4891
|
+
let html = await fs2.readFile(filePath, "utf-8");
|
|
4892
|
+
html = processHtml(html, exporter, pageUrl, "subpages", routes, true, report);
|
|
4893
|
+
await fs2.writeFile(filePath, html, "utf-8");
|
|
4894
|
+
built++;
|
|
4895
|
+
} catch (e) {
|
|
4896
|
+
warn("Sub-page post-processing skipped: " + filename + " - " + e.message);
|
|
3602
4897
|
}
|
|
3603
|
-
log("Applied " + exporter.platform.rewriteUrlPatterns.length + " custom URL rewrites");
|
|
3604
4898
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
4899
|
+
success("Sub-pages linked to local assets: " + built + "/" + exporter.subpages.size);
|
|
4900
|
+
}
|
|
4901
|
+
async function buildOutput(exporter) {
|
|
4902
|
+
exporter.cooking?.update("Stripping platform badges...");
|
|
4903
|
+
log("Starting HTML post-processing...");
|
|
4904
|
+
log("HTML size: " + (exporter.ssrHTML.length / 1024).toFixed(1) + " KB");
|
|
4905
|
+
let html = exporter.ssrHTML;
|
|
4906
|
+
if (!html) {
|
|
4907
|
+
warn("No SSR HTML available, cannot build output");
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
const routes = subpageRoutes(exporter);
|
|
4911
|
+
const report = { patterns: rewritePatterns(exporter), matched: /* @__PURE__ */ new Set() };
|
|
4912
|
+
exporter.cooking?.update("Processing exported HTML...");
|
|
4913
|
+
html = processHtml(html, exporter, exporter.siteUrl, "", routes, false, report);
|
|
4914
|
+
success("Index HTML pipeline complete");
|
|
4915
|
+
await rewriteDownloadedFiles(exporter, report);
|
|
4916
|
+
await buildSubpages(exporter, routes, report);
|
|
4917
|
+
report.patterns.forEach(({ from }, index) => {
|
|
4918
|
+
if (!report.matched.has(index)) warn("Rewrite pattern matched nothing: " + from.toString());
|
|
4919
|
+
});
|
|
3609
4920
|
success("All URLs rewritten to local paths");
|
|
3610
|
-
exporter.
|
|
3611
|
-
|
|
4921
|
+
if (exporter.prettyPrint !== false) {
|
|
4922
|
+
exporter.cooking?.update("Pretty-printing JS files...");
|
|
4923
|
+
await prettifyDownloadedJS(exporter);
|
|
4924
|
+
} else {
|
|
4925
|
+
log("Pretty-print disabled, keeping JS files as downloaded");
|
|
4926
|
+
}
|
|
3612
4927
|
exporter.cooking?.update("Writing final output...");
|
|
3613
4928
|
log("Writing index.html (" + (html.length / 1024).toFixed(1) + " KB)...");
|
|
3614
4929
|
await fs2.writeFile(path3.join(exporter.outDir, "index.html"), html);
|
|
4930
|
+
noteFile("index.html");
|
|
3615
4931
|
success("index.html written");
|
|
3616
4932
|
await fs2.writeFile(path3.join(exporter.outDir, "serve.js"), SERVE_SCRIPT);
|
|
4933
|
+
noteFile("serve.js");
|
|
3617
4934
|
log("serve.js written");
|
|
3618
4935
|
await fs2.writeFile(
|
|
3619
4936
|
path3.join(exporter.outDir, "package.json"),
|
|
3620
4937
|
JSON.stringify({ type: "module", scripts: { serve: "node serve.js" } }, null, 2) + "\n"
|
|
3621
4938
|
);
|
|
4939
|
+
noteFile("package.json");
|
|
3622
4940
|
log("package.json written for serve.js");
|
|
3623
4941
|
success("Output build complete");
|
|
3624
4942
|
}
|
|
3625
|
-
async function rewriteDownloadedFiles(exporter) {
|
|
4943
|
+
async function rewriteDownloadedFiles(exporter, report) {
|
|
3626
4944
|
const dirs = ["scripts/vendor", "scripts/modules", "styles"];
|
|
3627
4945
|
let rewritten = 0;
|
|
3628
4946
|
for (const dir of dirs) {
|
|
@@ -3641,6 +4959,7 @@ async function rewriteDownloadedFiles(exporter) {
|
|
|
3641
4959
|
let content = await fs2.readFile(filePath, "utf-8");
|
|
3642
4960
|
const before = content;
|
|
3643
4961
|
content = exporter.assets.rewrite(content, dir);
|
|
4962
|
+
content = applyRewritePatterns(content, report);
|
|
3644
4963
|
if (content !== before) {
|
|
3645
4964
|
await fs2.writeFile(filePath, content);
|
|
3646
4965
|
rewritten++;
|
|
@@ -3695,6 +5014,7 @@ var init_output = __esm({
|
|
|
3695
5014
|
"src/exporter/output.ts"() {
|
|
3696
5015
|
"use strict";
|
|
3697
5016
|
init_logger();
|
|
5017
|
+
init_progress();
|
|
3698
5018
|
init_prettify();
|
|
3699
5019
|
init_template();
|
|
3700
5020
|
}
|
|
@@ -3703,12 +5023,10 @@ var init_output = __esm({
|
|
|
3703
5023
|
// src/exporter/summary.ts
|
|
3704
5024
|
import fs3 from "fs/promises";
|
|
3705
5025
|
import path4 from "path";
|
|
3706
|
-
async function
|
|
3707
|
-
const w = maxWidth();
|
|
3708
|
-
const isSmall = w < 50;
|
|
5026
|
+
async function collectSummary(outDir) {
|
|
3709
5027
|
const count = async (d) => {
|
|
3710
5028
|
try {
|
|
3711
|
-
return (await fs3.readdir(path4.join(
|
|
5029
|
+
return (await fs3.readdir(path4.join(outDir, d))).length;
|
|
3712
5030
|
} catch {
|
|
3713
5031
|
return 0;
|
|
3714
5032
|
}
|
|
@@ -3724,6 +5042,25 @@ async function printSummary(exporter) {
|
|
|
3724
5042
|
count("data"),
|
|
3725
5043
|
count("subpages")
|
|
3726
5044
|
]);
|
|
5045
|
+
return [
|
|
5046
|
+
{ dir: "styles/", count: styles, kind: "CSS" },
|
|
5047
|
+
{ dir: "scripts/vendor/", count: vendor, kind: "JS vendor" },
|
|
5048
|
+
{ dir: "scripts/modules/", count: scripts, kind: "JS modules" },
|
|
5049
|
+
{ dir: "assets/images/", count: imgs, kind: "images" },
|
|
5050
|
+
{ dir: "assets/videos/", count: videos, kind: "videos" },
|
|
5051
|
+
{ dir: "assets/fonts/", count: fonts, kind: "fonts" },
|
|
5052
|
+
{ dir: "assets/misc/", count: misc, kind: "misc" },
|
|
5053
|
+
{ dir: "data/", count: data, kind: "data" },
|
|
5054
|
+
{ dir: "subpages/", count: subpages, kind: "pages" }
|
|
5055
|
+
];
|
|
5056
|
+
}
|
|
5057
|
+
async function printSummary(exporter) {
|
|
5058
|
+
const w = maxWidth();
|
|
5059
|
+
const isSmall = w < 50;
|
|
5060
|
+
const collected = await collectSummary(exporter.outDir);
|
|
5061
|
+
const [styles, vendor, scripts, imgs, videos, fonts, misc, data, subpages] = collected.map(
|
|
5062
|
+
(e) => e.count
|
|
5063
|
+
);
|
|
3727
5064
|
const G = ui.primary;
|
|
3728
5065
|
const G2 = ui.primarySoft;
|
|
3729
5066
|
const C2 = ui.secondary;
|
|
@@ -3798,10 +5135,10 @@ var init_summary = __esm({
|
|
|
3798
5135
|
// src/ai/prompt-assistant.ts
|
|
3799
5136
|
import fs4 from "fs/promises";
|
|
3800
5137
|
import path5 from "path";
|
|
3801
|
-
import { stdin as stdin2, stdout as
|
|
5138
|
+
import { stdin as stdin2, stdout as stdout4 } from "process";
|
|
3802
5139
|
import { spawn } from "child_process";
|
|
3803
5140
|
async function runAiPromptAssistant(exporter) {
|
|
3804
|
-
if (!stdin2.isTTY || !
|
|
5141
|
+
if (!stdin2.isTTY || !stdout4.isTTY) return;
|
|
3805
5142
|
const serveCommand = buildServeCommand(exporter);
|
|
3806
5143
|
const shouldConvert = await runExportCompletePrompt(exporter, serveCommand);
|
|
3807
5144
|
if (!shouldConvert) return;
|
|
@@ -3887,17 +5224,12 @@ async function countEntries(dir) {
|
|
|
3887
5224
|
function buttonLabel(label) {
|
|
3888
5225
|
return `${ui.border("[")} ${ui.text.bold(label)} ${ui.border("]")}`;
|
|
3889
5226
|
}
|
|
3890
|
-
function centerText2(text, width) {
|
|
3891
|
-
const visible = stripAnsi(text).length;
|
|
3892
|
-
if (visible >= width) return text;
|
|
3893
|
-
const left = Math.floor((width - visible) / 2);
|
|
3894
|
-
return " ".repeat(left) + text + " ".repeat(width - visible - left);
|
|
3895
|
-
}
|
|
3896
5227
|
function buildServeCommand(exporter) {
|
|
3897
5228
|
return `cd ${path5.basename(exporter.outDir)} && node serve.js`;
|
|
3898
5229
|
}
|
|
3899
5230
|
async function runExportCompletePrompt(exporter, serveCommand) {
|
|
3900
5231
|
let copyServeCommand = true;
|
|
5232
|
+
let copyExportLog = false;
|
|
3901
5233
|
while (true) {
|
|
3902
5234
|
const action = await select(
|
|
3903
5235
|
"Export complete",
|
|
@@ -3906,26 +5238,35 @@ async function runExportCompletePrompt(exporter, serveCommand) {
|
|
|
3906
5238
|
label: `${checkboxLabel(copyServeCommand)} Copy serve command when finishing`,
|
|
3907
5239
|
value: "toggle-copy"
|
|
3908
5240
|
},
|
|
5241
|
+
{
|
|
5242
|
+
label: `${checkboxLabel(copyExportLog)} Copy full export log when finishing`,
|
|
5243
|
+
value: "toggle-log"
|
|
5244
|
+
},
|
|
3909
5245
|
{
|
|
3910
5246
|
label: `${buttonLabel("Convert to AI code")} ${ui.error("BETA")}`,
|
|
3911
5247
|
value: "convert"
|
|
3912
5248
|
},
|
|
3913
5249
|
{ label: buttonLabel("Finish"), value: "finish" }
|
|
3914
5250
|
],
|
|
3915
|
-
|
|
5251
|
+
3,
|
|
3916
5252
|
{
|
|
3917
5253
|
headerLines: [
|
|
3918
5254
|
`Output: ${path5.basename(exporter.outDir)}`,
|
|
3919
5255
|
`Run: ${serveCommand}`,
|
|
5256
|
+
`Log: ${path5.basename(exporter.outDir)}/export.log`,
|
|
3920
5257
|
"Use Convert to open the AI conversion assistant."
|
|
3921
5258
|
],
|
|
3922
|
-
footer: "enter select
|
|
5259
|
+
footer: "enter select checkbox toggles mouse click"
|
|
3923
5260
|
}
|
|
3924
5261
|
);
|
|
3925
5262
|
if (action === "toggle-copy") {
|
|
3926
5263
|
copyServeCommand = !copyServeCommand;
|
|
3927
5264
|
continue;
|
|
3928
5265
|
}
|
|
5266
|
+
if (action === "toggle-log") {
|
|
5267
|
+
copyExportLog = !copyExportLog;
|
|
5268
|
+
continue;
|
|
5269
|
+
}
|
|
3929
5270
|
if (action === "finish") {
|
|
3930
5271
|
if (copyServeCommand) {
|
|
3931
5272
|
try {
|
|
@@ -3935,6 +5276,19 @@ async function runExportCompletePrompt(exporter, serveCommand) {
|
|
|
3935
5276
|
} catch (error) {
|
|
3936
5277
|
console.log(
|
|
3937
5278
|
` ${ui.warning("!")} ${ui.warning("Clipboard copy unavailable:")} ${ui.muted(error.message)}
|
|
5279
|
+
`
|
|
5280
|
+
);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
if (copyExportLog) {
|
|
5284
|
+
try {
|
|
5285
|
+
const logText = getLogHistory().map((r) => `[${r.time}] [${r.level}] ${r.message}`).join("\n");
|
|
5286
|
+
await copyToClipboard(logText);
|
|
5287
|
+
console.log(` ${ui.success("\u2713")} ${ui.text.bold("Export log copied")}
|
|
5288
|
+
`);
|
|
5289
|
+
} catch (error) {
|
|
5290
|
+
console.log(
|
|
5291
|
+
` ${ui.warning("!")} ${ui.warning("Clipboard copy unavailable:")} ${ui.muted(error.message)}
|
|
3938
5292
|
`
|
|
3939
5293
|
);
|
|
3940
5294
|
}
|
|
@@ -3983,10 +5337,10 @@ function printAssistantModal(title, lines) {
|
|
|
3983
5337
|
const inner = w - 4;
|
|
3984
5338
|
console.log("");
|
|
3985
5339
|
console.log(boxTop(w));
|
|
3986
|
-
console.log(boxLine(w,
|
|
5340
|
+
console.log(boxLine(w, centerText(`${ui.primary("\u25C6")} ${title}`, inner)));
|
|
3987
5341
|
console.log(boxSep(w));
|
|
3988
5342
|
for (const line of lines) {
|
|
3989
|
-
console.log(boxLine(w,
|
|
5343
|
+
console.log(boxLine(w, centerText(ui.muted(line), inner)));
|
|
3990
5344
|
}
|
|
3991
5345
|
console.log(boxBot(w));
|
|
3992
5346
|
console.log("");
|
|
@@ -4173,6 +5527,7 @@ var init_prompt_assistant = __esm({
|
|
|
4173
5527
|
init_select();
|
|
4174
5528
|
init_box();
|
|
4175
5529
|
init_theme();
|
|
5530
|
+
init_logger();
|
|
4176
5531
|
IMPORTANT_DIRS = [
|
|
4177
5532
|
"index.html",
|
|
4178
5533
|
"styles",
|
|
@@ -4300,7 +5655,7 @@ __export(exporter_exports, {
|
|
|
4300
5655
|
});
|
|
4301
5656
|
import fs5 from "fs/promises";
|
|
4302
5657
|
import path6 from "path";
|
|
4303
|
-
import
|
|
5658
|
+
import chalk8 from "chalk";
|
|
4304
5659
|
function deriveOutputName(url, platformName) {
|
|
4305
5660
|
try {
|
|
4306
5661
|
const parsed = new URL(url);
|
|
@@ -4337,6 +5692,8 @@ var init_exporter = __esm({
|
|
|
4337
5692
|
init_asset_map();
|
|
4338
5693
|
init_download();
|
|
4339
5694
|
init_logger();
|
|
5695
|
+
init_progress();
|
|
5696
|
+
init_sidebar();
|
|
4340
5697
|
init_capture();
|
|
4341
5698
|
init_anti_bot();
|
|
4342
5699
|
init_download2();
|
|
@@ -4354,9 +5711,12 @@ var init_exporter = __esm({
|
|
|
4354
5711
|
page;
|
|
4355
5712
|
ssrHTML;
|
|
4356
5713
|
prettyPrint;
|
|
5714
|
+
interactive;
|
|
4357
5715
|
platform;
|
|
4358
5716
|
cooking;
|
|
4359
|
-
|
|
5717
|
+
deviceScaleFactor;
|
|
5718
|
+
subpages = /* @__PURE__ */ new Map();
|
|
5719
|
+
constructor(siteUrl, outDir, platformOverride, deviceScaleFactor) {
|
|
4360
5720
|
this.siteUrl = siteUrl;
|
|
4361
5721
|
this.outDir = outDir;
|
|
4362
5722
|
this.assets = new AssetMap();
|
|
@@ -4364,6 +5724,8 @@ var init_exporter = __esm({
|
|
|
4364
5724
|
this.page = null;
|
|
4365
5725
|
this.ssrHTML = "";
|
|
4366
5726
|
this.prettyPrint = true;
|
|
5727
|
+
this.interactive = true;
|
|
5728
|
+
this.deviceScaleFactor = deviceScaleFactor;
|
|
4367
5729
|
if (platformOverride && platformOverride !== "unknown") {
|
|
4368
5730
|
this.platform = getPlatformByName(platformOverride);
|
|
4369
5731
|
} else {
|
|
@@ -4376,15 +5738,20 @@ var init_exporter = __esm({
|
|
|
4376
5738
|
${ui.text.bold("Framer Export")} ${chip("mirror")} ${ui.muted("v4 pipeline")}
|
|
4377
5739
|
`
|
|
4378
5740
|
);
|
|
4379
|
-
info("Source : " +
|
|
5741
|
+
info("Source : " + chalk8.underline(this.siteUrl));
|
|
4380
5742
|
info("Output : " + ui.primary(this.outDir));
|
|
4381
5743
|
info("Platform : " + ui.primary(this.platform.displayName));
|
|
5744
|
+
info("DPR : " + ui.primary(String(this.deviceScaleFactor || 1)));
|
|
4382
5745
|
if (includeSubpages) {
|
|
4383
5746
|
info("Subpages : " + ui.success("enabled"));
|
|
4384
5747
|
}
|
|
4385
5748
|
console.log("");
|
|
4386
5749
|
this.cooking = new CookingSpinner();
|
|
4387
5750
|
setCooking(this.cooking);
|
|
5751
|
+
resetProgress();
|
|
5752
|
+
const sidebar = new ExportSidebar();
|
|
5753
|
+
sidebar.start();
|
|
5754
|
+
this.phase("Preparing directories...");
|
|
4388
5755
|
this.cooking.start("Preparing directories...");
|
|
4389
5756
|
for (const d of [
|
|
4390
5757
|
"",
|
|
@@ -4401,6 +5768,7 @@ var init_exporter = __esm({
|
|
|
4401
5768
|
await fs5.mkdir(path6.join(this.outDir, d), { recursive: true });
|
|
4402
5769
|
}
|
|
4403
5770
|
log("Output directory structure created");
|
|
5771
|
+
this.phase("Fetching SSR HTML...");
|
|
4404
5772
|
this.cooking.update("Fetching SSR HTML...");
|
|
4405
5773
|
log("Fetching SSR HTML from " + this.siteUrl);
|
|
4406
5774
|
try {
|
|
@@ -4408,7 +5776,7 @@ var init_exporter = __esm({
|
|
|
4408
5776
|
this.ssrHTML = buf.toString("utf-8");
|
|
4409
5777
|
success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
|
|
4410
5778
|
} catch (e) {
|
|
4411
|
-
log(
|
|
5779
|
+
log(chalk8.red("Could not fetch SSR HTML: " + e.message));
|
|
4412
5780
|
}
|
|
4413
5781
|
const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
|
|
4414
5782
|
if (htmlDetected.name !== this.platform.name) {
|
|
@@ -4416,16 +5784,23 @@ var init_exporter = __esm({
|
|
|
4416
5784
|
log("Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)");
|
|
4417
5785
|
}
|
|
4418
5786
|
try {
|
|
5787
|
+
this.phase("Capturing rendered page...");
|
|
4419
5788
|
await launchAndCapture(this);
|
|
4420
5789
|
if (includeSubpages && this.page) {
|
|
4421
5790
|
await this.crawlSubpages();
|
|
4422
5791
|
}
|
|
4423
5792
|
await closeBrowser(this);
|
|
5793
|
+
this.phase("Downloading assets...");
|
|
4424
5794
|
this.cooking.update("Downloading assets...");
|
|
4425
5795
|
await downloadAll(this);
|
|
5796
|
+
this.phase("Resolving lazy-loaded chunks...");
|
|
5797
|
+
this.cooking.update("Resolving lazy-loaded chunks...");
|
|
5798
|
+
await downloadLazyChunks(this);
|
|
5799
|
+
this.phase("Building output...");
|
|
4426
5800
|
this.cooking.update("Building output...");
|
|
4427
5801
|
await buildOutput(this);
|
|
4428
5802
|
if (this.platform.postProcess) {
|
|
5803
|
+
this.phase("Post-processing...");
|
|
4429
5804
|
this.cooking.update("Running " + this.platform.displayName + " post-processing...");
|
|
4430
5805
|
try {
|
|
4431
5806
|
await this.platform.postProcess(this);
|
|
@@ -4435,6 +5810,7 @@ var init_exporter = __esm({
|
|
|
4435
5810
|
}
|
|
4436
5811
|
}
|
|
4437
5812
|
} catch (e) {
|
|
5813
|
+
sidebar.stop();
|
|
4438
5814
|
this.cooking?.stop();
|
|
4439
5815
|
setCooking(null);
|
|
4440
5816
|
await closeBrowser(this);
|
|
@@ -4444,14 +5820,32 @@ var init_exporter = __esm({
|
|
|
4444
5820
|
}
|
|
4445
5821
|
throw e;
|
|
4446
5822
|
}
|
|
5823
|
+
this.phase("Done");
|
|
5824
|
+
sidebar.stop();
|
|
4447
5825
|
this.cooking.stop();
|
|
4448
5826
|
setCooking(null);
|
|
4449
5827
|
console.log("");
|
|
4450
5828
|
success("Export complete!");
|
|
5829
|
+
await this.writeExportLog();
|
|
4451
5830
|
await printSummary(this);
|
|
4452
|
-
|
|
5831
|
+
if (this.interactive) {
|
|
5832
|
+
await runAiPromptAssistant(this);
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
phase(label) {
|
|
5836
|
+
setPhase(label);
|
|
5837
|
+
}
|
|
5838
|
+
async writeExportLog() {
|
|
5839
|
+
const lines = getLogHistory().map((r) => `[${r.time}] [${r.level}] ${r.message}`);
|
|
5840
|
+
const dest = path6.join(this.outDir, "export.log");
|
|
5841
|
+
try {
|
|
5842
|
+
await fs5.writeFile(dest, lines.join("\n") + "\n", "utf-8");
|
|
5843
|
+
log("Full export log written: export.log (" + lines.length + " lines)");
|
|
5844
|
+
} catch {
|
|
5845
|
+
}
|
|
4453
5846
|
}
|
|
4454
5847
|
async crawlSubpages() {
|
|
5848
|
+
this.phase("Crawling sub-pages...");
|
|
4455
5849
|
this.cooking?.update("Discovering sub-pages...");
|
|
4456
5850
|
log("Scanning page for internal links...");
|
|
4457
5851
|
const page = this.page;
|
|
@@ -4475,8 +5869,20 @@ var init_exporter = __esm({
|
|
|
4475
5869
|
}
|
|
4476
5870
|
return Array.from(hrefs);
|
|
4477
5871
|
}, baseHost);
|
|
4478
|
-
|
|
4479
|
-
const
|
|
5872
|
+
const ssrLinks = [];
|
|
5873
|
+
for (const m of this.ssrHTML.matchAll(/<a\b[^>]*\shref=["']([^"']+)["']/gi)) {
|
|
5874
|
+
const href = m[1];
|
|
5875
|
+
if (/^(javascript:|mailto:|tel:|#)/i.test(href)) continue;
|
|
5876
|
+
try {
|
|
5877
|
+
const u = new URL(href, this.siteUrl);
|
|
5878
|
+
if (u.protocol.startsWith("http") && u.hostname.replace(/^www\./, "") === baseHost && u.pathname !== "/" && u.pathname !== "") {
|
|
5879
|
+
ssrLinks.push(u.href.split("#")[0]);
|
|
5880
|
+
}
|
|
5881
|
+
} catch {
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
const uniqueLinks = [.../* @__PURE__ */ new Set([...links, ...ssrLinks])].slice(0, 50);
|
|
5885
|
+
log("Found " + uniqueLinks.length + " sub-page links");
|
|
4480
5886
|
if (uniqueLinks.length === 0) {
|
|
4481
5887
|
log("No sub-pages to crawl");
|
|
4482
5888
|
return;
|
|
@@ -4493,6 +5899,9 @@ var init_exporter = __esm({
|
|
|
4493
5899
|
const filename = slug + ".html";
|
|
4494
5900
|
const filepath = path6.join(this.outDir, "subpages", filename);
|
|
4495
5901
|
await fs5.writeFile(filepath, html, "utf-8");
|
|
5902
|
+
this.subpages.set(link, filename);
|
|
5903
|
+
noteSubpage();
|
|
5904
|
+
noteFile("subpages/" + filename);
|
|
4496
5905
|
log(" Saved: subpages/" + filename);
|
|
4497
5906
|
} catch (e) {
|
|
4498
5907
|
log(" Skipped " + link + ": " + e.message);
|
|
@@ -4514,67 +5923,269 @@ var init_exporter = __esm({
|
|
|
4514
5923
|
}
|
|
4515
5924
|
});
|
|
4516
5925
|
|
|
4517
|
-
// src/
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
if (choice === USE_DETECTED) return { platform: detected.name };
|
|
5926
|
+
// src/ui/server.ts
|
|
5927
|
+
var server_exports = {};
|
|
5928
|
+
__export(server_exports, {
|
|
5929
|
+
startUiServer: () => startUiServer
|
|
5930
|
+
});
|
|
5931
|
+
import http2 from "http";
|
|
5932
|
+
import path7 from "path";
|
|
5933
|
+
import { URL as URL4 } from "url";
|
|
5934
|
+
function isAllowedHost(req) {
|
|
5935
|
+
const host = (req.headers.host || "").toLowerCase();
|
|
5936
|
+
return /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(host);
|
|
5937
|
+
}
|
|
5938
|
+
function isSameOrigin(req) {
|
|
5939
|
+
const origin = req.headers.origin;
|
|
5940
|
+
if (!origin) return true;
|
|
5941
|
+
try {
|
|
5942
|
+
const parsed = new URL4(origin);
|
|
5943
|
+
return (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") && parsed.port === (req.headers.host || "").split(":")[1];
|
|
5944
|
+
} catch {
|
|
5945
|
+
return false;
|
|
4538
5946
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
5947
|
+
}
|
|
5948
|
+
function resolveOutDir(requested, fallbackName) {
|
|
5949
|
+
const cwd = process.cwd();
|
|
5950
|
+
const target = path7.resolve(cwd, requested || "./" + fallbackName);
|
|
5951
|
+
if (target !== cwd && !target.startsWith(cwd + path7.sep)) return null;
|
|
5952
|
+
return target;
|
|
5953
|
+
}
|
|
5954
|
+
function quoteForShell(name) {
|
|
5955
|
+
return '"' + name.replace(/["`$\\\r\n]/g, "") + '"';
|
|
5956
|
+
}
|
|
5957
|
+
function sse(event, data) {
|
|
5958
|
+
const payload = `event: ${event}
|
|
5959
|
+
data: ${JSON.stringify(data)}
|
|
5960
|
+
|
|
5961
|
+
`;
|
|
5962
|
+
for (const client of clients) client.write(payload);
|
|
5963
|
+
}
|
|
5964
|
+
function json(res, code, data) {
|
|
5965
|
+
const body = JSON.stringify(data);
|
|
5966
|
+
res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
|
|
5967
|
+
res.end(body);
|
|
5968
|
+
}
|
|
5969
|
+
function readBody(req) {
|
|
5970
|
+
return new Promise((resolve, reject) => {
|
|
5971
|
+
let data = "";
|
|
5972
|
+
req.on("data", (chunk) => {
|
|
5973
|
+
data += chunk;
|
|
5974
|
+
if (data.length > 1e6) reject(new Error("body too large"));
|
|
4549
5975
|
});
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
5976
|
+
req.on("end", () => resolve(data));
|
|
5977
|
+
req.on("error", reject);
|
|
5978
|
+
});
|
|
5979
|
+
}
|
|
5980
|
+
function platformsPayload() {
|
|
5981
|
+
const grouped = platformsByCategory();
|
|
5982
|
+
return {
|
|
5983
|
+
categories: CATEGORY_ORDER.map((cat) => ({
|
|
5984
|
+
id: cat,
|
|
5985
|
+
label: CATEGORY_LABELS[cat],
|
|
5986
|
+
platforms: grouped[cat].map((h) => ({
|
|
5987
|
+
name: h.name,
|
|
5988
|
+
displayName: h.displayName,
|
|
5989
|
+
beta: isBetaPlatform(h.name)
|
|
5990
|
+
}))
|
|
5991
|
+
}))
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5994
|
+
async function startExport(body, outDir) {
|
|
5995
|
+
run = { state: "running" };
|
|
5996
|
+
clearLogHistory();
|
|
5997
|
+
sse("status", run);
|
|
5998
|
+
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5999
|
+
CFG2.concurrency = body.concurrency || 12;
|
|
6000
|
+
const exporter = new FramerExporter(
|
|
6001
|
+
body.url,
|
|
6002
|
+
outDir,
|
|
6003
|
+
body.platform || void 0
|
|
6004
|
+
);
|
|
6005
|
+
exporter.prettyPrint = body.prettyPrint !== false;
|
|
6006
|
+
exporter.interactive = false;
|
|
6007
|
+
try {
|
|
6008
|
+
await exporter.run(body.subpages === true);
|
|
6009
|
+
run = {
|
|
6010
|
+
state: "done",
|
|
6011
|
+
outDir,
|
|
6012
|
+
serveCommand: `cd ${quoteForShell(path7.basename(outDir))} && node serve.js`,
|
|
6013
|
+
summary: await collectSummary(outDir)
|
|
6014
|
+
};
|
|
6015
|
+
} catch (e) {
|
|
6016
|
+
run = { state: "error", error: e.message };
|
|
6017
|
+
}
|
|
6018
|
+
sse("status", run);
|
|
6019
|
+
}
|
|
6020
|
+
function startUiServer(port) {
|
|
6021
|
+
const unsubscribeLog = onLog((record2) => sse("log", record2));
|
|
6022
|
+
const unsubscribeProgress = onProgress((progress) => sse("progress", progress));
|
|
6023
|
+
const server = http2.createServer(async (req, res) => {
|
|
6024
|
+
const parsed = new URL4(req.url || "/", "http://localhost");
|
|
6025
|
+
const route = parsed.pathname;
|
|
6026
|
+
if (!isAllowedHost(req) || !isSameOrigin(req)) {
|
|
6027
|
+
json(res, 403, { error: "forbidden" });
|
|
6028
|
+
return;
|
|
6029
|
+
}
|
|
6030
|
+
if (route === "/" && req.method === "GET") {
|
|
6031
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
6032
|
+
res.end(UI_HTML);
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
if (route === "/app.js" && req.method === "GET") {
|
|
6036
|
+
res.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8" });
|
|
6037
|
+
res.end(APP_JS);
|
|
6038
|
+
return;
|
|
6039
|
+
}
|
|
6040
|
+
if (route === "/pixel-blast.js" && req.method === "GET") {
|
|
6041
|
+
res.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8" });
|
|
6042
|
+
res.end(PIXEL_BLAST_JS);
|
|
6043
|
+
return;
|
|
6044
|
+
}
|
|
6045
|
+
if (route === "/api/platforms" && req.method === "GET") {
|
|
6046
|
+
json(res, 200, platformsPayload());
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
if (route === "/api/derive" && req.method === "GET") {
|
|
6050
|
+
const url = parsed.searchParams.get("url") || "";
|
|
6051
|
+
const platform = parsed.searchParams.get("platform") || null;
|
|
6052
|
+
try {
|
|
6053
|
+
const { detectPlatform: detectPlatform2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
6054
|
+
const name = deriveOutputName(url, platform || detectPlatform2(url).name);
|
|
6055
|
+
json(res, 200, { name });
|
|
6056
|
+
} catch {
|
|
6057
|
+
json(res, 400, { error: "invalid url" });
|
|
6058
|
+
}
|
|
6059
|
+
return;
|
|
6060
|
+
}
|
|
6061
|
+
if (route === "/api/status" && req.method === "GET") {
|
|
6062
|
+
json(res, 200, { run, progress: getProgress() });
|
|
6063
|
+
return;
|
|
6064
|
+
}
|
|
6065
|
+
if (route === "/api/log" && req.method === "GET") {
|
|
6066
|
+
const text = getLogHistory().map((r) => `[${r.time}] [${r.level}] ${r.message}`).join("\n");
|
|
6067
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
6068
|
+
res.end(text + "\n");
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
if (route === "/api/events" && req.method === "GET") {
|
|
6072
|
+
res.writeHead(200, {
|
|
6073
|
+
"Content-Type": "text/event-stream",
|
|
6074
|
+
"Cache-Control": "no-cache",
|
|
6075
|
+
Connection: "keep-alive"
|
|
6076
|
+
});
|
|
6077
|
+
res.write(`event: status
|
|
6078
|
+
data: ${JSON.stringify(run)}
|
|
6079
|
+
|
|
6080
|
+
`);
|
|
6081
|
+
res.write(`event: progress
|
|
6082
|
+
data: ${JSON.stringify(getProgress())}
|
|
6083
|
+
|
|
6084
|
+
`);
|
|
6085
|
+
clients.add(res);
|
|
6086
|
+
req.on("close", () => clients.delete(res));
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
if (route === "/api/export" && req.method === "POST") {
|
|
6090
|
+
if (run.state === "running") {
|
|
6091
|
+
json(res, 409, { error: "an export is already running" });
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
let body;
|
|
6095
|
+
try {
|
|
6096
|
+
body = JSON.parse(await readBody(req));
|
|
6097
|
+
new URL4(body.url);
|
|
6098
|
+
} catch {
|
|
6099
|
+
json(res, 400, { error: "invalid request body" });
|
|
6100
|
+
return;
|
|
6101
|
+
}
|
|
6102
|
+
const outDir = resolveOutDir(body.outDir, deriveOutputName(body.url, "framer"));
|
|
6103
|
+
if (!outDir) {
|
|
6104
|
+
json(res, 400, { error: "output directory must stay inside the working directory" });
|
|
6105
|
+
return;
|
|
6106
|
+
}
|
|
6107
|
+
json(res, 202, { started: true });
|
|
6108
|
+
void startExport(body, outDir);
|
|
6109
|
+
return;
|
|
6110
|
+
}
|
|
6111
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
6112
|
+
res.end('{"error":"not found"}');
|
|
6113
|
+
});
|
|
6114
|
+
server.on("close", () => {
|
|
6115
|
+
unsubscribeLog();
|
|
6116
|
+
unsubscribeProgress();
|
|
6117
|
+
});
|
|
6118
|
+
return new Promise((resolve, reject) => {
|
|
6119
|
+
server.on("error", reject);
|
|
6120
|
+
server.listen(port, "127.0.0.1", () => {
|
|
6121
|
+
const address = server.address();
|
|
6122
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
6123
|
+
console.log("");
|
|
6124
|
+
console.log(` ${ui.text.bold("Framer Export UI")}`);
|
|
6125
|
+
console.log(` ${ui.muted("Local")} ${ui.primary(`http://localhost:${actualPort}`)}`);
|
|
6126
|
+
console.log(` ${ui.muted("Stop")} ${ui.primary("ctrl+c")}`);
|
|
6127
|
+
console.log("");
|
|
6128
|
+
resolve({
|
|
6129
|
+
port: actualPort,
|
|
6130
|
+
close: () => new Promise((done) => {
|
|
6131
|
+
for (const client of clients) client.end();
|
|
6132
|
+
clients.clear();
|
|
6133
|
+
server.close(() => done());
|
|
6134
|
+
})
|
|
6135
|
+
});
|
|
4563
6136
|
});
|
|
4564
|
-
|
|
4565
|
-
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
var clients, run;
|
|
6140
|
+
var init_server = __esm({
|
|
6141
|
+
"src/ui/server.ts"() {
|
|
6142
|
+
"use strict";
|
|
6143
|
+
init_page();
|
|
6144
|
+
init_app_js();
|
|
6145
|
+
init_pixel_blast();
|
|
6146
|
+
init_exporter();
|
|
6147
|
+
init_summary();
|
|
6148
|
+
init_platforms();
|
|
6149
|
+
init_logger();
|
|
6150
|
+
init_progress();
|
|
6151
|
+
init_theme();
|
|
6152
|
+
clients = /* @__PURE__ */ new Set();
|
|
6153
|
+
run = { state: "idle" };
|
|
6154
|
+
}
|
|
6155
|
+
});
|
|
6156
|
+
|
|
6157
|
+
// src/cli/select-grouped.ts
|
|
6158
|
+
async function selectTool() {
|
|
6159
|
+
const grouped = platformsByCategory();
|
|
6160
|
+
const options = [{ label: "Auto-detect from URL", value: AUTO_DETECT }];
|
|
6161
|
+
for (const cat of CATEGORY_ORDER) {
|
|
6162
|
+
options.push({ label: CATEGORY_LABELS[cat], value: `__heading_${cat}`, heading: true });
|
|
6163
|
+
for (const handler of grouped[cat]) {
|
|
6164
|
+
options.push({
|
|
6165
|
+
label: isBetaPlatform(handler.name) ? `${handler.displayName} [beta]` : handler.displayName,
|
|
6166
|
+
value: handler.name
|
|
6167
|
+
});
|
|
6168
|
+
}
|
|
4566
6169
|
}
|
|
6170
|
+
const choice = await select("Select a tool to export", options, 0, {
|
|
6171
|
+
headerLines: ["25+ platforms supported, type to search the list."],
|
|
6172
|
+
searchable: true,
|
|
6173
|
+
footer: "type to search \u2191\u2193 scroll enter select esc close"
|
|
6174
|
+
});
|
|
6175
|
+
if (choice === AUTO_DETECT) return { auto: true };
|
|
6176
|
+
return { platform: choice };
|
|
6177
|
+
}
|
|
6178
|
+
function resolveDetected(siteUrl) {
|
|
6179
|
+
const detected = detectByUrl(siteUrl);
|
|
6180
|
+
return detected ? detected.name : null;
|
|
4567
6181
|
}
|
|
4568
|
-
var
|
|
6182
|
+
var AUTO_DETECT;
|
|
4569
6183
|
var init_select_grouped = __esm({
|
|
4570
6184
|
"src/cli/select-grouped.ts"() {
|
|
4571
6185
|
"use strict";
|
|
4572
6186
|
init_select();
|
|
4573
6187
|
init_platforms();
|
|
4574
|
-
|
|
4575
|
-
BROWSE = "__browse__";
|
|
4576
|
-
USE_DETECTED = "__use_detected__";
|
|
4577
|
-
BACK = "__back__";
|
|
6188
|
+
AUTO_DETECT = "__auto__";
|
|
4578
6189
|
}
|
|
4579
6190
|
});
|
|
4580
6191
|
|
|
@@ -4584,21 +6195,12 @@ __export(setup_exports, {
|
|
|
4584
6195
|
runSetup: () => runSetup
|
|
4585
6196
|
});
|
|
4586
6197
|
import readline2 from "readline/promises";
|
|
4587
|
-
import { stdin as stdin3, stdout as
|
|
4588
|
-
import
|
|
4589
|
-
import { URL as
|
|
4590
|
-
import
|
|
4591
|
-
function
|
|
4592
|
-
|
|
4593
|
-
if (w < 50) {
|
|
4594
|
-
console.log(`
|
|
4595
|
-
${bullet("\u25CF")} ${ui.text.bold(title)}`);
|
|
4596
|
-
return;
|
|
4597
|
-
}
|
|
4598
|
-
console.log(boxTop(w));
|
|
4599
|
-
console.log(boxLine(w, ui.text.bold(" " + title)));
|
|
4600
|
-
console.log(boxBot(w));
|
|
4601
|
-
console.log("");
|
|
6198
|
+
import { stdin as stdin3, stdout as stdout5 } from "process";
|
|
6199
|
+
import path8 from "path";
|
|
6200
|
+
import { URL as URL5 } from "url";
|
|
6201
|
+
import chalk9 from "chalk";
|
|
6202
|
+
function checkbox(checked) {
|
|
6203
|
+
return checked ? "[\u2713]" : "[ ]";
|
|
4602
6204
|
}
|
|
4603
6205
|
async function runSetup(legacyMode = false) {
|
|
4604
6206
|
showBanner();
|
|
@@ -4607,269 +6209,183 @@ async function runSetup(legacyMode = false) {
|
|
|
4607
6209
|
` ${ui.muted("Export 25+ website platforms into a clean, self-hosted local mirror.")}
|
|
4608
6210
|
`
|
|
4609
6211
|
);
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
return answer.trim() || defaultVal || "";
|
|
4619
|
-
};
|
|
4620
|
-
if (legacyMode) drawHeader("Step 1 : Site URL");
|
|
6212
|
+
if (legacyMode) {
|
|
6213
|
+
return runLegacySetup();
|
|
6214
|
+
}
|
|
6215
|
+
let platformName = null;
|
|
6216
|
+
let autoDetect = false;
|
|
6217
|
+
const tool = await selectTool();
|
|
6218
|
+
if ("auto" in tool) autoDetect = true;
|
|
6219
|
+
else platformName = tool.platform;
|
|
4621
6220
|
let siteUrl = "";
|
|
4622
6221
|
let urlError = "";
|
|
4623
6222
|
while (!siteUrl) {
|
|
4624
|
-
const
|
|
4625
|
-
"
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
);
|
|
6223
|
+
const headerLines = [
|
|
6224
|
+
autoDetect ? "Tool: auto-detect from URL" : `Tool: ${getPlatformByName(platformName).displayName}`,
|
|
6225
|
+
urlError
|
|
6226
|
+
].filter(Boolean);
|
|
6227
|
+
const input = await promptInput("Enter the site URL", "", { headerLines });
|
|
4629
6228
|
try {
|
|
4630
|
-
new
|
|
6229
|
+
new URL5(input);
|
|
4631
6230
|
siteUrl = input;
|
|
4632
6231
|
urlError = "";
|
|
4633
6232
|
} catch {
|
|
4634
6233
|
urlError = "Invalid URL. Enter a valid URL (https://...)";
|
|
4635
|
-
if (legacyMode) {
|
|
4636
|
-
console.log(` ${ui.error("\u2717")} ${ui.error(urlError)}
|
|
4637
|
-
`);
|
|
4638
|
-
}
|
|
4639
6234
|
}
|
|
4640
6235
|
}
|
|
4641
|
-
if (
|
|
4642
|
-
|
|
4643
|
-
`);
|
|
4644
|
-
}
|
|
4645
|
-
let platformName = null;
|
|
4646
|
-
if (legacyMode) {
|
|
4647
|
-
drawHeader("Step 2 : Platform");
|
|
4648
|
-
const detected = detectPlatform(siteUrl);
|
|
4649
|
-
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
4650
|
-
const platformInput = await ask("Platform name", detected.name);
|
|
4651
|
-
const known = PLATFORM_REGISTRY.some((platform) => platform.name === platformInput);
|
|
4652
|
-
platformName = known ? platformInput : detected.name;
|
|
4653
|
-
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
4654
|
-
`);
|
|
6236
|
+
if (autoDetect) {
|
|
6237
|
+
platformName = detectPlatform(siteUrl).name;
|
|
4655
6238
|
} else {
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
urlError = "";
|
|
4671
|
-
} catch {
|
|
4672
|
-
urlError = "Invalid URL. Enter a valid URL (https://...)";
|
|
4673
|
-
}
|
|
4674
|
-
}
|
|
4675
|
-
continue;
|
|
4676
|
-
}
|
|
4677
|
-
platformName = selection.platform;
|
|
6239
|
+
const detected = resolveDetected(siteUrl);
|
|
6240
|
+
if (detected && detected !== platformName) {
|
|
6241
|
+
const detectedName = getPlatformByName(detected).displayName;
|
|
6242
|
+
const chosenName = getPlatformByName(platformName).displayName;
|
|
6243
|
+
const answer = await select(
|
|
6244
|
+
"URL looks like a different platform",
|
|
6245
|
+
[
|
|
6246
|
+
{ label: `Switch to ${detectedName} (detected)`, value: "switch" },
|
|
6247
|
+
{ label: `Keep ${chosenName}`, value: "keep" }
|
|
6248
|
+
],
|
|
6249
|
+
0,
|
|
6250
|
+
{ headerLines: [`URL: ${siteUrl}`] }
|
|
6251
|
+
);
|
|
6252
|
+
if (answer === "switch") platformName = detected;
|
|
4678
6253
|
}
|
|
4679
6254
|
}
|
|
4680
|
-
if (!platformName) {
|
|
4681
|
-
throw new Error("Platform selection failed");
|
|
4682
|
-
}
|
|
4683
|
-
const rl2 = legacyMode ? rl : null;
|
|
4684
|
-
const ask2 = async (question, defaultVal, headerLines = []) => {
|
|
4685
|
-
if (!legacyMode) {
|
|
4686
|
-
return promptInput(question, defaultVal || "", { headerLines });
|
|
4687
|
-
}
|
|
4688
|
-
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
4689
|
-
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
4690
|
-
const answer = await rl2.question(prompt);
|
|
4691
|
-
return answer.trim() || defaultVal || "";
|
|
4692
|
-
};
|
|
4693
6255
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
4694
6256
|
let outDir = "./" + derivedName;
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
"Step 3 : Output Directory",
|
|
4699
|
-
`URL: ${siteUrl}`,
|
|
4700
|
-
`Platform: ${platformName}`
|
|
4701
|
-
]);
|
|
4702
|
-
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
4703
|
-
`);
|
|
4704
|
-
drawHeader("Step 4 : Options");
|
|
4705
|
-
} else {
|
|
4706
|
-
outDir = await ask2("Output directory", outDir, [
|
|
4707
|
-
"Step 3 : Output Directory",
|
|
4708
|
-
`URL: ${siteUrl}`,
|
|
4709
|
-
`Platform: ${platformName}`
|
|
4710
|
-
]);
|
|
4711
|
-
}
|
|
6257
|
+
outDir = await promptInput("Output directory", outDir, {
|
|
6258
|
+
headerLines: [`URL: ${siteUrl}`, `Tool: ${getPlatformByName(platformName).displayName}`]
|
|
6259
|
+
});
|
|
4712
6260
|
let prettyPrint = true;
|
|
4713
6261
|
let concurrency = 12;
|
|
4714
6262
|
let includeSubpages = false;
|
|
4715
|
-
let
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
[
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
4732
|
-
console.log(boxSep(w));
|
|
4733
|
-
} else {
|
|
4734
|
-
console.log(ui.text.bold(" Summary:"));
|
|
4735
|
-
}
|
|
4736
|
-
for (const [label, value] of summaryRows()) {
|
|
4737
|
-
console.log(boxRow(w, label, value));
|
|
4738
|
-
}
|
|
4739
|
-
if (!isSmall) {
|
|
4740
|
-
console.log(boxBot(w));
|
|
4741
|
-
}
|
|
4742
|
-
console.log("");
|
|
4743
|
-
};
|
|
4744
|
-
if (legacyMode) {
|
|
4745
|
-
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
4746
|
-
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
4747
|
-
console.log(
|
|
4748
|
-
` ${ui.success("\u2713")} Pretty-print: ${prettyPrint ? ui.success("yes") : ui.error("no")}
|
|
4749
|
-
`
|
|
4750
|
-
);
|
|
4751
|
-
const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
|
|
4752
|
-
includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
|
|
4753
|
-
console.log(
|
|
4754
|
-
` ${ui.success("\u2713")} Sub-pages: ${includeSubpages ? ui.success("yes") : ui.error("no")}
|
|
4755
|
-
`
|
|
4756
|
-
);
|
|
4757
|
-
const concurrencyAnswer = await ask2("Download concurrency", "12");
|
|
4758
|
-
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
4759
|
-
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
4760
|
-
`);
|
|
4761
|
-
printSummary2();
|
|
4762
|
-
const confirm = await ask2("Start export? (y/n)", "y");
|
|
4763
|
-
startExport = confirm.toLowerCase().startsWith("y");
|
|
4764
|
-
rl2.close();
|
|
4765
|
-
} else {
|
|
4766
|
-
let optionStep = 0;
|
|
4767
|
-
while (true) {
|
|
4768
|
-
while (optionStep < 3) {
|
|
4769
|
-
if (optionStep === 0) {
|
|
4770
|
-
const prettyVal = await select(
|
|
4771
|
-
"Pretty-print JS files?",
|
|
4772
|
-
[
|
|
4773
|
-
{ label: "Yes", value: "yes" },
|
|
4774
|
-
{ label: "No", value: "no" }
|
|
4775
|
-
],
|
|
4776
|
-
prettyPrint ? 0 : 1,
|
|
4777
|
-
{
|
|
4778
|
-
headerLines: ["Step 4 : Options"],
|
|
4779
|
-
actions: [{ label: "Previous: output directory", value: "previous" }],
|
|
4780
|
-
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4781
|
-
}
|
|
4782
|
-
);
|
|
4783
|
-
if (prettyVal === "previous") {
|
|
4784
|
-
outDir = await ask2("Output directory", outDir, [
|
|
4785
|
-
"Step 3 : Output Directory",
|
|
4786
|
-
`URL: ${siteUrl}`,
|
|
4787
|
-
`Platform: ${platformName}`
|
|
4788
|
-
]);
|
|
4789
|
-
optionStep = 0;
|
|
4790
|
-
continue;
|
|
4791
|
-
}
|
|
4792
|
-
prettyPrint = prettyVal === "yes";
|
|
4793
|
-
optionStep = 1;
|
|
4794
|
-
continue;
|
|
4795
|
-
}
|
|
4796
|
-
if (optionStep === 1) {
|
|
4797
|
-
const subpagesVal = await select(
|
|
4798
|
-
"Export sub-pages?",
|
|
4799
|
-
[
|
|
4800
|
-
{ label: "No", value: "no" },
|
|
4801
|
-
{ label: "Yes, crawl and export", value: "yes" }
|
|
4802
|
-
],
|
|
4803
|
-
includeSubpages ? 1 : 0,
|
|
4804
|
-
{
|
|
4805
|
-
headerLines: ["Step 4 : Options"],
|
|
4806
|
-
actions: [{ label: "Previous: pretty-print", value: "previous" }],
|
|
4807
|
-
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4808
|
-
}
|
|
4809
|
-
);
|
|
4810
|
-
if (subpagesVal === "previous") {
|
|
4811
|
-
optionStep = 0;
|
|
4812
|
-
continue;
|
|
4813
|
-
}
|
|
4814
|
-
includeSubpages = subpagesVal === "yes";
|
|
4815
|
-
optionStep = 2;
|
|
4816
|
-
continue;
|
|
4817
|
-
}
|
|
4818
|
-
const concurrencyVal = await select(
|
|
4819
|
-
"Download concurrency",
|
|
4820
|
-
[
|
|
4821
|
-
{ label: "6 (slow connection)", value: "6" },
|
|
4822
|
-
{ label: "12 (default)", value: "12" },
|
|
4823
|
-
{ label: "20 (fast connection)", value: "20" }
|
|
4824
|
-
],
|
|
4825
|
-
concurrency === 6 ? 0 : concurrency === 20 ? 2 : 1,
|
|
4826
|
-
{
|
|
4827
|
-
headerLines: ["Step 4 : Options"],
|
|
4828
|
-
actions: [{ label: "Previous: sub-pages", value: "previous" }],
|
|
4829
|
-
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4830
|
-
}
|
|
4831
|
-
);
|
|
4832
|
-
if (concurrencyVal === "previous") {
|
|
4833
|
-
optionStep = 1;
|
|
4834
|
-
continue;
|
|
6263
|
+
let startExport2 = false;
|
|
6264
|
+
let focusIndex = 0;
|
|
6265
|
+
options: while (true) {
|
|
6266
|
+
const summary = [
|
|
6267
|
+
`URL ${siteUrl}`,
|
|
6268
|
+
`Tool ${getPlatformByName(platformName).displayName}`,
|
|
6269
|
+
`Output ${path8.resolve(outDir)}`
|
|
6270
|
+
];
|
|
6271
|
+
const choice = await select(
|
|
6272
|
+
"Export options",
|
|
6273
|
+
[
|
|
6274
|
+
{ label: `${checkbox(prettyPrint)} Pretty-print JS files`, value: "pretty" },
|
|
6275
|
+
{ label: `${checkbox(includeSubpages)} Export sub-pages`, value: "subpages" },
|
|
6276
|
+
{
|
|
6277
|
+
label: `[${String(concurrency).padStart(2)}] Download concurrency`,
|
|
6278
|
+
value: "concurrency"
|
|
4835
6279
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
{ label: "
|
|
4843
|
-
{ label: "Next: start now", value: "next" },
|
|
6280
|
+
],
|
|
6281
|
+
focusIndex,
|
|
6282
|
+
{
|
|
6283
|
+
headerLines: summary,
|
|
6284
|
+
actions: [
|
|
6285
|
+
{ label: "Start export", value: "start" },
|
|
6286
|
+
{ label: "Back", value: "back" },
|
|
4844
6287
|
{ label: "Cancel", value: "cancel" }
|
|
4845
6288
|
],
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
6289
|
+
footer: "enter toggle tab actions mouse click esc close"
|
|
6290
|
+
}
|
|
6291
|
+
);
|
|
6292
|
+
switch (choice) {
|
|
6293
|
+
case "pretty":
|
|
6294
|
+
prettyPrint = !prettyPrint;
|
|
6295
|
+
focusIndex = 0;
|
|
6296
|
+
continue;
|
|
6297
|
+
case "subpages":
|
|
6298
|
+
includeSubpages = !includeSubpages;
|
|
6299
|
+
focusIndex = 1;
|
|
6300
|
+
continue;
|
|
6301
|
+
case "concurrency": {
|
|
6302
|
+
const idx = CONCURRENCY_STEPS.indexOf(concurrency);
|
|
6303
|
+
concurrency = CONCURRENCY_STEPS[(idx + 1) % CONCURRENCY_STEPS.length];
|
|
6304
|
+
focusIndex = 2;
|
|
4854
6305
|
continue;
|
|
4855
6306
|
}
|
|
4856
|
-
|
|
4857
|
-
|
|
6307
|
+
case "back":
|
|
6308
|
+
outDir = await promptInput("Output directory", outDir, {
|
|
6309
|
+
headerLines: [`URL: ${siteUrl}`]
|
|
6310
|
+
});
|
|
6311
|
+
focusIndex = 0;
|
|
6312
|
+
continue;
|
|
6313
|
+
case "start":
|
|
6314
|
+
startExport2 = true;
|
|
6315
|
+
break options;
|
|
6316
|
+
default:
|
|
6317
|
+
startExport2 = false;
|
|
6318
|
+
break options;
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
if (!startExport2) {
|
|
6322
|
+
console.log(`
|
|
6323
|
+
${ui.warning("Export cancelled.")}
|
|
6324
|
+
`);
|
|
6325
|
+
return;
|
|
6326
|
+
}
|
|
6327
|
+
console.log("");
|
|
6328
|
+
await launchExport(siteUrl, outDir, platformName, {
|
|
6329
|
+
prettyPrint,
|
|
6330
|
+
concurrency,
|
|
6331
|
+
includeSubpages
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
async function runLegacySetup() {
|
|
6335
|
+
const rl = readline2.createInterface({ input: stdin3, output: stdout5 });
|
|
6336
|
+
const ask = async (question, defaultVal) => {
|
|
6337
|
+
const suffix = defaultVal ? chalk9.gray(` (${defaultVal})`) : "";
|
|
6338
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
6339
|
+
const answer = await rl.question(prompt);
|
|
6340
|
+
return answer.trim() || defaultVal || "";
|
|
6341
|
+
};
|
|
6342
|
+
const platformInput = await ask('Tool (platform id, or "auto")', "auto");
|
|
6343
|
+
const known = PLATFORM_REGISTRY.some((platform) => platform.name === platformInput);
|
|
6344
|
+
let siteUrl = "";
|
|
6345
|
+
while (!siteUrl) {
|
|
6346
|
+
const input = await ask("Enter the site URL");
|
|
6347
|
+
try {
|
|
6348
|
+
new URL5(input);
|
|
6349
|
+
siteUrl = input;
|
|
6350
|
+
} catch {
|
|
6351
|
+
console.log(
|
|
6352
|
+
` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
|
|
6353
|
+
`
|
|
6354
|
+
);
|
|
4858
6355
|
}
|
|
4859
6356
|
}
|
|
4860
|
-
|
|
6357
|
+
const platformName = known ? platformInput : detectPlatform(siteUrl).name;
|
|
6358
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Tool:")} ${ui.primary(platformName)}
|
|
6359
|
+
`);
|
|
6360
|
+
const outDir = await ask("Output directory", "./" + deriveOutputName(siteUrl, platformName));
|
|
6361
|
+
const prettyAnswer = await ask("Pretty-print JS files? (y/n)", "y");
|
|
6362
|
+
const prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
6363
|
+
const subpagesAnswer = await ask("Export sub-pages? (y/n)", "n");
|
|
6364
|
+
const includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
|
|
6365
|
+
const concurrencyAnswer = await ask("Download concurrency", "12");
|
|
6366
|
+
const concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
6367
|
+
const confirm = await ask("Start export? (y/n)", "y");
|
|
6368
|
+
rl.close();
|
|
6369
|
+
if (!confirm.toLowerCase().startsWith("y")) {
|
|
4861
6370
|
console.log(`
|
|
4862
6371
|
${ui.warning("Export cancelled.")}
|
|
4863
6372
|
`);
|
|
4864
6373
|
return;
|
|
4865
6374
|
}
|
|
4866
6375
|
console.log("");
|
|
6376
|
+
await launchExport(siteUrl, outDir, platformName, {
|
|
6377
|
+
prettyPrint,
|
|
6378
|
+
concurrency,
|
|
6379
|
+
includeSubpages
|
|
6380
|
+
});
|
|
6381
|
+
}
|
|
6382
|
+
async function launchExport(siteUrl, outDir, platformName, opts) {
|
|
4867
6383
|
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4868
|
-
CFG2.concurrency = concurrency;
|
|
4869
|
-
const exporter = new FramerExporter(siteUrl,
|
|
4870
|
-
exporter.prettyPrint = prettyPrint;
|
|
6384
|
+
CFG2.concurrency = opts.concurrency;
|
|
6385
|
+
const exporter = new FramerExporter(siteUrl, path8.resolve(outDir), platformName);
|
|
6386
|
+
exporter.prettyPrint = opts.prettyPrint;
|
|
4871
6387
|
try {
|
|
4872
|
-
await exporter.run(includeSubpages);
|
|
6388
|
+
await exporter.run(opts.includeSubpages);
|
|
4873
6389
|
} catch (e) {
|
|
4874
6390
|
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
4875
6391
|
if (e instanceof AntiBotError2) {
|
|
@@ -4879,6 +6395,7 @@ async function runSetup(legacyMode = false) {
|
|
|
4879
6395
|
throw e;
|
|
4880
6396
|
}
|
|
4881
6397
|
}
|
|
6398
|
+
var CONCURRENCY_STEPS;
|
|
4882
6399
|
var init_setup = __esm({
|
|
4883
6400
|
"src/cli/setup.ts"() {
|
|
4884
6401
|
"use strict";
|
|
@@ -4887,15 +6404,15 @@ var init_setup = __esm({
|
|
|
4887
6404
|
init_platforms();
|
|
4888
6405
|
init_select();
|
|
4889
6406
|
init_select_grouped();
|
|
4890
|
-
init_box();
|
|
4891
6407
|
init_theme();
|
|
6408
|
+
CONCURRENCY_STEPS = [6, 12, 20];
|
|
4892
6409
|
}
|
|
4893
6410
|
});
|
|
4894
6411
|
|
|
4895
6412
|
// src/cli/index.ts
|
|
4896
6413
|
init_package();
|
|
4897
|
-
import
|
|
4898
|
-
import { URL as
|
|
6414
|
+
import path9 from "path";
|
|
6415
|
+
import { URL as URL6 } from "url";
|
|
4899
6416
|
import { spawnSync } from "child_process";
|
|
4900
6417
|
|
|
4901
6418
|
// src/cli/help.ts
|
|
@@ -4912,13 +6429,20 @@ function showHelp() {
|
|
|
4912
6429
|
console.log(
|
|
4913
6430
|
` ${ui.primary("fexport")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`
|
|
4914
6431
|
);
|
|
6432
|
+
console.log(
|
|
6433
|
+
` ${ui.primary("framer-export")} ${ui.success("ui")} ${ui.muted("[--port <n>] [--no-open]")}`
|
|
6434
|
+
);
|
|
4915
6435
|
console.log("");
|
|
4916
6436
|
console.log(ui.text.bold(" OPTIONS\n"));
|
|
4917
6437
|
const opts = [
|
|
6438
|
+
["ui", "Launch the web interface (gallery, options, live logs)"],
|
|
4918
6439
|
["--setup", "Launch the interactive setup assistant"],
|
|
4919
6440
|
["--platform <p>", "Force platform by id (e.g. framer, shopify, notion)"],
|
|
4920
6441
|
["--subpages", "Crawl and export sub-pages"],
|
|
4921
|
-
["--
|
|
6442
|
+
["--dpr <number>", "Capture device pixel ratio (default: 1)"],
|
|
6443
|
+
["--legacy-mode", "With --setup: use text input instead of arrow selection"],
|
|
6444
|
+
["--about", "Show version and package information"],
|
|
6445
|
+
["--version, -v", "Show the version number"],
|
|
4922
6446
|
["--help, -h", "Show this help message"]
|
|
4923
6447
|
];
|
|
4924
6448
|
for (const [flag, desc] of opts) {
|
|
@@ -4928,9 +6452,10 @@ function showHelp() {
|
|
|
4928
6452
|
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
4929
6453
|
const grouped = platformsByCategory();
|
|
4930
6454
|
for (const cat of CATEGORY_ORDER) {
|
|
4931
|
-
const names = grouped[cat].map((handler) => handler.displayName).join(", ");
|
|
6455
|
+
const names = grouped[cat].map((handler) => handler.displayName + (isBetaPlatform(handler.name) ? "*" : "")).join(", ");
|
|
4932
6456
|
console.log(` ${ui.primary(CATEGORY_LABELS[cat].padEnd(22))} ${ui.muted(names)}`);
|
|
4933
6457
|
}
|
|
6458
|
+
console.log(` ${ui.muted("* beta support, Framer, Webflow and Wix are stable")}`);
|
|
4934
6459
|
console.log("");
|
|
4935
6460
|
console.log(ui.text.bold(" EXAMPLES\n"));
|
|
4936
6461
|
console.log(
|
|
@@ -4971,8 +6496,8 @@ async function checkForUpdates(currentVersion) {
|
|
|
4971
6496
|
res.on("data", (chunk) => data += chunk);
|
|
4972
6497
|
res.on("end", () => {
|
|
4973
6498
|
try {
|
|
4974
|
-
const
|
|
4975
|
-
const latest =
|
|
6499
|
+
const json2 = JSON.parse(data);
|
|
6500
|
+
const latest = json2.version;
|
|
4976
6501
|
if (!latest) return resolve(null);
|
|
4977
6502
|
if (isNewerVersion(latest, currentVersion)) return resolve(latest);
|
|
4978
6503
|
resolve(null);
|
|
@@ -5078,20 +6603,20 @@ async function main() {
|
|
|
5078
6603
|
process.exit(0);
|
|
5079
6604
|
}
|
|
5080
6605
|
if (args.includes("--about")) {
|
|
5081
|
-
const
|
|
6606
|
+
const chalk10 = (await import("chalk")).default;
|
|
5082
6607
|
showBanner();
|
|
5083
6608
|
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
5084
6609
|
console.log(` ${ui.text(package_default.description)}
|
|
5085
6610
|
`);
|
|
5086
6611
|
console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
|
|
5087
6612
|
console.log(
|
|
5088
|
-
` ${ui.text.bold("Portfolio")} ${
|
|
6613
|
+
` ${ui.text.bold("Portfolio")} ${chalk10.underline.hex("#FAB283")("https://github.com/danbenba")}`
|
|
5089
6614
|
);
|
|
5090
6615
|
console.log(
|
|
5091
|
-
` ${ui.text.bold("GitHub")} ${
|
|
6616
|
+
` ${ui.text.bold("GitHub")} ${chalk10.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`
|
|
5092
6617
|
);
|
|
5093
6618
|
console.log(
|
|
5094
|
-
` ${ui.text.bold("npm")} ${
|
|
6619
|
+
` ${ui.text.bold("npm")} ${chalk10.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`
|
|
5095
6620
|
);
|
|
5096
6621
|
console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
|
|
5097
6622
|
console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
|
|
@@ -5102,6 +6627,27 @@ async function main() {
|
|
|
5102
6627
|
showHelp();
|
|
5103
6628
|
process.exit(0);
|
|
5104
6629
|
}
|
|
6630
|
+
if (args[0] === "ui") {
|
|
6631
|
+
args.shift();
|
|
6632
|
+
const portValue = extractFlag(args, "--port");
|
|
6633
|
+
const port = portValue === null ? 4400 : Number(portValue);
|
|
6634
|
+
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
6635
|
+
console.log(
|
|
6636
|
+
` ${ui.error("\u2717")} ${ui.error.bold("Invalid port:")} ${ui.text(portValue || "")}
|
|
6637
|
+
`
|
|
6638
|
+
);
|
|
6639
|
+
process.exit(1);
|
|
6640
|
+
}
|
|
6641
|
+
showBanner();
|
|
6642
|
+
const { startUiServer: startUiServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
6643
|
+
const handle = await startUiServer2(port);
|
|
6644
|
+
if (!hasFlag(args, "--no-open")) {
|
|
6645
|
+
const target = `http://localhost:${handle.port}`;
|
|
6646
|
+
const opener = process.platform === "win32" ? { cmd: "cmd", args: ["/c", "start", "", target] } : process.platform === "darwin" ? { cmd: "open", args: [target] } : { cmd: "xdg-open", args: [target] };
|
|
6647
|
+
spawnSync(opener.cmd, opener.args, { stdio: "ignore" });
|
|
6648
|
+
}
|
|
6649
|
+
return;
|
|
6650
|
+
}
|
|
5105
6651
|
await showLoadingIntro(VERSION);
|
|
5106
6652
|
await showUpdateNotice();
|
|
5107
6653
|
if (args.includes("--setup")) {
|
|
@@ -5117,11 +6663,20 @@ async function main() {
|
|
|
5117
6663
|
return;
|
|
5118
6664
|
}
|
|
5119
6665
|
const platformOverride = extractFlag(args, "--platform");
|
|
6666
|
+
const hasDprFlag = args.includes("--dpr");
|
|
6667
|
+
const dprValue = extractFlag(args, "--dpr");
|
|
5120
6668
|
const includeSubpages = hasFlag(args, "--subpages");
|
|
6669
|
+
const deviceScaleFactor = dprValue === null ? 1 : Number(dprValue);
|
|
6670
|
+
if (hasDprFlag && dprValue === null || !Number.isFinite(deviceScaleFactor) || deviceScaleFactor <= 0) {
|
|
6671
|
+
console.log(` ${ui.error("\u2717")} ${ui.error.bold("Invalid DPR:")} ${ui.text(dprValue || "")}`);
|
|
6672
|
+
console.log(` ${ui.muted("Expected a positive number, for example: --dpr 2")}
|
|
6673
|
+
`);
|
|
6674
|
+
process.exit(1);
|
|
6675
|
+
}
|
|
5121
6676
|
showBanner();
|
|
5122
6677
|
const url = args[0];
|
|
5123
6678
|
try {
|
|
5124
|
-
new
|
|
6679
|
+
new URL6(url);
|
|
5125
6680
|
} catch {
|
|
5126
6681
|
console.log(` ${ui.error("\u2717")} ${ui.error.bold("Invalid URL:")} ${ui.text(url)}`);
|
|
5127
6682
|
console.log(` ${ui.muted("Expected: https://yoursite.framer.app")}
|
|
@@ -5134,21 +6689,24 @@ async function main() {
|
|
|
5134
6689
|
const defaultDir = deriveOutputName2(url, detected);
|
|
5135
6690
|
const out = args[1] || `./${defaultDir}`;
|
|
5136
6691
|
try {
|
|
5137
|
-
await new FramerExporter2(
|
|
5138
|
-
|
|
5139
|
-
|
|
6692
|
+
await new FramerExporter2(
|
|
6693
|
+
url,
|
|
6694
|
+
path9.resolve(out),
|
|
6695
|
+
platformOverride || void 0,
|
|
6696
|
+
deviceScaleFactor
|
|
6697
|
+
).run(includeSubpages);
|
|
5140
6698
|
} catch (e) {
|
|
5141
6699
|
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
5142
6700
|
if (e instanceof AntiBotError2) {
|
|
5143
6701
|
console.log(formatAntiBotError2(e));
|
|
5144
6702
|
process.exit(1);
|
|
5145
6703
|
}
|
|
5146
|
-
const
|
|
6704
|
+
const chalk10 = (await import("chalk")).default;
|
|
5147
6705
|
console.log(
|
|
5148
6706
|
`
|
|
5149
6707
|
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`
|
|
5150
6708
|
);
|
|
5151
|
-
console.log(
|
|
6709
|
+
console.log(chalk10.gray(e.stack || ""));
|
|
5152
6710
|
process.exit(1);
|
|
5153
6711
|
}
|
|
5154
6712
|
}
|