framer-export 4.4.2 → 5.0.0-beta.1
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 +10 -8
- package/dist/cli/index.js +2344 -464
- package/dist/cli/index.js.map +1 -1
- package/package.json +13 -5
package/dist/cli/index.js
CHANGED
|
@@ -15,15 +15,15 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "framer-export",
|
|
18
|
-
version: "
|
|
19
|
-
description: "Export
|
|
18
|
+
version: "5.0.0-beta.1",
|
|
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",
|
|
22
22
|
types: "dist/cli/index.d.ts",
|
|
23
23
|
bin: {
|
|
24
|
-
"framer-export": "
|
|
25
|
-
fexport: "
|
|
26
|
-
framerexport: "
|
|
24
|
+
"framer-export": "bin/framer-export.js",
|
|
25
|
+
fexport: "bin/fexport.js",
|
|
26
|
+
framerexport: "bin/framerexport.js"
|
|
27
27
|
},
|
|
28
28
|
files: [
|
|
29
29
|
"dist/",
|
|
@@ -44,6 +44,14 @@ var init_package = __esm({
|
|
|
44
44
|
"framer",
|
|
45
45
|
"webflow",
|
|
46
46
|
"wix",
|
|
47
|
+
"shopify",
|
|
48
|
+
"notion",
|
|
49
|
+
"wordpress",
|
|
50
|
+
"squarespace",
|
|
51
|
+
"ghost",
|
|
52
|
+
"carrd",
|
|
53
|
+
"bubble",
|
|
54
|
+
"gumroad",
|
|
47
55
|
"exporter",
|
|
48
56
|
"export",
|
|
49
57
|
"scraper",
|
|
@@ -164,7 +172,7 @@ function showBanner() {
|
|
|
164
172
|
`
|
|
165
173
|
${ui.primary.bold("f-export")} ${ui.muted(`v${package_default.version}`)} ${chip("beta ui")}`
|
|
166
174
|
);
|
|
167
|
-
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("
|
|
175
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("25+ platforms \xB7 local mirror")}
|
|
168
176
|
`);
|
|
169
177
|
return;
|
|
170
178
|
}
|
|
@@ -177,7 +185,7 @@ function showBanner() {
|
|
|
177
185
|
` ${ui.muted(`v${package_default.version}`)} ${ui.text.bold("Framer Export")} ${chip("fexport")} ${ui.muted("local mirror exporter")}`
|
|
178
186
|
);
|
|
179
187
|
console.log(
|
|
180
|
-
` ${ui.muted("Framer")} ${ui.border("/")} ${ui.muted("Webflow")} ${ui.border("/")} ${ui.muted("Wix")} ${ui.border("\xB7")} ${ui.primary("clean assets")} ${ui.border("\xB7")} ${ui.secondary("local serve")}
|
|
188
|
+
` ${ui.muted("Framer")} ${ui.border("/")} ${ui.muted("Webflow")} ${ui.border("/")} ${ui.muted("Wix")} ${ui.border("/")} ${ui.muted("+22 more")} ${ui.border("\xB7")} ${ui.primary("clean assets")} ${ui.border("\xB7")} ${ui.secondary("local serve")}
|
|
181
189
|
`
|
|
182
190
|
);
|
|
183
191
|
}
|
|
@@ -204,6 +212,1756 @@ var init_banner = __esm({
|
|
|
204
212
|
}
|
|
205
213
|
});
|
|
206
214
|
|
|
215
|
+
// src/platforms/framer.ts
|
|
216
|
+
var FONT_EXTS, framer;
|
|
217
|
+
var init_framer = __esm({
|
|
218
|
+
"src/platforms/framer.ts"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
|
|
221
|
+
framer = {
|
|
222
|
+
name: "framer",
|
|
223
|
+
displayName: "Framer",
|
|
224
|
+
category: "builder",
|
|
225
|
+
priority: 95,
|
|
226
|
+
detectByUrl(url) {
|
|
227
|
+
return /\.framer\.(app|website)|framercanvas\.com/.test(url);
|
|
228
|
+
},
|
|
229
|
+
detectByHtml(html) {
|
|
230
|
+
return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
|
|
231
|
+
},
|
|
232
|
+
stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
|
|
233
|
+
stripSelectors: [
|
|
234
|
+
'script[src*="events.framer.com"]',
|
|
235
|
+
"#__framer-badge-container",
|
|
236
|
+
"#__framer-badge",
|
|
237
|
+
'link[href*="canvas-sandbox"]',
|
|
238
|
+
'script[src*="framer.com/bootstrap"]'
|
|
239
|
+
],
|
|
240
|
+
stripPatterns: [
|
|
241
|
+
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
242
|
+
/<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
243
|
+
],
|
|
244
|
+
hydrationTimeout: 1e4,
|
|
245
|
+
needsHydrationCheck: true,
|
|
246
|
+
mapAssetDir(host, pathname, ext) {
|
|
247
|
+
if (host.includes("framerusercontent.com")) {
|
|
248
|
+
if (pathname.startsWith("/images/")) return "assets/images";
|
|
249
|
+
if (pathname.startsWith("/assets/")) {
|
|
250
|
+
return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
|
|
251
|
+
}
|
|
252
|
+
if (pathname.startsWith("/sites/")) {
|
|
253
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
254
|
+
if (ext === ".json") return "data";
|
|
255
|
+
if (ext === ".css") return "styles";
|
|
256
|
+
if (ext === ".framercms") return "data";
|
|
257
|
+
return "assets/misc";
|
|
258
|
+
}
|
|
259
|
+
if (pathname.startsWith("/modules/")) {
|
|
260
|
+
return ext === ".framercms" ? "data" : "scripts/modules";
|
|
261
|
+
}
|
|
262
|
+
return "assets/misc";
|
|
263
|
+
}
|
|
264
|
+
if (host.includes("app.framerstatic.com")) {
|
|
265
|
+
if (ext === ".css") return "styles";
|
|
266
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
267
|
+
if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
|
|
268
|
+
if (ext === ".png" || ext === ".svg") return "assets/images";
|
|
269
|
+
return "assets/misc";
|
|
270
|
+
}
|
|
271
|
+
if (host.includes("framercanvas.com") || host.includes("framer.com")) {
|
|
272
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
273
|
+
if (ext === ".css") return "styles";
|
|
274
|
+
return "assets/misc";
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// src/platforms/webflow.ts
|
|
283
|
+
var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
|
|
284
|
+
var init_webflow = __esm({
|
|
285
|
+
"src/platforms/webflow.ts"() {
|
|
286
|
+
"use strict";
|
|
287
|
+
IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
|
|
288
|
+
FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
289
|
+
VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
|
|
290
|
+
webflow = {
|
|
291
|
+
name: "webflow",
|
|
292
|
+
displayName: "Webflow",
|
|
293
|
+
category: "builder",
|
|
294
|
+
priority: 90,
|
|
295
|
+
detectByUrl(url) {
|
|
296
|
+
return /\.webflow\.(io|com)/.test(url);
|
|
297
|
+
},
|
|
298
|
+
detectByHtml(html) {
|
|
299
|
+
return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
|
|
300
|
+
},
|
|
301
|
+
stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
|
|
302
|
+
stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
|
|
303
|
+
stripPatterns: [
|
|
304
|
+
/<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
305
|
+
/<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
306
|
+
/<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
|
|
307
|
+
/Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
|
|
308
|
+
/<!-- This site was created in Webflow\.[^>]*-->/g,
|
|
309
|
+
/<html([^>]*) data-wf-domain="[^"]*"/g,
|
|
310
|
+
/<html([^>]*) data-wf-page="[^"]*"/g,
|
|
311
|
+
/<html([^>]*) data-wf-site="[^"]*"/g,
|
|
312
|
+
/<html([^>]*) data-wf-status="[^"]*"/g,
|
|
313
|
+
/<meta[^>]*content="Webflow"[^>]*>/g
|
|
314
|
+
],
|
|
315
|
+
hydrationTimeout: 2e3,
|
|
316
|
+
needsHydrationCheck: false,
|
|
317
|
+
mapAssetDir(host, pathname, ext) {
|
|
318
|
+
if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
|
|
319
|
+
if (VIDEO_EXTS.includes(ext)) return "assets/videos";
|
|
320
|
+
if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
|
|
321
|
+
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
322
|
+
if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
|
|
323
|
+
if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
|
|
324
|
+
if (FONT_EXTS2.includes(ext)) return "assets/fonts";
|
|
325
|
+
return "assets/misc";
|
|
326
|
+
}
|
|
327
|
+
if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
|
|
328
|
+
if (ext === ".js") return "scripts/vendor";
|
|
329
|
+
if (ext === ".css") return "styles";
|
|
330
|
+
if (IMG_EXTS.includes(ext)) return "assets/images";
|
|
331
|
+
return "assets/misc";
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// src/platforms/wix.ts
|
|
340
|
+
var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
|
|
341
|
+
var init_wix = __esm({
|
|
342
|
+
"src/platforms/wix.ts"() {
|
|
343
|
+
"use strict";
|
|
344
|
+
IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
345
|
+
FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
346
|
+
VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
|
|
347
|
+
wix = {
|
|
348
|
+
name: "wix",
|
|
349
|
+
displayName: "Wix",
|
|
350
|
+
category: "builder",
|
|
351
|
+
priority: 88,
|
|
352
|
+
detectByUrl(url) {
|
|
353
|
+
return /\.wixsite\.com|\.wix\.com/.test(url);
|
|
354
|
+
},
|
|
355
|
+
detectByHtml(html) {
|
|
356
|
+
return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk") || html.includes("data-wix") || html.includes("wix-code") || html.includes("WixCode") || /\bwix\.com\b/.test(html) || /meta\s+name=["']generator["']\s+content=["'][^"']*Wix[^"']*["']/i.test(html) || /_wix_/i.test(html) || /wix-ecom/i.test(html);
|
|
357
|
+
},
|
|
358
|
+
stripDomains: [
|
|
359
|
+
"frog.wix.com",
|
|
360
|
+
"bi.wixapi.com",
|
|
361
|
+
"fed.wixcodescheduler.com",
|
|
362
|
+
"editor.wix.com",
|
|
363
|
+
"panorama.wixapps.net"
|
|
364
|
+
],
|
|
365
|
+
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
366
|
+
stripPatterns: [
|
|
367
|
+
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
368
|
+
],
|
|
369
|
+
hydrationTimeout: 3e3,
|
|
370
|
+
needsHydrationCheck: false,
|
|
371
|
+
mapAssetDir(host, pathname, ext) {
|
|
372
|
+
if (host === "video.wixstatic.com") {
|
|
373
|
+
return "assets/videos";
|
|
374
|
+
}
|
|
375
|
+
if (host.includes("wixstatic.com")) {
|
|
376
|
+
if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
|
|
377
|
+
if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
|
|
378
|
+
if (ext === ".css") return "styles";
|
|
379
|
+
if (ext === ".js") return "scripts/vendor";
|
|
380
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
381
|
+
return "assets/misc";
|
|
382
|
+
}
|
|
383
|
+
if (host.includes("parastorage.com")) {
|
|
384
|
+
if (ext === ".js") return "scripts/vendor";
|
|
385
|
+
if (ext === ".css") return "styles";
|
|
386
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
387
|
+
if (IMG_EXTS2.includes(ext)) return "assets/images";
|
|
388
|
+
return "assets/misc";
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// src/platforms/builder/bubble.ts
|
|
397
|
+
var IMG_EXTS3, FONT_EXTS4, VIDEO_EXTS3, bubble;
|
|
398
|
+
var init_bubble = __esm({
|
|
399
|
+
"src/platforms/builder/bubble.ts"() {
|
|
400
|
+
"use strict";
|
|
401
|
+
IMG_EXTS3 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
402
|
+
FONT_EXTS4 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
403
|
+
VIDEO_EXTS3 = [".mp4", ".webm", ".ogg"];
|
|
404
|
+
bubble = {
|
|
405
|
+
name: "bubble",
|
|
406
|
+
displayName: "Bubble",
|
|
407
|
+
category: "builder",
|
|
408
|
+
priority: 85,
|
|
409
|
+
detectByUrl(url) {
|
|
410
|
+
return /\.bubbleapps\.io/i.test(url);
|
|
411
|
+
},
|
|
412
|
+
detectByHtml(html) {
|
|
413
|
+
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
|
+
},
|
|
415
|
+
// Telemetry only (Plausible-based, on standard-plan apps). NEVER the asset CDN.
|
|
416
|
+
stripDomains: ["pluginpul.se"],
|
|
417
|
+
// Post-process removal of any telemetry <script> that survived into the DOM.
|
|
418
|
+
stripSelectors: ['script[src*="pluginpul.se"]'],
|
|
419
|
+
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
|
+
/<a\b[^>]*href="https?:\/\/(?:www\.)?bubble\.io\/?(?:\?[^"]*)?"[^>]*>[\s\S]*?<\/a>/gi
|
|
427
|
+
],
|
|
428
|
+
needsHydrationCheck: true,
|
|
429
|
+
hydrationTimeout: 8e3,
|
|
430
|
+
hydrationSelector: "#main-page",
|
|
431
|
+
captureRenderedDom: true,
|
|
432
|
+
mapAssetDir(host, pathname, ext) {
|
|
433
|
+
if (host.includes("meta-fonts.cdn.bubble.io")) {
|
|
434
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
435
|
+
if (ext === ".css") return "styles";
|
|
436
|
+
if (IMG_EXTS3.includes(ext)) return "assets/images";
|
|
437
|
+
return "assets/misc";
|
|
438
|
+
}
|
|
439
|
+
if (host.includes("cdn.bubble.io")) {
|
|
440
|
+
if (VIDEO_EXTS3.includes(ext)) return "assets/videos";
|
|
441
|
+
if (pathname.includes("/images/") || IMG_EXTS3.includes(ext)) return "assets/images";
|
|
442
|
+
if (ext === ".css") return "styles";
|
|
443
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
444
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
445
|
+
if (ext === ".json") return "data";
|
|
446
|
+
return "assets/misc";
|
|
447
|
+
}
|
|
448
|
+
if (host.includes("d3dqmih97rcqmh.cloudfront.net") || host.includes("d1muf25xaso8hp.cloudfront.net")) {
|
|
449
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
450
|
+
if (ext === ".css") return "styles";
|
|
451
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
452
|
+
if (IMG_EXTS3.includes(ext)) return "assets/images";
|
|
453
|
+
if (ext === ".json") return "data";
|
|
454
|
+
return "assets/misc";
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/platforms/builder/carrd.ts
|
|
463
|
+
var IMG_EXTS4, FONT_EXTS5, VIDEO_EXTS4, carrd;
|
|
464
|
+
var init_carrd = __esm({
|
|
465
|
+
"src/platforms/builder/carrd.ts"() {
|
|
466
|
+
"use strict";
|
|
467
|
+
IMG_EXTS4 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
468
|
+
FONT_EXTS5 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
469
|
+
VIDEO_EXTS4 = [".mp4", ".webm", ".ogg"];
|
|
470
|
+
carrd = {
|
|
471
|
+
name: "carrd",
|
|
472
|
+
displayName: "Carrd",
|
|
473
|
+
category: "builder",
|
|
474
|
+
priority: 85,
|
|
475
|
+
detectByUrl(url) {
|
|
476
|
+
return /\.carrd\.co/i.test(url);
|
|
477
|
+
},
|
|
478
|
+
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
|
+
);
|
|
488
|
+
},
|
|
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
|
+
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
|
+
stripSelectors: ["#credits"],
|
|
496
|
+
stripPatterns: [
|
|
497
|
+
// Free-plan badge paragraph: <p id="credits"><a href="https://carrd.co">…</a></p>
|
|
498
|
+
/<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
|
+
/<a[^>]*href="https:\/\/carrd\.co"[^>]*>\s*\(\s*Made with Carrd\s*\)\s*<\/a>/gi
|
|
502
|
+
],
|
|
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
|
+
hydrationTimeout: 2e3,
|
|
508
|
+
needsHydrationCheck: false,
|
|
509
|
+
mapAssetDir(host, pathname, ext) {
|
|
510
|
+
if (host.includes("fonts.googleapis.com")) {
|
|
511
|
+
return "styles";
|
|
512
|
+
}
|
|
513
|
+
if (host.includes("carrd.co")) {
|
|
514
|
+
if (VIDEO_EXTS4.includes(ext)) return "assets/videos";
|
|
515
|
+
if (pathname.includes("/assets/images/") || IMG_EXTS4.includes(ext)) return "assets/images";
|
|
516
|
+
if (pathname.includes("/assets/css/") || ext === ".css") return "styles";
|
|
517
|
+
if (pathname.includes("/assets/js/") || ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
518
|
+
if (FONT_EXTS5.includes(ext)) return "assets/fonts";
|
|
519
|
+
return "assets/misc";
|
|
520
|
+
}
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// src/platforms/builder/duda.ts
|
|
528
|
+
var IMG_EXTS5, FONT_EXTS6, VIDEO_EXTS5, duda;
|
|
529
|
+
var init_duda = __esm({
|
|
530
|
+
"src/platforms/builder/duda.ts"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
IMG_EXTS5 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
533
|
+
FONT_EXTS6 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
534
|
+
VIDEO_EXTS5 = [".mp4", ".webm", ".ogg"];
|
|
535
|
+
duda = {
|
|
536
|
+
name: "duda",
|
|
537
|
+
displayName: "Duda",
|
|
538
|
+
category: "builder",
|
|
539
|
+
priority: 82,
|
|
540
|
+
detectByUrl(url) {
|
|
541
|
+
return /\.(?:multiscreensite|dudaone)\.com/i.test(url);
|
|
542
|
+
},
|
|
543
|
+
detectByHtml(html) {
|
|
544
|
+
return html.includes("atob('RFVEQU9ORQ==')") || // window.Parameters SiteType → 'DUDAONE'
|
|
545
|
+
html.includes('id="dmRoot"') || html.includes("DM_DIRECT") || // window.Parameters productId
|
|
546
|
+
html.includes("d_track_campaign");
|
|
547
|
+
},
|
|
548
|
+
// No external telemetry/analytics hosts on Duda (tracking is inline, first-party).
|
|
549
|
+
stripDomains: [],
|
|
550
|
+
// Inline first-party tracking <script id="..."> elements (removed by id).
|
|
551
|
+
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
|
+
],
|
|
556
|
+
hydrationTimeout: 0,
|
|
557
|
+
needsHydrationCheck: false,
|
|
558
|
+
mapAssetDir(host, pathname, ext) {
|
|
559
|
+
const isCdnWebsite = host.endsWith(".cdn-website.com");
|
|
560
|
+
const isMsCdn = host === "ms-cdn.multiscreensite.com" || host === "irp-cdn.multiscreensite.com";
|
|
561
|
+
if (!isCdnWebsite && !isMsCdn) return null;
|
|
562
|
+
if (VIDEO_EXTS5.includes(ext)) return "assets/videos";
|
|
563
|
+
if (IMG_EXTS5.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
564
|
+
if (ext === ".css") return "styles";
|
|
565
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
566
|
+
if (FONT_EXTS6.includes(ext)) return "assets/fonts";
|
|
567
|
+
const isResizeProxy = host.startsWith("irp") || host.startsWith("lirp");
|
|
568
|
+
return isResizeProxy ? "assets/images" : "assets/misc";
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// src/platforms/builder/squarespace.ts
|
|
575
|
+
var IMG_EXTS6, FONT_EXTS7, VIDEO_EXTS6, STATIC_EDGE_RE, squarespace;
|
|
576
|
+
var init_squarespace = __esm({
|
|
577
|
+
"src/platforms/builder/squarespace.ts"() {
|
|
578
|
+
"use strict";
|
|
579
|
+
IMG_EXTS6 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
580
|
+
FONT_EXTS7 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
581
|
+
VIDEO_EXTS6 = [".mp4", ".webm", ".ogg"];
|
|
582
|
+
STATIC_EDGE_RE = /^static\d+\.squarespace\.com$/i;
|
|
583
|
+
squarespace = {
|
|
584
|
+
name: "squarespace",
|
|
585
|
+
displayName: "Squarespace",
|
|
586
|
+
category: "builder",
|
|
587
|
+
priority: 82,
|
|
588
|
+
detectByUrl(url) {
|
|
589
|
+
return /\.squarespace\.com/i.test(url);
|
|
590
|
+
},
|
|
591
|
+
detectByHtml(html) {
|
|
592
|
+
return html.includes("<!-- This is Squarespace. -->") || html.includes("Static.SQUARESPACE_CONTEXT") || html.includes("<!-- End of Squarespace Headers -->") || html.includes("images.squarespace-cdn.com");
|
|
593
|
+
},
|
|
594
|
+
// First-party analytics only; no default third-party telemetry host to block.
|
|
595
|
+
stripDomains: [],
|
|
596
|
+
// No editor chrome / badge in public SSR HTML on paid or template sites.
|
|
597
|
+
stripSelectors: [],
|
|
598
|
+
stripPatterns: [],
|
|
599
|
+
hydrationTimeout: 3e3,
|
|
600
|
+
needsHydrationCheck: false,
|
|
601
|
+
mapAssetDir(host, pathname, ext) {
|
|
602
|
+
if (host.includes("images.squarespace-cdn.com")) {
|
|
603
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
604
|
+
if (ext === ".css") return "styles";
|
|
605
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
606
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
607
|
+
if (ext === ".json") return "data";
|
|
608
|
+
return "assets/images";
|
|
609
|
+
}
|
|
610
|
+
if (host.includes("file.squarespace-cdn.com")) {
|
|
611
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
612
|
+
if (IMG_EXTS6.includes(ext)) return "assets/images";
|
|
613
|
+
if (ext === ".css") return "styles";
|
|
614
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
615
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
616
|
+
if (ext === ".json") return "data";
|
|
617
|
+
return "assets/misc";
|
|
618
|
+
}
|
|
619
|
+
if (host.includes("assets.squarespace.com") || STATIC_EDGE_RE.test(host)) {
|
|
620
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
621
|
+
if (ext === ".css") return "styles";
|
|
622
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
623
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
624
|
+
if (ext === ".json") return "data";
|
|
625
|
+
if (IMG_EXTS6.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
626
|
+
return "assets/misc";
|
|
627
|
+
}
|
|
628
|
+
if (host.includes("definitions.sqspcdn.com")) {
|
|
629
|
+
if (ext === ".json") return "data";
|
|
630
|
+
if (ext === ".css") return "styles";
|
|
631
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
632
|
+
return "assets/misc";
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// src/platforms/builder/strikingly.ts
|
|
641
|
+
var IMG_EXTS7, FONT_EXTS8, VIDEO_EXTS7, strikingly;
|
|
642
|
+
var init_strikingly = __esm({
|
|
643
|
+
"src/platforms/builder/strikingly.ts"() {
|
|
644
|
+
"use strict";
|
|
645
|
+
IMG_EXTS7 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
646
|
+
FONT_EXTS8 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
647
|
+
VIDEO_EXTS7 = [".mp4", ".webm", ".ogg"];
|
|
648
|
+
strikingly = {
|
|
649
|
+
name: "strikingly",
|
|
650
|
+
displayName: "Strikingly",
|
|
651
|
+
category: "builder",
|
|
652
|
+
priority: 85,
|
|
653
|
+
detectByUrl(url) {
|
|
654
|
+
return /\.mystrikingly\.com/i.test(url);
|
|
655
|
+
},
|
|
656
|
+
detectByHtml(html) {
|
|
657
|
+
return html.includes("strikingly-host-suffix") || html.includes('data-hypernova-key="SiteBootstrapper"') || html.includes('id="s-page-client-container"') || html.includes("strikinglycdn.com");
|
|
658
|
+
},
|
|
659
|
+
// Telemetry / error-monitoring only — NEVER the strikinglycdn asset CDN.
|
|
660
|
+
stripDomains: [
|
|
661
|
+
"google-analytics.com",
|
|
662
|
+
"www.google-analytics.com",
|
|
663
|
+
"notify.bugsnag.com",
|
|
664
|
+
"sessions.bugsnag.com"
|
|
665
|
+
],
|
|
666
|
+
// The "Powered by Strikingly" footer section carries the unique class
|
|
667
|
+
// `.show-strikingly-logo` (inside #s-footer-section-container).
|
|
668
|
+
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.
|
|
674
|
+
hydrationTimeout: 3e3,
|
|
675
|
+
needsHydrationCheck: false,
|
|
676
|
+
hydrationSelector: "#s-page-client-container",
|
|
677
|
+
captureRenderedDom: true,
|
|
678
|
+
mapAssetDir(host, pathname, ext) {
|
|
679
|
+
if (host.includes("strikinglycdn.com")) {
|
|
680
|
+
if (VIDEO_EXTS7.includes(ext)) return "assets/videos";
|
|
681
|
+
if (host.includes("static-fonts-css")) {
|
|
682
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
683
|
+
if (ext === ".css") return "styles";
|
|
684
|
+
}
|
|
685
|
+
if (host.includes("static-assets")) {
|
|
686
|
+
if (ext === ".css") return "styles";
|
|
687
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
688
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
689
|
+
if (IMG_EXTS7.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
690
|
+
return "assets/misc";
|
|
691
|
+
}
|
|
692
|
+
if (IMG_EXTS7.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
693
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
694
|
+
if (ext === ".css") return "styles";
|
|
695
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
696
|
+
return "assets/misc";
|
|
697
|
+
}
|
|
698
|
+
if (host.includes("cdnjs.cloudflare.com")) {
|
|
699
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
700
|
+
if (ext === ".css") return "styles";
|
|
701
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
702
|
+
if (IMG_EXTS7.includes(ext)) return "assets/images";
|
|
703
|
+
return "assets/misc";
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// src/platforms/builder/tilda.ts
|
|
712
|
+
var IMG_EXTS8, FONT_EXTS9, VIDEO_EXTS8, tilda;
|
|
713
|
+
var init_tilda = __esm({
|
|
714
|
+
"src/platforms/builder/tilda.ts"() {
|
|
715
|
+
"use strict";
|
|
716
|
+
IMG_EXTS8 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
717
|
+
FONT_EXTS9 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
718
|
+
VIDEO_EXTS8 = [".mp4", ".webm", ".ogg"];
|
|
719
|
+
tilda = {
|
|
720
|
+
name: "tilda",
|
|
721
|
+
displayName: "Tilda",
|
|
722
|
+
category: "builder",
|
|
723
|
+
priority: 80,
|
|
724
|
+
detectByUrl(url) {
|
|
725
|
+
return /\.tilda\.ws/i.test(url);
|
|
726
|
+
},
|
|
727
|
+
detectByHtml(html) {
|
|
728
|
+
return html.includes("data-tilda-project-id") || html.includes("data-tilda-page-id") || html.includes("tildacdn") || html.includes('id="allrecords"');
|
|
729
|
+
},
|
|
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
|
+
stripDomains: ["www.google-analytics.com", "google-analytics.com"],
|
|
733
|
+
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
|
+
],
|
|
739
|
+
hydrationTimeout: 0,
|
|
740
|
+
needsHydrationCheck: false,
|
|
741
|
+
mapAssetDir(host, pathname, ext) {
|
|
742
|
+
if (host.includes("tildacdn")) {
|
|
743
|
+
if (host.startsWith("thb.")) return "assets/images";
|
|
744
|
+
if (VIDEO_EXTS8.includes(ext)) return "assets/videos";
|
|
745
|
+
if (ext === ".css" || pathname.includes("/css/")) return "styles";
|
|
746
|
+
if (ext === ".js" || ext === ".mjs" || pathname.includes("/js/")) return "scripts/vendor";
|
|
747
|
+
if (FONT_EXTS9.includes(ext) || pathname.includes("/fonts/")) return "assets/fonts";
|
|
748
|
+
if (ext === ".json") return "data";
|
|
749
|
+
if (IMG_EXTS8.includes(ext) || pathname.includes("/img/") || pathname.includes("/images/") || pathname.includes("/tild")) {
|
|
750
|
+
return "assets/images";
|
|
751
|
+
}
|
|
752
|
+
return "assets/misc";
|
|
753
|
+
}
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// src/platforms/builder/weebly.ts
|
|
761
|
+
var IMG_EXTS9, FONT_EXTS10, VIDEO_EXTS9, weebly;
|
|
762
|
+
var init_weebly = __esm({
|
|
763
|
+
"src/platforms/builder/weebly.ts"() {
|
|
764
|
+
"use strict";
|
|
765
|
+
IMG_EXTS9 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
766
|
+
FONT_EXTS10 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
767
|
+
VIDEO_EXTS9 = [".mp4", ".webm", ".ogg"];
|
|
768
|
+
weebly = {
|
|
769
|
+
name: "weebly",
|
|
770
|
+
displayName: "Weebly",
|
|
771
|
+
category: "builder",
|
|
772
|
+
priority: 82,
|
|
773
|
+
detectByUrl(url) {
|
|
774
|
+
return /\.weebly\.com/i.test(url);
|
|
775
|
+
},
|
|
776
|
+
detectByHtml(html) {
|
|
777
|
+
return html.includes("Weebly.footer.setupContainer") || html.includes("weebly-footer-signup-container-v3") || html.includes("wsite-content") || html.includes("window._W");
|
|
778
|
+
},
|
|
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
|
+
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
|
+
stripSelectors: ["#weebly-footer-signup-container-v3", 'script[src*="footerSignup.js"]'],
|
|
787
|
+
// Best-effort removal of the rendered badge anchor should it appear inline.
|
|
788
|
+
stripPatterns: [/<a[^>]*href="[^"]*weebly\.com\/signup[^"]*"[^>]*>[\s\S]*?<\/a>/g],
|
|
789
|
+
hydrationTimeout: 0,
|
|
790
|
+
needsHydrationCheck: false,
|
|
791
|
+
mapAssetDir(host, pathname, ext) {
|
|
792
|
+
if (host.includes("editmysite.com")) {
|
|
793
|
+
if (VIDEO_EXTS9.includes(ext)) return "assets/videos";
|
|
794
|
+
if (pathname.includes("/images/") || IMG_EXTS9.includes(ext)) return "assets/images";
|
|
795
|
+
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
796
|
+
if (pathname.includes("/js/") || ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
797
|
+
if (FONT_EXTS10.includes(ext)) return "assets/fonts";
|
|
798
|
+
return "assets/misc";
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// src/platforms/landing/clickfunnels.ts
|
|
807
|
+
var IMG_EXTS10, FONT_EXTS11, VIDEO_EXTS10, clickfunnels;
|
|
808
|
+
var init_clickfunnels = __esm({
|
|
809
|
+
"src/platforms/landing/clickfunnels.ts"() {
|
|
810
|
+
"use strict";
|
|
811
|
+
IMG_EXTS10 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
812
|
+
FONT_EXTS11 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
813
|
+
VIDEO_EXTS10 = [".mp4", ".webm", ".ogg"];
|
|
814
|
+
clickfunnels = {
|
|
815
|
+
name: "clickfunnels",
|
|
816
|
+
displayName: 'ClickFunnels (2.0 / "CF2")',
|
|
817
|
+
category: "landing",
|
|
818
|
+
priority: 82,
|
|
819
|
+
detectByUrl(url) {
|
|
820
|
+
return /\.myclickfunnels\.com/i.test(url);
|
|
821
|
+
},
|
|
822
|
+
detectByHtml(html) {
|
|
823
|
+
return html.includes("cf-head-scripts") || html.includes("window.cfRootDomain") || html.includes("cf-lander-serialized-custom-fonts") || html.includes("data-page-element");
|
|
824
|
+
},
|
|
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
|
+
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
|
+
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
|
+
stripPatterns: [],
|
|
839
|
+
// ssr-static: SSR body is complete, no hydration wait needed.
|
|
840
|
+
hydrationTimeout: 0,
|
|
841
|
+
needsHydrationCheck: false,
|
|
842
|
+
mapAssetDir(host, pathname, ext) {
|
|
843
|
+
if (host.includes("statics.myclickfunnels.com")) {
|
|
844
|
+
if (VIDEO_EXTS10.includes(ext)) return "assets/videos";
|
|
845
|
+
if (pathname.includes("/image/") || pathname.includes("/images/") || IMG_EXTS10.includes(ext))
|
|
846
|
+
return "assets/images";
|
|
847
|
+
if (ext === ".css") return "styles";
|
|
848
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
849
|
+
if (FONT_EXTS11.includes(ext)) return "assets/fonts";
|
|
850
|
+
return "assets/misc";
|
|
851
|
+
}
|
|
852
|
+
if (host.includes("images.clickfunnels.com")) {
|
|
853
|
+
if (VIDEO_EXTS10.includes(ext)) return "assets/videos";
|
|
854
|
+
if (FONT_EXTS11.includes(ext)) return "assets/fonts";
|
|
855
|
+
return "assets/images";
|
|
856
|
+
}
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// src/platforms/landing/elementor.ts
|
|
864
|
+
var IMG_EXTS11, FONT_EXTS12, VIDEO_EXTS11, elementor;
|
|
865
|
+
var init_elementor = __esm({
|
|
866
|
+
"src/platforms/landing/elementor.ts"() {
|
|
867
|
+
"use strict";
|
|
868
|
+
IMG_EXTS11 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
869
|
+
FONT_EXTS12 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
870
|
+
VIDEO_EXTS11 = [".mp4", ".webm", ".ogg"];
|
|
871
|
+
elementor = {
|
|
872
|
+
name: "elementor",
|
|
873
|
+
displayName: "Elementor",
|
|
874
|
+
category: "landing",
|
|
875
|
+
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
|
+
detectByUrl(url) {
|
|
879
|
+
return /\.elementor\.cloud/i.test(url);
|
|
880
|
+
},
|
|
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
|
+
detectByHtml(html) {
|
|
888
|
+
return html.includes("/wp-content/plugins/elementor/") || html.includes("elementor-kit-") || html.includes("data-elementor-type=") || html.includes("elementorFrontendConfig");
|
|
889
|
+
},
|
|
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
|
+
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
|
+
stripDomains: [],
|
|
897
|
+
// No front-end editor chrome or badge element to remove.
|
|
898
|
+
stripSelectors: [],
|
|
899
|
+
// Verified: no "Made with Elementor" badge in the front-end DOM → nothing to strip.
|
|
900
|
+
stripPatterns: [],
|
|
901
|
+
hydrationTimeout: 0,
|
|
902
|
+
needsHydrationCheck: false,
|
|
903
|
+
mapAssetDir(host, pathname, ext) {
|
|
904
|
+
if (!pathname.includes("/wp-content/") && !pathname.includes("/wp-includes/")) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
if (VIDEO_EXTS11.includes(ext)) return "assets/videos";
|
|
908
|
+
if (ext === ".css") return "styles";
|
|
909
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
910
|
+
if (FONT_EXTS12.includes(ext)) return "assets/fonts";
|
|
911
|
+
if (IMG_EXTS11.includes(ext)) return "assets/images";
|
|
912
|
+
if (ext === ".json") return "data";
|
|
913
|
+
return "assets/misc";
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// src/platforms/landing/instapage.ts
|
|
920
|
+
var IMG_EXTS12, FONT_EXTS13, VIDEO_EXTS12, instapage;
|
|
921
|
+
var init_instapage = __esm({
|
|
922
|
+
"src/platforms/landing/instapage.ts"() {
|
|
923
|
+
"use strict";
|
|
924
|
+
IMG_EXTS12 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
925
|
+
FONT_EXTS13 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
926
|
+
VIDEO_EXTS12 = [".mp4", ".webm", ".ogg"];
|
|
927
|
+
instapage = {
|
|
928
|
+
name: "instapage",
|
|
929
|
+
displayName: "Instapage",
|
|
930
|
+
category: "landing",
|
|
931
|
+
priority: 82,
|
|
932
|
+
detectByUrl(url) {
|
|
933
|
+
return /\.(?:pagedemo|pageserve)\.co/i.test(url);
|
|
934
|
+
},
|
|
935
|
+
detectByHtml(html) {
|
|
936
|
+
return html.includes("instapageSp") || html.includes("_instapageSnowplow") || html.includes("anthill.instapage.com") || html.includes("pageserver.page2");
|
|
937
|
+
},
|
|
938
|
+
// Telemetry / analytics hosts only — blocked during capture. The fastcdn.co
|
|
939
|
+
// asset CDN is deliberately NOT listed here (those assets are mirrored).
|
|
940
|
+
stripDomains: [
|
|
941
|
+
"anthill.instapage.com",
|
|
942
|
+
"cdn.instapagemetrics.com",
|
|
943
|
+
"heatmap-events-collector.instapage.com",
|
|
944
|
+
"heatmap.services"
|
|
945
|
+
],
|
|
946
|
+
stripSelectors: [
|
|
947
|
+
'script[src*="instapagemetrics.com"]',
|
|
948
|
+
'script[src*="anthill.instapage.com"]',
|
|
949
|
+
'script[src*="heatmap.services"]'
|
|
950
|
+
],
|
|
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
|
+
],
|
|
956
|
+
hydrationTimeout: 0,
|
|
957
|
+
needsHydrationCheck: false,
|
|
958
|
+
mapAssetDir(host, pathname, ext) {
|
|
959
|
+
if (host.includes("fastcdn.co")) {
|
|
960
|
+
if (VIDEO_EXTS12.includes(ext)) return "assets/videos";
|
|
961
|
+
if (IMG_EXTS12.includes(ext) || pathname.includes("/u/")) return "assets/images";
|
|
962
|
+
if (ext === ".css") return "styles";
|
|
963
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
964
|
+
if (FONT_EXTS13.includes(ext)) return "assets/fonts";
|
|
965
|
+
if (ext === ".json") return "data";
|
|
966
|
+
return "assets/misc";
|
|
967
|
+
}
|
|
968
|
+
if (host.includes("d3mwhxgzltpnyp.cloudfront.net")) {
|
|
969
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
970
|
+
if (ext === ".css") return "styles";
|
|
971
|
+
if (IMG_EXTS12.includes(ext)) return "assets/images";
|
|
972
|
+
if (FONT_EXTS13.includes(ext)) return "assets/fonts";
|
|
973
|
+
return "assets/misc";
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// src/platforms/landing/systemeio.ts
|
|
982
|
+
var IMG_EXTS13, FONT_EXTS14, VIDEO_EXTS13, systemeio;
|
|
983
|
+
var init_systemeio = __esm({
|
|
984
|
+
"src/platforms/landing/systemeio.ts"() {
|
|
985
|
+
"use strict";
|
|
986
|
+
IMG_EXTS13 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
987
|
+
FONT_EXTS14 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
988
|
+
VIDEO_EXTS13 = [".mp4", ".webm", ".ogg"];
|
|
989
|
+
systemeio = {
|
|
990
|
+
name: "systemeio",
|
|
991
|
+
displayName: "Systeme.io",
|
|
992
|
+
category: "landing",
|
|
993
|
+
priority: 80,
|
|
994
|
+
detectByUrl(url) {
|
|
995
|
+
return /\.systeme\.io/i.test(url);
|
|
996
|
+
},
|
|
997
|
+
detectByHtml(html) {
|
|
998
|
+
return html.includes("<!-- Created with https://systeme.io -->") || html.includes("d3fit27i5nzkqh.cloudfront.net") || html.includes("systeme.io/?sa=") || html.includes("d3syewzhvzylbl.cloudfront.net");
|
|
999
|
+
},
|
|
1000
|
+
// First-party click/funnel telemetry — blocked during capture, never downloaded.
|
|
1001
|
+
stripDomains: ["log.systeme.io"],
|
|
1002
|
+
stripSelectors: ['script[src*="log.systeme.io"]'],
|
|
1003
|
+
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
|
+
/<a[^>]*href="[^"]*systeme\.io\/\?sa=[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1007
|
+
// Branding comment injected right after <!DOCTYPE html>.
|
|
1008
|
+
/<!--\s*Created with https:\/\/systeme\.io\s*-->/g
|
|
1009
|
+
],
|
|
1010
|
+
hydrationTimeout: 0,
|
|
1011
|
+
needsHydrationCheck: false,
|
|
1012
|
+
mapAssetDir(host, pathname, ext) {
|
|
1013
|
+
if (host.includes("d3fit27i5nzkqh.cloudfront.net")) {
|
|
1014
|
+
if (ext === ".css") return "styles";
|
|
1015
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1016
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1017
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1018
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1019
|
+
if (ext === ".json") return "data";
|
|
1020
|
+
return "assets/misc";
|
|
1021
|
+
}
|
|
1022
|
+
if (host.includes("d3syewzhvzylbl.cloudfront.net")) {
|
|
1023
|
+
if (FONT_EXTS14.includes(ext) || pathname.includes("/fonts/")) return "assets/fonts";
|
|
1024
|
+
if (ext === ".css") return "styles";
|
|
1025
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1026
|
+
return "assets/misc";
|
|
1027
|
+
}
|
|
1028
|
+
if (host.includes("d1yei2z3i6k35z.cloudfront.net")) {
|
|
1029
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1030
|
+
if (IMG_EXTS13.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1031
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1032
|
+
if (ext === ".css") return "styles";
|
|
1033
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1034
|
+
return "assets/misc";
|
|
1035
|
+
}
|
|
1036
|
+
if (host.includes("d2543nuuc0wvdg.cloudfront.net")) {
|
|
1037
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1038
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1039
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1040
|
+
if (ext === ".css") return "styles";
|
|
1041
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1042
|
+
return "assets/misc";
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// src/platforms/landing/unbounce.ts
|
|
1051
|
+
var IMG_EXTS14, FONT_EXTS15, VIDEO_EXTS14, unbounce;
|
|
1052
|
+
var init_unbounce = __esm({
|
|
1053
|
+
"src/platforms/landing/unbounce.ts"() {
|
|
1054
|
+
"use strict";
|
|
1055
|
+
IMG_EXTS14 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1056
|
+
FONT_EXTS15 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1057
|
+
VIDEO_EXTS14 = [".mp4", ".webm", ".ogg"];
|
|
1058
|
+
unbounce = {
|
|
1059
|
+
name: "unbounce",
|
|
1060
|
+
displayName: "Unbounce",
|
|
1061
|
+
category: "landing",
|
|
1062
|
+
priority: 82,
|
|
1063
|
+
detectByUrl(url) {
|
|
1064
|
+
return /\.(?:unbouncepages|ubpages)\.com/i.test(url);
|
|
1065
|
+
},
|
|
1066
|
+
detectByHtml(html) {
|
|
1067
|
+
return html.includes("lp-pom-root") || html.includes("window.ub.page") || html.includes("builder-assets.unbounce.com") || html.includes("ubembed.com");
|
|
1068
|
+
},
|
|
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
|
+
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
|
+
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
|
+
],
|
|
1081
|
+
hydrationTimeout: 0,
|
|
1082
|
+
needsHydrationCheck: false,
|
|
1083
|
+
mapAssetDir(host, pathname, ext) {
|
|
1084
|
+
if (host.includes("builder-assets.unbounce.com") || host.includes("assets.unbounce.com")) {
|
|
1085
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1086
|
+
if (IMG_EXTS14.includes(ext) || pathname.includes("/lp-image")) return "assets/images";
|
|
1087
|
+
if (ext === ".css") return "styles";
|
|
1088
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1089
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1090
|
+
if (ext === ".json") return "data";
|
|
1091
|
+
return "assets/misc";
|
|
1092
|
+
}
|
|
1093
|
+
if (host.includes("ubembed.com")) {
|
|
1094
|
+
if (ext === ".css") return "styles";
|
|
1095
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1096
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1097
|
+
if (IMG_EXTS14.includes(ext)) return "assets/images";
|
|
1098
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1099
|
+
return "assets/misc";
|
|
1100
|
+
}
|
|
1101
|
+
if (host.includes("cloudfront.net")) {
|
|
1102
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1103
|
+
if (IMG_EXTS14.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1104
|
+
if (ext === ".css") return "styles";
|
|
1105
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1106
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1107
|
+
return "assets/misc";
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// src/platforms/cms/ghost.ts
|
|
1116
|
+
var IMG_EXTS15, FONT_EXTS16, VIDEO_EXTS15, ghost;
|
|
1117
|
+
var init_ghost = __esm({
|
|
1118
|
+
"src/platforms/cms/ghost.ts"() {
|
|
1119
|
+
"use strict";
|
|
1120
|
+
IMG_EXTS15 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1121
|
+
FONT_EXTS16 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1122
|
+
VIDEO_EXTS15 = [".mp4", ".webm", ".ogg"];
|
|
1123
|
+
ghost = {
|
|
1124
|
+
name: "ghost",
|
|
1125
|
+
displayName: "Ghost",
|
|
1126
|
+
category: "cms",
|
|
1127
|
+
priority: 78,
|
|
1128
|
+
detectByUrl(url) {
|
|
1129
|
+
return /\.ghost\.io/i.test(url);
|
|
1130
|
+
},
|
|
1131
|
+
detectByHtml(html) {
|
|
1132
|
+
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
|
+
},
|
|
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
|
+
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
|
+
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
|
+
stripSelectors: [],
|
|
1147
|
+
stripPatterns: [
|
|
1148
|
+
// <div class="gh-powered-by"><a href="https://ghost.org/" ...>Powered by Ghost</a></div>
|
|
1149
|
+
/<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
|
+
/<a[^>]*href="[^"]*ghost\.org[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1152
|
+
// Identifying <meta name="generator" content="Ghost X.Y">.
|
|
1153
|
+
/<meta[^>]*name="generator"[^>]*content="Ghost[^"]*"[^>]*>/gi
|
|
1154
|
+
],
|
|
1155
|
+
hydrationTimeout: 0,
|
|
1156
|
+
needsHydrationCheck: false,
|
|
1157
|
+
scrollStrategy: "paginated",
|
|
1158
|
+
mapAssetDir(host, pathname, ext) {
|
|
1159
|
+
if (host.includes("storage.ghost.io")) {
|
|
1160
|
+
if (VIDEO_EXTS15.includes(ext)) return "assets/videos";
|
|
1161
|
+
if (pathname.includes("/images/") || IMG_EXTS15.includes(ext)) return "assets/images";
|
|
1162
|
+
if (FONT_EXTS16.includes(ext)) return "assets/fonts";
|
|
1163
|
+
if (ext === ".css") return "styles";
|
|
1164
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1165
|
+
return "assets/misc";
|
|
1166
|
+
}
|
|
1167
|
+
if (host.includes("cdn.jsdelivr.net")) {
|
|
1168
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1169
|
+
if (ext === ".css") return "styles";
|
|
1170
|
+
if (FONT_EXTS16.includes(ext)) return "assets/fonts";
|
|
1171
|
+
if (IMG_EXTS15.includes(ext)) return "assets/images";
|
|
1172
|
+
return "assets/misc";
|
|
1173
|
+
}
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// src/platforms/cms/notion.ts
|
|
1181
|
+
var IMG_EXTS16, FONT_EXTS17, VIDEO_EXTS16, notion;
|
|
1182
|
+
var init_notion = __esm({
|
|
1183
|
+
"src/platforms/cms/notion.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
IMG_EXTS16 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1186
|
+
FONT_EXTS17 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1187
|
+
VIDEO_EXTS16 = [".mp4", ".webm", ".ogg"];
|
|
1188
|
+
notion = {
|
|
1189
|
+
name: "notion",
|
|
1190
|
+
displayName: "Notion (Notion Sites)",
|
|
1191
|
+
category: "cms",
|
|
1192
|
+
priority: 85,
|
|
1193
|
+
detectByUrl(url) {
|
|
1194
|
+
return /\.notion\.site/i.test(url);
|
|
1195
|
+
},
|
|
1196
|
+
detectByHtml(html) {
|
|
1197
|
+
return html.includes('data-notion-html="web"') || html.includes('id="notion-app"') || html.includes("__notion_html_async") || html.includes("window.__notion_boot_data");
|
|
1198
|
+
},
|
|
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
|
+
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
|
+
stripSelectors: ["#initial-loading-spinner", "#skeleton", "#skeleton-sidebar"],
|
|
1206
|
+
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
|
+
/<a[^>]*href="[^"]*notion\.(so|com)[^"]*"[^>]*>[\s\S]*?Built with Notion[\s\S]*?<\/a>/gi
|
|
1212
|
+
],
|
|
1213
|
+
hydrationTimeout: 8e3,
|
|
1214
|
+
needsHydrationCheck: true,
|
|
1215
|
+
hydrationSelector: "#notion-app",
|
|
1216
|
+
captureRenderedDom: true,
|
|
1217
|
+
mapAssetDir(host, pathname, ext) {
|
|
1218
|
+
if (host.includes("img.notionusercontent.com") || host.includes("app.notion.com")) {
|
|
1219
|
+
if (VIDEO_EXTS16.includes(ext)) return "assets/videos";
|
|
1220
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1221
|
+
if (IMG_EXTS16.includes(ext) || pathname.includes("/image")) return "assets/images";
|
|
1222
|
+
return "assets/images";
|
|
1223
|
+
}
|
|
1224
|
+
if (host.includes("prod-files-secure.s3.us-west-2.amazonaws.com") || host === "s3.us-west-2.amazonaws.com") {
|
|
1225
|
+
if (VIDEO_EXTS16.includes(ext)) return "assets/videos";
|
|
1226
|
+
if (IMG_EXTS16.includes(ext)) return "assets/images";
|
|
1227
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1228
|
+
if (ext === ".css") return "styles";
|
|
1229
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1230
|
+
return "assets/misc";
|
|
1231
|
+
}
|
|
1232
|
+
if (host.includes("www.notion.so") || host.includes("notion.so")) {
|
|
1233
|
+
if (ext === ".css") return "styles";
|
|
1234
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1235
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1236
|
+
if (IMG_EXTS16.includes(ext)) return "assets/images";
|
|
1237
|
+
if (ext === ".json") return "data";
|
|
1238
|
+
return "assets/misc";
|
|
1239
|
+
}
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// src/platforms/cms/wordpress.ts
|
|
1247
|
+
var IMG_EXTS17, FONT_EXTS18, VIDEO_EXTS17, wordpress;
|
|
1248
|
+
var init_wordpress = __esm({
|
|
1249
|
+
"src/platforms/cms/wordpress.ts"() {
|
|
1250
|
+
"use strict";
|
|
1251
|
+
IMG_EXTS17 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1252
|
+
FONT_EXTS18 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1253
|
+
VIDEO_EXTS17 = [".mp4", ".webm", ".ogg"];
|
|
1254
|
+
wordpress = {
|
|
1255
|
+
name: "wordpress",
|
|
1256
|
+
displayName: "WordPress",
|
|
1257
|
+
category: "cms",
|
|
1258
|
+
priority: 40,
|
|
1259
|
+
detectByUrl(url) {
|
|
1260
|
+
return /\.wordpress\.com|\/wp-(content|includes|json)\//i.test(url);
|
|
1261
|
+
},
|
|
1262
|
+
detectByHtml(html) {
|
|
1263
|
+
return html.includes("/wp-includes/") || html.includes("/wp-content/") || html.includes("api.w.org") || html.includes("_wpemojiSettings");
|
|
1264
|
+
},
|
|
1265
|
+
// Matched against the <meta name="generator"> content only. Covers both
|
|
1266
|
+
// "WordPress <version>" (self-hosted) and exact "WordPress.com" (hosted).
|
|
1267
|
+
detectGenerator: /WordPress/i,
|
|
1268
|
+
// Jetpack / WP.com Stats trackers only — never the asset CDN.
|
|
1269
|
+
stripDomains: ["stats.wp.com", "pixel.wp.com"],
|
|
1270
|
+
// Remove the leftover tracker <script> tags after their requests are blocked.
|
|
1271
|
+
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
|
+
stripPatterns: [
|
|
1275
|
+
/<meta[^>]*name=["']generator["'][^>]*content=["'][^"']*WordPress[^"']*["'][^>]*>/gi
|
|
1276
|
+
],
|
|
1277
|
+
hydrationTimeout: 0,
|
|
1278
|
+
needsHydrationCheck: false,
|
|
1279
|
+
mapAssetDir(host, pathname, ext) {
|
|
1280
|
+
if (host.includes("i0.wp.com") || host.includes("i1.wp.com") || host.includes("i2.wp.com")) {
|
|
1281
|
+
return "assets/images";
|
|
1282
|
+
}
|
|
1283
|
+
if (host.includes("gravatar.com")) {
|
|
1284
|
+
return "assets/images";
|
|
1285
|
+
}
|
|
1286
|
+
if (host.includes("fonts.wp.com")) {
|
|
1287
|
+
if (ext === ".css") return "styles";
|
|
1288
|
+
if (IMG_EXTS17.includes(ext)) return "assets/images";
|
|
1289
|
+
return "assets/fonts";
|
|
1290
|
+
}
|
|
1291
|
+
if (host.includes("s0.wp.com") || host.includes("s1.wp.com") || host.includes("s2.wp.com") || host.includes("c0.wp.com") || host.includes("s.w.org")) {
|
|
1292
|
+
if (VIDEO_EXTS17.includes(ext)) return "assets/videos";
|
|
1293
|
+
if (IMG_EXTS17.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1294
|
+
if (ext === ".css") return "styles";
|
|
1295
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1296
|
+
if (FONT_EXTS18.includes(ext)) return "assets/fonts";
|
|
1297
|
+
return "assets/misc";
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// src/platforms/course/kajabi.ts
|
|
1306
|
+
var IMG_EXTS18, FONT_EXTS19, VIDEO_EXTS18, kajabi;
|
|
1307
|
+
var init_kajabi = __esm({
|
|
1308
|
+
"src/platforms/course/kajabi.ts"() {
|
|
1309
|
+
"use strict";
|
|
1310
|
+
IMG_EXTS18 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1311
|
+
FONT_EXTS19 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1312
|
+
VIDEO_EXTS18 = [".mp4", ".webm", ".ogg"];
|
|
1313
|
+
kajabi = {
|
|
1314
|
+
name: "kajabi",
|
|
1315
|
+
displayName: "Kajabi",
|
|
1316
|
+
category: "course",
|
|
1317
|
+
priority: 85,
|
|
1318
|
+
detectByUrl(url) {
|
|
1319
|
+
return /\.mykajabi\.com/i.test(url);
|
|
1320
|
+
},
|
|
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
|
+
detectByHtml(html) {
|
|
1325
|
+
return html.includes("var Kajabi = Kajabi") || // inline bootstrap: `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");
|
|
1329
|
+
},
|
|
1330
|
+
// Telemetry / analytics hosts only — NEVER the kajabi-cdn.com asset hosts.
|
|
1331
|
+
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
|
+
stripSelectors: [".footer__powered-by"],
|
|
1335
|
+
stripPatterns: [
|
|
1336
|
+
// Whole badge wrapper: <div class="footer__powered-by"> … </div>
|
|
1337
|
+
// (real markup has indentation/newlines between the div and the anchor).
|
|
1338
|
+
/<div[^>]*class="[^"]*footer__powered-by[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1339
|
+
// Belt-and-suspenders: the branded referral anchor itself.
|
|
1340
|
+
/<a[^>]*href="[^"]*app\.kajabi\.com\/r\/[^"]*powered_by=true[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1341
|
+
],
|
|
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
|
+
hydrationTimeout: 2e3,
|
|
1347
|
+
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
|
+
mapAssetDir(host, pathname, ext) {
|
|
1353
|
+
if (host.includes("kajabi-cdn.com")) {
|
|
1354
|
+
if (VIDEO_EXTS18.includes(ext)) return "assets/videos";
|
|
1355
|
+
if (pathname.includes("/images/") || IMG_EXTS18.includes(ext)) return "assets/images";
|
|
1356
|
+
if (ext === ".css") return "styles";
|
|
1357
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1358
|
+
if (FONT_EXTS19.includes(ext)) return "assets/fonts";
|
|
1359
|
+
if (ext === ".json") return "data";
|
|
1360
|
+
return "assets/misc";
|
|
1361
|
+
}
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
// src/platforms/course/podia.ts
|
|
1369
|
+
var IMG_EXTS19, FONT_EXTS20, VIDEO_EXTS19, podia;
|
|
1370
|
+
var init_podia = __esm({
|
|
1371
|
+
"src/platforms/course/podia.ts"() {
|
|
1372
|
+
"use strict";
|
|
1373
|
+
IMG_EXTS19 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1374
|
+
FONT_EXTS20 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1375
|
+
VIDEO_EXTS19 = [".mp4", ".webm", ".ogg"];
|
|
1376
|
+
podia = {
|
|
1377
|
+
name: "podia",
|
|
1378
|
+
displayName: "Podia",
|
|
1379
|
+
category: "course",
|
|
1380
|
+
priority: 80,
|
|
1381
|
+
detectByUrl(url) {
|
|
1382
|
+
return /\.podia\.com/i.test(url);
|
|
1383
|
+
},
|
|
1384
|
+
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
|
+
);
|
|
1392
|
+
},
|
|
1393
|
+
// Telemetry only — NEVER the asset CDN. Podia's default analytics is Google Tag Manager.
|
|
1394
|
+
stripDomains: ["www.googletagmanager.com"],
|
|
1395
|
+
stripSelectors: ['script[src*="googletagmanager.com"]'],
|
|
1396
|
+
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
|
+
/<a[^>]*href=['"][^'"]*podia\.com[^'"]*utm_medium=poweredby[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1403
|
+
],
|
|
1404
|
+
hydrationTimeout: 4e3,
|
|
1405
|
+
needsHydrationCheck: true,
|
|
1406
|
+
// Content lives in these React islands rather than a #main root.
|
|
1407
|
+
hydrationSelector: ".react-page-section",
|
|
1408
|
+
captureRenderedDom: true,
|
|
1409
|
+
mapAssetDir(host, pathname, ext) {
|
|
1410
|
+
if (host.includes("cdn.podia.com")) {
|
|
1411
|
+
if (VIDEO_EXTS19.includes(ext)) return "assets/videos";
|
|
1412
|
+
if (ext === ".css") return "styles";
|
|
1413
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1414
|
+
if (FONT_EXTS20.includes(ext)) return "assets/fonts";
|
|
1415
|
+
if (IMG_EXTS19.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1416
|
+
return "assets/misc";
|
|
1417
|
+
}
|
|
1418
|
+
if (host.includes(".podia.com") && pathname.includes("/content-assets/")) {
|
|
1419
|
+
if (VIDEO_EXTS19.includes(ext)) return "assets/videos";
|
|
1420
|
+
if (IMG_EXTS19.includes(ext)) return "assets/images";
|
|
1421
|
+
if (FONT_EXTS20.includes(ext)) return "assets/fonts";
|
|
1422
|
+
return "assets/misc";
|
|
1423
|
+
}
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
// src/platforms/course/teachable.ts
|
|
1431
|
+
var IMG_EXTS20, FONT_EXTS21, VIDEO_EXTS20, teachable;
|
|
1432
|
+
var init_teachable = __esm({
|
|
1433
|
+
"src/platforms/course/teachable.ts"() {
|
|
1434
|
+
"use strict";
|
|
1435
|
+
IMG_EXTS20 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1436
|
+
FONT_EXTS21 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1437
|
+
VIDEO_EXTS20 = [".mp4", ".webm", ".ogg"];
|
|
1438
|
+
teachable = {
|
|
1439
|
+
name: "teachable",
|
|
1440
|
+
displayName: "Teachable",
|
|
1441
|
+
category: "course",
|
|
1442
|
+
priority: 82,
|
|
1443
|
+
detectByUrl(url) {
|
|
1444
|
+
return /\.teachable\.com/i.test(url);
|
|
1445
|
+
},
|
|
1446
|
+
detectByHtml(html) {
|
|
1447
|
+
return html.includes("teachablecdn.com") || html.includes("eventable.internal.teachable.com") || html.includes("Teach Online with") || html.includes("src=school_footer");
|
|
1448
|
+
},
|
|
1449
|
+
stripDomains: [
|
|
1450
|
+
"cdn.heapanalytics.com",
|
|
1451
|
+
"bam.nr-data.net",
|
|
1452
|
+
"www.recaptcha.net",
|
|
1453
|
+
"fast.wistia.com"
|
|
1454
|
+
],
|
|
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
|
+
],
|
|
1462
|
+
stripPatterns: [
|
|
1463
|
+
// Footer "Teach Online with" badge — anchored on its unique powered-by class.
|
|
1464
|
+
/<a[^>]*class=['"][^'"]*powered-by[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi,
|
|
1465
|
+
// Fallback: anchored on the teachable.com ?src=school_footer badge href.
|
|
1466
|
+
/<a[^>]*href=['"][^'"]*teachable\.com[^'"]*src=school_footer[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1467
|
+
],
|
|
1468
|
+
hydrationTimeout: 0,
|
|
1469
|
+
needsHydrationCheck: false,
|
|
1470
|
+
mapAssetDir(host, pathname, ext) {
|
|
1471
|
+
if (host.includes("teachablecdn.com")) {
|
|
1472
|
+
if (VIDEO_EXTS20.includes(ext)) return "assets/videos";
|
|
1473
|
+
if (IMG_EXTS20.includes(ext)) return "assets/images";
|
|
1474
|
+
if (FONT_EXTS21.includes(ext)) return "assets/fonts";
|
|
1475
|
+
if (ext === ".css") return "styles";
|
|
1476
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1477
|
+
if (ext === ".json") return "data";
|
|
1478
|
+
if (pathname.includes("/images/")) return "assets/images";
|
|
1479
|
+
if (pathname.includes("/packs/")) return "scripts/vendor";
|
|
1480
|
+
return "assets/misc";
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
// src/platforms/course/thinkific.ts
|
|
1489
|
+
var IMG_EXTS21, FONT_EXTS22, VIDEO_EXTS21, thinkific;
|
|
1490
|
+
var init_thinkific = __esm({
|
|
1491
|
+
"src/platforms/course/thinkific.ts"() {
|
|
1492
|
+
"use strict";
|
|
1493
|
+
IMG_EXTS21 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1494
|
+
FONT_EXTS22 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1495
|
+
VIDEO_EXTS21 = [".mp4", ".webm", ".ogg"];
|
|
1496
|
+
thinkific = {
|
|
1497
|
+
name: "thinkific",
|
|
1498
|
+
displayName: "Thinkific",
|
|
1499
|
+
category: "course",
|
|
1500
|
+
priority: 82,
|
|
1501
|
+
detectByUrl(url) {
|
|
1502
|
+
return /\.thinkific\.com/i.test(url);
|
|
1503
|
+
},
|
|
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
|
+
detectByHtml(html) {
|
|
1509
|
+
return html.includes("window.Thinkific") || // inline bootstrap: `window.Thinkific = 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");
|
|
1513
|
+
},
|
|
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
|
+
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
|
+
stripSelectors: [],
|
|
1530
|
+
stripPatterns: [
|
|
1531
|
+
// Whole badge wrapper: <div class="footer__white-label"> … </div>
|
|
1532
|
+
// (inner anchor + inline SVG titled "Teach online with Thinkific").
|
|
1533
|
+
/<div[^>]*class="[^"]*footer__white-label[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1534
|
+
// Belt-and-suspenders: the branded powered-by referral anchor itself.
|
|
1535
|
+
/<a[^>]*href="[^"]*thinkific\.com[^"]*utm_medium=powered-by[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1536
|
+
],
|
|
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
|
+
hydrationTimeout: 2e3,
|
|
1542
|
+
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
|
+
mapAssetDir(host, pathname, ext) {
|
|
1548
|
+
if (host.includes("thinkific.com")) {
|
|
1549
|
+
if (VIDEO_EXTS21.includes(ext)) return "assets/videos";
|
|
1550
|
+
if (pathname.includes("/images/") || IMG_EXTS21.includes(ext)) return "assets/images";
|
|
1551
|
+
if (ext === ".css") return "styles";
|
|
1552
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1553
|
+
if (FONT_EXTS22.includes(ext)) return "assets/fonts";
|
|
1554
|
+
if (ext === ".json") return "data";
|
|
1555
|
+
return "assets/misc";
|
|
1556
|
+
}
|
|
1557
|
+
if (host.includes("s3.amazonaws.com")) {
|
|
1558
|
+
if (VIDEO_EXTS21.includes(ext)) return "assets/videos";
|
|
1559
|
+
if (IMG_EXTS21.includes(ext)) return "assets/images";
|
|
1560
|
+
if (FONT_EXTS22.includes(ext)) return "assets/fonts";
|
|
1561
|
+
if (ext === ".css") return "styles";
|
|
1562
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1563
|
+
return "assets/misc";
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
// src/platforms/ecommerce/gumroad.ts
|
|
1572
|
+
var IMG_EXTS22, FONT_EXTS23, VIDEO_EXTS22, gumroad;
|
|
1573
|
+
var init_gumroad = __esm({
|
|
1574
|
+
"src/platforms/ecommerce/gumroad.ts"() {
|
|
1575
|
+
"use strict";
|
|
1576
|
+
IMG_EXTS22 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1577
|
+
FONT_EXTS23 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1578
|
+
VIDEO_EXTS22 = [".mp4", ".webm", ".ogg"];
|
|
1579
|
+
gumroad = {
|
|
1580
|
+
name: "gumroad",
|
|
1581
|
+
displayName: "Gumroad",
|
|
1582
|
+
category: "ecommerce",
|
|
1583
|
+
priority: 80,
|
|
1584
|
+
detectByUrl(url) {
|
|
1585
|
+
return /\.gumroad\.com|\bgum\.co\b/i.test(url);
|
|
1586
|
+
},
|
|
1587
|
+
detectByHtml(html) {
|
|
1588
|
+
return html.includes("cable.gumroad.com") || html.includes("assets.gumroad.com") || html.includes("ns/fb/gumroad#") || html.includes("gumroad:product");
|
|
1589
|
+
},
|
|
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
|
+
stripDomains: [
|
|
1595
|
+
"gumroad-analytics.com",
|
|
1596
|
+
"www.googletagmanager.com",
|
|
1597
|
+
"connect.facebook.net",
|
|
1598
|
+
"analytics.tiktok.com"
|
|
1599
|
+
],
|
|
1600
|
+
// Defensively drop any surviving analytics <script> tags from the rendered DOM
|
|
1601
|
+
// (stripDomains already blocks them at the network layer during capture).
|
|
1602
|
+
stripSelectors: [
|
|
1603
|
+
'script[src*="gumroad-analytics.com"]',
|
|
1604
|
+
'script[src*="googletagmanager.com"]',
|
|
1605
|
+
'script[src*="connect.facebook.net"]',
|
|
1606
|
+
'script[src*="analytics.tiktok.com"]'
|
|
1607
|
+
],
|
|
1608
|
+
// No server-rendered badge to remove (badgePresent = false, confirmed).
|
|
1609
|
+
stripPatterns: [],
|
|
1610
|
+
hydrationTimeout: 8e3,
|
|
1611
|
+
needsHydrationCheck: true,
|
|
1612
|
+
hydrationSelector: "#app",
|
|
1613
|
+
captureRenderedDom: true,
|
|
1614
|
+
mapAssetDir(host, pathname, ext) {
|
|
1615
|
+
if (host.includes("assets.gumroad.com") || host.includes("public-files.gumroad.com") || host.includes("static-2.gumroad.com")) {
|
|
1616
|
+
if (VIDEO_EXTS22.includes(ext)) return "assets/videos";
|
|
1617
|
+
if (pathname.includes("/fonts/") || FONT_EXTS23.includes(ext)) return "assets/fonts";
|
|
1618
|
+
if (pathname.includes("/stylesheets/") || ext === ".css") return "styles";
|
|
1619
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1620
|
+
if (pathname.includes("/images/") || IMG_EXTS22.includes(ext)) return "assets/images";
|
|
1621
|
+
if (ext === ".json") return "data";
|
|
1622
|
+
return "assets/misc";
|
|
1623
|
+
}
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
// src/platforms/ecommerce/shopify.ts
|
|
1631
|
+
var IMG_EXTS23, FONT_EXTS24, VIDEO_EXTS23, shopify;
|
|
1632
|
+
var init_shopify = __esm({
|
|
1633
|
+
"src/platforms/ecommerce/shopify.ts"() {
|
|
1634
|
+
"use strict";
|
|
1635
|
+
IMG_EXTS23 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1636
|
+
FONT_EXTS24 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1637
|
+
VIDEO_EXTS23 = [".mp4", ".webm", ".ogg"];
|
|
1638
|
+
shopify = {
|
|
1639
|
+
name: "shopify",
|
|
1640
|
+
displayName: "Shopify",
|
|
1641
|
+
category: "ecommerce",
|
|
1642
|
+
priority: 80,
|
|
1643
|
+
detectByUrl(url) {
|
|
1644
|
+
return /\.myshopify\.com/i.test(url);
|
|
1645
|
+
},
|
|
1646
|
+
detectByHtml(html) {
|
|
1647
|
+
return html.includes("cdn.shopify.com") || html.includes("/cdn/shop/") || html.includes("Shopify.shop") || html.includes("ShopifyAnalytics");
|
|
1648
|
+
},
|
|
1649
|
+
// Telemetry only — never the asset CDN.
|
|
1650
|
+
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
|
+
stripSelectors: [],
|
|
1658
|
+
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
|
+
/<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
|
+
/<a[^>]*href="[^"]*shopify\.com\?utm_campaign=poweredby[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
1663
|
+
],
|
|
1664
|
+
hydrationTimeout: 3500,
|
|
1665
|
+
needsHydrationCheck: true,
|
|
1666
|
+
mapAssetDir(host, pathname, ext) {
|
|
1667
|
+
const isShopifyAssetPath = pathname.includes("/cdn/shop/");
|
|
1668
|
+
if (host.includes("cdn.shopify.com") || isShopifyAssetPath) {
|
|
1669
|
+
if (VIDEO_EXTS23.includes(ext)) return "assets/videos";
|
|
1670
|
+
if (IMG_EXTS23.includes(ext)) return "assets/images";
|
|
1671
|
+
if (ext === ".css") return "styles";
|
|
1672
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1673
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1674
|
+
if (ext === ".json") return "data";
|
|
1675
|
+
return "assets/misc";
|
|
1676
|
+
}
|
|
1677
|
+
if (host.includes("fonts.shopifycdn.com")) {
|
|
1678
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1679
|
+
return "assets/misc";
|
|
1680
|
+
}
|
|
1681
|
+
if (host.includes("extensions.shopifycdn.com")) {
|
|
1682
|
+
if (VIDEO_EXTS23.includes(ext)) return "assets/videos";
|
|
1683
|
+
if (IMG_EXTS23.includes(ext)) return "assets/images";
|
|
1684
|
+
if (ext === ".css") return "styles";
|
|
1685
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1686
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1687
|
+
return "assets/misc";
|
|
1688
|
+
}
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// src/platforms/ai/gamma.ts
|
|
1696
|
+
var IMG_EXTS24, FONT_EXTS25, VIDEO_EXTS24, gamma;
|
|
1697
|
+
var init_gamma = __esm({
|
|
1698
|
+
"src/platforms/ai/gamma.ts"() {
|
|
1699
|
+
"use strict";
|
|
1700
|
+
IMG_EXTS24 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1701
|
+
FONT_EXTS25 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1702
|
+
VIDEO_EXTS24 = [".mp4", ".webm", ".ogg"];
|
|
1703
|
+
gamma = {
|
|
1704
|
+
name: "gamma",
|
|
1705
|
+
displayName: "Gamma",
|
|
1706
|
+
category: "ai",
|
|
1707
|
+
priority: 85,
|
|
1708
|
+
detectByUrl(url) {
|
|
1709
|
+
return /\.gamma\.site/i.test(url);
|
|
1710
|
+
},
|
|
1711
|
+
detectByHtml(html) {
|
|
1712
|
+
return html.includes("assets.gammahosted.com") || html.includes("imgproxy.gamma.app") || html.includes("cdn.gamma.app") || html.includes("gamma-moveable-wrapper");
|
|
1713
|
+
},
|
|
1714
|
+
// No telemetry/analytics hosts observed in the profile.
|
|
1715
|
+
stripDomains: [],
|
|
1716
|
+
// No site badge and no editor chrome to strip on published Gamma sites.
|
|
1717
|
+
stripSelectors: [],
|
|
1718
|
+
stripPatterns: [],
|
|
1719
|
+
hydrationTimeout: 8e3,
|
|
1720
|
+
needsHydrationCheck: true,
|
|
1721
|
+
hydrationSelector: "#__next",
|
|
1722
|
+
scrollStrategy: "standard",
|
|
1723
|
+
mapAssetDir(host, pathname, ext) {
|
|
1724
|
+
if (host.includes("assets.gammahosted.com")) {
|
|
1725
|
+
if (ext === ".css") return "styles";
|
|
1726
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1727
|
+
if (ext === ".json") return "data";
|
|
1728
|
+
if (FONT_EXTS25.includes(ext)) return "assets/fonts";
|
|
1729
|
+
if (VIDEO_EXTS24.includes(ext)) return "assets/videos";
|
|
1730
|
+
if (IMG_EXTS24.includes(ext)) return "assets/images";
|
|
1731
|
+
return "assets/misc";
|
|
1732
|
+
}
|
|
1733
|
+
if (host.includes("imgproxy.gamma.app")) {
|
|
1734
|
+
return "assets/images";
|
|
1735
|
+
}
|
|
1736
|
+
if (host.includes("assets.api.gamma.app")) {
|
|
1737
|
+
return "assets/images";
|
|
1738
|
+
}
|
|
1739
|
+
if (host.includes("cdn.gamma.app")) {
|
|
1740
|
+
if (VIDEO_EXTS24.includes(ext)) return "assets/videos";
|
|
1741
|
+
if (FONT_EXTS25.includes(ext)) return "assets/fonts";
|
|
1742
|
+
if (ext === ".css") return "styles";
|
|
1743
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1744
|
+
if (IMG_EXTS24.includes(ext)) return "assets/images";
|
|
1745
|
+
return "assets/images";
|
|
1746
|
+
}
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// src/platforms/registry.ts
|
|
1754
|
+
function sortedByPriority() {
|
|
1755
|
+
return [...PLATFORM_REGISTRY].sort((a, b) => b.priority - a.priority);
|
|
1756
|
+
}
|
|
1757
|
+
function platformsByCategory() {
|
|
1758
|
+
const out = {};
|
|
1759
|
+
for (const cat of CATEGORY_ORDER) out[cat] = [];
|
|
1760
|
+
for (const handler of PLATFORM_REGISTRY) {
|
|
1761
|
+
if (!out[handler.category]) out[handler.category] = [];
|
|
1762
|
+
out[handler.category].push(handler);
|
|
1763
|
+
}
|
|
1764
|
+
for (const cat of CATEGORY_ORDER) {
|
|
1765
|
+
out[cat].sort((a, b) => b.priority - a.priority);
|
|
1766
|
+
}
|
|
1767
|
+
return out;
|
|
1768
|
+
}
|
|
1769
|
+
var PLATFORM_REGISTRY, CATEGORY_LABELS, CATEGORY_ORDER;
|
|
1770
|
+
var init_registry = __esm({
|
|
1771
|
+
"src/platforms/registry.ts"() {
|
|
1772
|
+
"use strict";
|
|
1773
|
+
init_framer();
|
|
1774
|
+
init_webflow();
|
|
1775
|
+
init_wix();
|
|
1776
|
+
init_bubble();
|
|
1777
|
+
init_carrd();
|
|
1778
|
+
init_duda();
|
|
1779
|
+
init_squarespace();
|
|
1780
|
+
init_strikingly();
|
|
1781
|
+
init_tilda();
|
|
1782
|
+
init_weebly();
|
|
1783
|
+
init_clickfunnels();
|
|
1784
|
+
init_elementor();
|
|
1785
|
+
init_instapage();
|
|
1786
|
+
init_systemeio();
|
|
1787
|
+
init_unbounce();
|
|
1788
|
+
init_ghost();
|
|
1789
|
+
init_notion();
|
|
1790
|
+
init_wordpress();
|
|
1791
|
+
init_kajabi();
|
|
1792
|
+
init_podia();
|
|
1793
|
+
init_teachable();
|
|
1794
|
+
init_thinkific();
|
|
1795
|
+
init_gumroad();
|
|
1796
|
+
init_shopify();
|
|
1797
|
+
init_gamma();
|
|
1798
|
+
PLATFORM_REGISTRY = [
|
|
1799
|
+
// frozen trio first (highest priority, unchanged behaviour)
|
|
1800
|
+
framer,
|
|
1801
|
+
webflow,
|
|
1802
|
+
wix,
|
|
1803
|
+
// builders
|
|
1804
|
+
bubble,
|
|
1805
|
+
carrd,
|
|
1806
|
+
duda,
|
|
1807
|
+
squarespace,
|
|
1808
|
+
strikingly,
|
|
1809
|
+
tilda,
|
|
1810
|
+
weebly,
|
|
1811
|
+
// landing
|
|
1812
|
+
clickfunnels,
|
|
1813
|
+
elementor,
|
|
1814
|
+
instapage,
|
|
1815
|
+
systemeio,
|
|
1816
|
+
unbounce,
|
|
1817
|
+
// cms
|
|
1818
|
+
ghost,
|
|
1819
|
+
notion,
|
|
1820
|
+
wordpress,
|
|
1821
|
+
// course
|
|
1822
|
+
kajabi,
|
|
1823
|
+
podia,
|
|
1824
|
+
teachable,
|
|
1825
|
+
thinkific,
|
|
1826
|
+
// ecommerce
|
|
1827
|
+
gumroad,
|
|
1828
|
+
shopify,
|
|
1829
|
+
// ai
|
|
1830
|
+
gamma
|
|
1831
|
+
];
|
|
1832
|
+
CATEGORY_LABELS = {
|
|
1833
|
+
builder: "Website Builders",
|
|
1834
|
+
landing: "Landing Pages & Funnels",
|
|
1835
|
+
cms: "CMS & Blogs",
|
|
1836
|
+
course: "Courses & Membership",
|
|
1837
|
+
ecommerce: "E-commerce",
|
|
1838
|
+
ai: "AI & Presentations"
|
|
1839
|
+
};
|
|
1840
|
+
CATEGORY_ORDER = [
|
|
1841
|
+
"builder",
|
|
1842
|
+
"landing",
|
|
1843
|
+
"cms",
|
|
1844
|
+
"course",
|
|
1845
|
+
"ecommerce",
|
|
1846
|
+
"ai"
|
|
1847
|
+
];
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
// src/platforms/detect.ts
|
|
1852
|
+
function readGenerator(html) {
|
|
1853
|
+
const m = html.match(/<meta[^>]+name=["']generator["'][^>]*content=["']([^"']*)["']/i) || html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']generator["']/i);
|
|
1854
|
+
return m ? m[1] : "";
|
|
1855
|
+
}
|
|
1856
|
+
function detectByUrl(url) {
|
|
1857
|
+
for (const platform of sortedByPriority()) {
|
|
1858
|
+
try {
|
|
1859
|
+
if (platform.detectByUrl(url)) return platform;
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function detectByGenerator(html) {
|
|
1866
|
+
const gen = readGenerator(html);
|
|
1867
|
+
if (!gen) return null;
|
|
1868
|
+
for (const platform of sortedByPriority()) {
|
|
1869
|
+
if (platform.detectGenerator && platform.detectGenerator.test(gen)) return platform;
|
|
1870
|
+
}
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
function detectByHtml(html) {
|
|
1874
|
+
const byGen = detectByGenerator(html);
|
|
1875
|
+
if (byGen) return byGen;
|
|
1876
|
+
for (const platform of sortedByPriority()) {
|
|
1877
|
+
try {
|
|
1878
|
+
if (platform.detectByHtml(html)) return platform;
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
function detectPlatform(url, html) {
|
|
1885
|
+
const byUrl = detectByUrl(url);
|
|
1886
|
+
if (byUrl) return byUrl;
|
|
1887
|
+
if (html) {
|
|
1888
|
+
const byHtml = detectByHtml(html);
|
|
1889
|
+
if (byHtml) return byHtml;
|
|
1890
|
+
}
|
|
1891
|
+
return framer;
|
|
1892
|
+
}
|
|
1893
|
+
async function detectByDom(page) {
|
|
1894
|
+
try {
|
|
1895
|
+
const signal = await page.evaluate(() => {
|
|
1896
|
+
const html = document.documentElement.outerHTML || "";
|
|
1897
|
+
if (/wix-viewer-model|_wix_|wix-ecom/i.test(html)) return "wix";
|
|
1898
|
+
if (/data-wf-|w-webflow-badge|webflow\.js/i.test(html)) return "webflow";
|
|
1899
|
+
if (/framerusercontent|framercanvas|framerstatic/i.test(html)) return "framer";
|
|
1900
|
+
const srcs = [];
|
|
1901
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
1902
|
+
scripts.forEach((s) => {
|
|
1903
|
+
if (s.src) srcs.push(s.src);
|
|
1904
|
+
});
|
|
1905
|
+
const allSrcs = srcs.join(" ");
|
|
1906
|
+
if (/wixstatic\.com|parastorage\.com/.test(allSrcs)) return "wix";
|
|
1907
|
+
if (/website-files\.com|webflow\.com/.test(allSrcs)) return "webflow";
|
|
1908
|
+
if (/framerusercontent\.com|framerstatic\.com|framer\.app/.test(allSrcs)) return "framer";
|
|
1909
|
+
return "";
|
|
1910
|
+
});
|
|
1911
|
+
if (signal) return getPlatformByName(signal);
|
|
1912
|
+
} catch {
|
|
1913
|
+
}
|
|
1914
|
+
for (const platform of sortedByPriority()) {
|
|
1915
|
+
if (!platform.detectByDom) continue;
|
|
1916
|
+
try {
|
|
1917
|
+
if (await platform.detectByDom(page)) return platform;
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
function getPlatformByName(name) {
|
|
1924
|
+
const found = PLATFORM_REGISTRY.find((platform) => platform.name === name);
|
|
1925
|
+
return found || framer;
|
|
1926
|
+
}
|
|
1927
|
+
var init_detect = __esm({
|
|
1928
|
+
"src/platforms/detect.ts"() {
|
|
1929
|
+
"use strict";
|
|
1930
|
+
init_registry();
|
|
1931
|
+
init_framer();
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
// src/platforms/index.ts
|
|
1936
|
+
var platforms_exports = {};
|
|
1937
|
+
__export(platforms_exports, {
|
|
1938
|
+
CATEGORY_LABELS: () => CATEGORY_LABELS,
|
|
1939
|
+
CATEGORY_ORDER: () => CATEGORY_ORDER,
|
|
1940
|
+
PLATFORM_REGISTRY: () => PLATFORM_REGISTRY,
|
|
1941
|
+
detectByDom: () => detectByDom,
|
|
1942
|
+
detectByGenerator: () => detectByGenerator,
|
|
1943
|
+
detectByHtml: () => detectByHtml,
|
|
1944
|
+
detectByUrl: () => detectByUrl,
|
|
1945
|
+
detectPlatform: () => detectPlatform,
|
|
1946
|
+
framer: () => framer,
|
|
1947
|
+
getPlatformByName: () => getPlatformByName,
|
|
1948
|
+
platformsByCategory: () => platformsByCategory,
|
|
1949
|
+
readGenerator: () => readGenerator,
|
|
1950
|
+
sortedByPriority: () => sortedByPriority,
|
|
1951
|
+
webflow: () => webflow,
|
|
1952
|
+
wix: () => wix
|
|
1953
|
+
});
|
|
1954
|
+
var init_platforms = __esm({
|
|
1955
|
+
"src/platforms/index.ts"() {
|
|
1956
|
+
"use strict";
|
|
1957
|
+
init_framer();
|
|
1958
|
+
init_webflow();
|
|
1959
|
+
init_wix();
|
|
1960
|
+
init_registry();
|
|
1961
|
+
init_detect();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
|
|
207
1965
|
// src/cli/cooking.ts
|
|
208
1966
|
import chalk2 from "chalk";
|
|
209
1967
|
async function showLoadingIntro(version) {
|
|
@@ -215,7 +1973,8 @@ async function showLoadingIntro(version) {
|
|
|
215
1973
|
const title = renderShinyText(`Framer Export v${version}`, frame, {
|
|
216
1974
|
baseColor: "#969696",
|
|
217
1975
|
shineColor: "#ffffff",
|
|
218
|
-
shineWidth: 18
|
|
1976
|
+
shineWidth: 18,
|
|
1977
|
+
speed: 1.2
|
|
219
1978
|
});
|
|
220
1979
|
const dots = ".".repeat(frame % 4).padEnd(3, " ");
|
|
221
1980
|
process.stdout.write(
|
|
@@ -237,8 +1996,9 @@ function renderShinyText(text, frame, options = {}) {
|
|
|
237
1996
|
const baseColor = options.baseColor || "#808080";
|
|
238
1997
|
const shineColor = options.shineColor || "#ffffff";
|
|
239
1998
|
const shineWidth = options.shineWidth || SHINE_WIDTH;
|
|
1999
|
+
const speed = options.speed ?? SHINE_SPEED;
|
|
240
2000
|
const travel = text.length + shineWidth * 2;
|
|
241
|
-
const pos = frame *
|
|
2001
|
+
const pos = frame * speed % travel - shineWidth;
|
|
242
2002
|
const base = hexToRgb(baseColor);
|
|
243
2003
|
const shine = hexToRgb(shineColor);
|
|
244
2004
|
let result = "";
|
|
@@ -338,9 +2098,66 @@ function maxWidth() {
|
|
|
338
2098
|
return Math.min(process.stdout.columns || 80, 76);
|
|
339
2099
|
}
|
|
340
2100
|
function padRight(text, w) {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
2101
|
+
const fitted = fitAnsi(text, w);
|
|
2102
|
+
const visible = stripAnsi(fitted).length;
|
|
2103
|
+
if (visible >= w) return fitted;
|
|
2104
|
+
return fitted + " ".repeat(w - visible);
|
|
2105
|
+
}
|
|
2106
|
+
function fitAnsi(text, width) {
|
|
2107
|
+
if (stripAnsi(text).length <= width) return text;
|
|
2108
|
+
if (width <= 2) return ".".repeat(Math.max(0, width));
|
|
2109
|
+
let out = "";
|
|
2110
|
+
let visible = 0;
|
|
2111
|
+
let i = 0;
|
|
2112
|
+
const limit = width - 2;
|
|
2113
|
+
while (i < text.length && visible < limit) {
|
|
2114
|
+
if (text[i] === "\x1B") {
|
|
2115
|
+
const match = text.slice(i).match(/^\x1B\[[0-9;]*[a-zA-Z]/);
|
|
2116
|
+
if (match) {
|
|
2117
|
+
out += match[0];
|
|
2118
|
+
i += match[0].length;
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
out += text[i];
|
|
2123
|
+
visible++;
|
|
2124
|
+
i++;
|
|
2125
|
+
}
|
|
2126
|
+
return out + "..\x1B[0m";
|
|
2127
|
+
}
|
|
2128
|
+
function fitValue(value, width) {
|
|
2129
|
+
if (value.length <= width) return value;
|
|
2130
|
+
const pathLike = value.includes("\\") || value.includes("/");
|
|
2131
|
+
if (pathLike) {
|
|
2132
|
+
const shortened = shortenPath(value, width);
|
|
2133
|
+
if (shortened.length <= width) return shortened;
|
|
2134
|
+
}
|
|
2135
|
+
if (width <= 3) return ".".repeat(Math.max(0, width));
|
|
2136
|
+
return value.slice(0, width - 2) + "..";
|
|
2137
|
+
}
|
|
2138
|
+
function shortenPath(value, width) {
|
|
2139
|
+
if (width <= 12) {
|
|
2140
|
+
return width <= 3 ? ".".repeat(Math.max(0, width)) : value.slice(0, width - 2) + "..";
|
|
2141
|
+
}
|
|
2142
|
+
const separator = value.includes("\\") ? "\\" : "/";
|
|
2143
|
+
const parts = value.split(/[\\/]+/).filter(Boolean);
|
|
2144
|
+
const prefix = value.startsWith(separator) ? separator : "";
|
|
2145
|
+
const shortened = parts.map((part, index) => {
|
|
2146
|
+
const isLast = index === parts.length - 1;
|
|
2147
|
+
if (isLast) return part;
|
|
2148
|
+
if (/^[A-Za-z]:$/.test(part)) return part;
|
|
2149
|
+
return part.length > 5 ? part.slice(0, 3) + ".." : part;
|
|
2150
|
+
}).join(separator);
|
|
2151
|
+
const withPrefix = prefix + shortened;
|
|
2152
|
+
if (withPrefix.length <= width) return withPrefix;
|
|
2153
|
+
const last = parts.at(-1) || value;
|
|
2154
|
+
const first = parts.slice(0, -1).map((part) => /^[A-Za-z]:$/.test(part) ? part : part.slice(0, 3) + "..");
|
|
2155
|
+
const compact = prefix + [...first, last].join(separator);
|
|
2156
|
+
if (compact.length <= width) return compact;
|
|
2157
|
+
const tailSpace = Math.max(8, Math.floor(width * 0.45));
|
|
2158
|
+
const headSpace = Math.max(0, width - tailSpace - 4);
|
|
2159
|
+
const middle = compact.slice(0, headSpace) + separator + ".." + separator + compact.slice(-tailSpace);
|
|
2160
|
+
return middle.length <= width ? middle : compact.slice(0, width - 2) + "..";
|
|
344
2161
|
}
|
|
345
2162
|
function boxTop(w) {
|
|
346
2163
|
const inner = w - 4;
|
|
@@ -379,14 +2196,10 @@ function panelSep(w) {
|
|
|
379
2196
|
function boxRow(w, label, value) {
|
|
380
2197
|
const inner = w - 4;
|
|
381
2198
|
const labelPlain = stripAnsi(chalk3.bold(label));
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
|
|
387
|
-
}
|
|
388
|
-
const right = inner - labelPlain.length - 1 - value.length;
|
|
389
|
-
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
|
|
2199
|
+
const avail = Math.max(0, inner - labelPlain.length - 2);
|
|
2200
|
+
const fittedValue = fitValue(value, avail);
|
|
2201
|
+
const right = Math.max(0, inner - labelPlain.length - 2 - fittedValue.length);
|
|
2202
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(fittedValue) + " ".repeat(right) + ui.border(" \u2502");
|
|
390
2203
|
}
|
|
391
2204
|
var init_box = __esm({
|
|
392
2205
|
"src/cli/box.ts"() {
|
|
@@ -1015,315 +2828,137 @@ var init_logger = __esm({
|
|
|
1015
2828
|
"use strict";
|
|
1016
2829
|
init_theme();
|
|
1017
2830
|
_cooking = null;
|
|
1018
|
-
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
1019
|
-
LOG_PALETTE = [
|
|
1020
|
-
(s) => chalk4.hex(THEME.primary)(s),
|
|
1021
|
-
(s) => chalk4.hex(THEME.primarySoft)(s),
|
|
1022
|
-
(s) => chalk4.hex(THEME.secondary)(s),
|
|
1023
|
-
(s) => chalk4.hex(THEME.accent)(s),
|
|
1024
|
-
(s) => chalk4.hex(THEME.info)(s),
|
|
1025
|
-
(s) => chalk4.hex(THEME.text)(s)
|
|
1026
|
-
];
|
|
1027
|
-
_li = 0;
|
|
1028
|
-
log = (m) => {
|
|
1029
|
-
_li++;
|
|
1030
|
-
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
1031
|
-
output(
|
|
1032
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
|
|
1033
|
-
);
|
|
1034
|
-
};
|
|
1035
|
-
INFO_PALETTE = [
|
|
1036
|
-
(s) => chalk4.hex(THEME.info)(s),
|
|
1037
|
-
(s) => chalk4.hex(THEME.secondary)(s),
|
|
1038
|
-
(s) => chalk4.hex(THEME.primarySoft)(s)
|
|
1039
|
-
];
|
|
1040
|
-
_ii = 0;
|
|
1041
|
-
info = (m) => {
|
|
1042
|
-
_ii++;
|
|
1043
|
-
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
1044
|
-
output(
|
|
1045
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
|
|
1046
|
-
);
|
|
1047
|
-
};
|
|
1048
|
-
warn = (m) => {
|
|
1049
|
-
const colors = [
|
|
1050
|
-
(s) => chalk4.hex(THEME.warning)(s),
|
|
1051
|
-
(s) => chalk4.hex(THEME.primary)(s)
|
|
1052
|
-
];
|
|
1053
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
1054
|
-
const line = `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
1055
|
-
if (_cooking) _cooking.log(line);
|
|
1056
|
-
else console.warn(line);
|
|
1057
|
-
};
|
|
1058
|
-
success = (m) => {
|
|
1059
|
-
const colors = [
|
|
1060
|
-
(s) => chalk4.hex(THEME.success)(s),
|
|
1061
|
-
(s) => chalk4.hex(THEME.info)(s)
|
|
1062
|
-
];
|
|
1063
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
1064
|
-
output(
|
|
1065
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
|
|
1066
|
-
);
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
|
|
1071
|
-
// src/platforms/framer.ts
|
|
1072
|
-
var FONT_EXTS, framer;
|
|
1073
|
-
var init_framer = __esm({
|
|
1074
|
-
"src/platforms/framer.ts"() {
|
|
1075
|
-
"use strict";
|
|
1076
|
-
FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
|
|
1077
|
-
framer = {
|
|
1078
|
-
name: "framer",
|
|
1079
|
-
displayName: "Framer",
|
|
1080
|
-
detectByUrl(url) {
|
|
1081
|
-
return /\.framer\.(app|website)|framercanvas\.com/.test(url);
|
|
1082
|
-
},
|
|
1083
|
-
detectByHtml(html) {
|
|
1084
|
-
return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
|
|
1085
|
-
},
|
|
1086
|
-
stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
|
|
1087
|
-
stripSelectors: [
|
|
1088
|
-
'script[src*="events.framer.com"]',
|
|
1089
|
-
"#__framer-badge-container",
|
|
1090
|
-
"#__framer-badge",
|
|
1091
|
-
'link[href*="canvas-sandbox"]',
|
|
1092
|
-
'script[src*="framer.com/bootstrap"]'
|
|
1093
|
-
],
|
|
1094
|
-
stripPatterns: [
|
|
1095
|
-
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
1096
|
-
/<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
1097
|
-
],
|
|
1098
|
-
hydrationTimeout: 1e4,
|
|
1099
|
-
needsHydrationCheck: true,
|
|
1100
|
-
mapAssetDir(host, pathname, ext) {
|
|
1101
|
-
if (host.includes("framerusercontent.com")) {
|
|
1102
|
-
if (pathname.startsWith("/images/")) return "assets/images";
|
|
1103
|
-
if (pathname.startsWith("/assets/")) {
|
|
1104
|
-
return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
|
|
1105
|
-
}
|
|
1106
|
-
if (pathname.startsWith("/sites/")) {
|
|
1107
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1108
|
-
if (ext === ".json") return "data";
|
|
1109
|
-
if (ext === ".css") return "styles";
|
|
1110
|
-
if (ext === ".framercms") return "data";
|
|
1111
|
-
return "assets/misc";
|
|
1112
|
-
}
|
|
1113
|
-
if (pathname.startsWith("/modules/")) {
|
|
1114
|
-
return ext === ".framercms" ? "data" : "scripts/modules";
|
|
1115
|
-
}
|
|
1116
|
-
return "assets/misc";
|
|
1117
|
-
}
|
|
1118
|
-
if (host.includes("app.framerstatic.com")) {
|
|
1119
|
-
if (ext === ".css") return "styles";
|
|
1120
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1121
|
-
if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
|
|
1122
|
-
if (ext === ".png" || ext === ".svg") return "assets/images";
|
|
1123
|
-
return "assets/misc";
|
|
1124
|
-
}
|
|
1125
|
-
if (host.includes("framercanvas.com") || host.includes("framer.com")) {
|
|
1126
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1127
|
-
if (ext === ".css") return "styles";
|
|
1128
|
-
return "assets/misc";
|
|
1129
|
-
}
|
|
1130
|
-
return null;
|
|
1131
|
-
}
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
// src/platforms/webflow.ts
|
|
1137
|
-
var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
|
|
1138
|
-
var init_webflow = __esm({
|
|
1139
|
-
"src/platforms/webflow.ts"() {
|
|
1140
|
-
"use strict";
|
|
1141
|
-
IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
|
|
1142
|
-
FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1143
|
-
VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
|
|
1144
|
-
webflow = {
|
|
1145
|
-
name: "webflow",
|
|
1146
|
-
displayName: "Webflow",
|
|
1147
|
-
detectByUrl(url) {
|
|
1148
|
-
return /\.webflow\.(io|com)/.test(url);
|
|
1149
|
-
},
|
|
1150
|
-
detectByHtml(html) {
|
|
1151
|
-
return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
|
|
1152
|
-
},
|
|
1153
|
-
stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
|
|
1154
|
-
stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
|
|
1155
|
-
stripPatterns: [
|
|
1156
|
-
/<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1157
|
-
/<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1158
|
-
/<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
|
|
1159
|
-
/Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
|
|
1160
|
-
/<!-- This site was created in Webflow\.[^>]*-->/g,
|
|
1161
|
-
/<html([^>]*) data-wf-domain="[^"]*"/g,
|
|
1162
|
-
/<html([^>]*) data-wf-page="[^"]*"/g,
|
|
1163
|
-
/<html([^>]*) data-wf-site="[^"]*"/g,
|
|
1164
|
-
/<html([^>]*) data-wf-status="[^"]*"/g,
|
|
1165
|
-
/<meta[^>]*content="Webflow"[^>]*>/g
|
|
1166
|
-
],
|
|
1167
|
-
hydrationTimeout: 2e3,
|
|
1168
|
-
needsHydrationCheck: false,
|
|
1169
|
-
mapAssetDir(host, pathname, ext) {
|
|
1170
|
-
if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
|
|
1171
|
-
if (VIDEO_EXTS.includes(ext)) return "assets/videos";
|
|
1172
|
-
if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
|
|
1173
|
-
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
1174
|
-
if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
|
|
1175
|
-
if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
|
|
1176
|
-
if (FONT_EXTS2.includes(ext)) return "assets/fonts";
|
|
1177
|
-
return "assets/misc";
|
|
1178
|
-
}
|
|
1179
|
-
if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
|
|
1180
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1181
|
-
if (ext === ".css") return "styles";
|
|
1182
|
-
if (IMG_EXTS.includes(ext)) return "assets/images";
|
|
1183
|
-
return "assets/misc";
|
|
1184
|
-
}
|
|
1185
|
-
return null;
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
// src/platforms/wix.ts
|
|
1192
|
-
var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
|
|
1193
|
-
var init_wix = __esm({
|
|
1194
|
-
"src/platforms/wix.ts"() {
|
|
1195
|
-
"use strict";
|
|
1196
|
-
IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1197
|
-
FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1198
|
-
VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
|
|
1199
|
-
wix = {
|
|
1200
|
-
name: "wix",
|
|
1201
|
-
displayName: "Wix",
|
|
1202
|
-
detectByUrl(url) {
|
|
1203
|
-
return /\.wixsite\.com|\.wix\.com/.test(url);
|
|
1204
|
-
},
|
|
1205
|
-
detectByHtml(html) {
|
|
1206
|
-
return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk") || html.includes("data-wix") || html.includes("wix-code") || html.includes("WixCode") || /\bwix\.com\b/.test(html) || /meta\s+name=["']generator["']\s+content=["'][^"']*Wix[^"']*["']/i.test(html) || /_wix_/i.test(html) || /wix-ecom/i.test(html);
|
|
1207
|
-
},
|
|
1208
|
-
stripDomains: [
|
|
1209
|
-
"frog.wix.com",
|
|
1210
|
-
"bi.wixapi.com",
|
|
1211
|
-
"fed.wixcodescheduler.com",
|
|
1212
|
-
"editor.wix.com",
|
|
1213
|
-
"panorama.wixapps.net"
|
|
1214
|
-
],
|
|
1215
|
-
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
1216
|
-
stripPatterns: [
|
|
1217
|
-
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
1218
|
-
],
|
|
1219
|
-
hydrationTimeout: 3e3,
|
|
1220
|
-
needsHydrationCheck: false,
|
|
1221
|
-
mapAssetDir(host, pathname, ext) {
|
|
1222
|
-
if (host === "video.wixstatic.com") {
|
|
1223
|
-
return "assets/videos";
|
|
1224
|
-
}
|
|
1225
|
-
if (host.includes("wixstatic.com")) {
|
|
1226
|
-
if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
|
|
1227
|
-
if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1228
|
-
if (ext === ".css") return "styles";
|
|
1229
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1230
|
-
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1231
|
-
return "assets/misc";
|
|
1232
|
-
}
|
|
1233
|
-
if (host.includes("parastorage.com")) {
|
|
1234
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1235
|
-
if (ext === ".css") return "styles";
|
|
1236
|
-
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1237
|
-
if (IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1238
|
-
return "assets/misc";
|
|
1239
|
-
}
|
|
1240
|
-
return null;
|
|
1241
|
-
}
|
|
2831
|
+
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
2832
|
+
LOG_PALETTE = [
|
|
2833
|
+
(s) => chalk4.hex(THEME.primary)(s),
|
|
2834
|
+
(s) => chalk4.hex(THEME.primarySoft)(s),
|
|
2835
|
+
(s) => chalk4.hex(THEME.secondary)(s),
|
|
2836
|
+
(s) => chalk4.hex(THEME.accent)(s),
|
|
2837
|
+
(s) => chalk4.hex(THEME.info)(s),
|
|
2838
|
+
(s) => chalk4.hex(THEME.text)(s)
|
|
2839
|
+
];
|
|
2840
|
+
_li = 0;
|
|
2841
|
+
log = (m) => {
|
|
2842
|
+
_li++;
|
|
2843
|
+
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
2844
|
+
output(
|
|
2845
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
|
|
2846
|
+
);
|
|
2847
|
+
};
|
|
2848
|
+
INFO_PALETTE = [
|
|
2849
|
+
(s) => chalk4.hex(THEME.info)(s),
|
|
2850
|
+
(s) => chalk4.hex(THEME.secondary)(s),
|
|
2851
|
+
(s) => chalk4.hex(THEME.primarySoft)(s)
|
|
2852
|
+
];
|
|
2853
|
+
_ii = 0;
|
|
2854
|
+
info = (m) => {
|
|
2855
|
+
_ii++;
|
|
2856
|
+
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
2857
|
+
output(
|
|
2858
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
|
|
2859
|
+
);
|
|
2860
|
+
};
|
|
2861
|
+
warn = (m) => {
|
|
2862
|
+
const colors = [
|
|
2863
|
+
(s) => chalk4.hex(THEME.warning)(s),
|
|
2864
|
+
(s) => chalk4.hex(THEME.primary)(s)
|
|
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);
|
|
2870
|
+
};
|
|
2871
|
+
success = (m) => {
|
|
2872
|
+
const colors = [
|
|
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)];
|
|
2877
|
+
output(
|
|
2878
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
|
|
2879
|
+
);
|
|
1242
2880
|
};
|
|
1243
2881
|
}
|
|
1244
2882
|
});
|
|
1245
2883
|
|
|
1246
|
-
// src/
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
2884
|
+
// src/exporter/anti-bot.ts
|
|
2885
|
+
var anti_bot_exports = {};
|
|
2886
|
+
__export(anti_bot_exports, {
|
|
2887
|
+
AntiBotError: () => AntiBotError,
|
|
2888
|
+
detectAntiBotHtml: () => detectAntiBotHtml,
|
|
2889
|
+
detectAntiBotPage: () => detectAntiBotPage,
|
|
2890
|
+
formatAntiBotError: () => formatAntiBotError
|
|
2891
|
+
});
|
|
2892
|
+
function detectAntiBotHtml(html) {
|
|
2893
|
+
if (!html) return null;
|
|
2894
|
+
if (/<title>\s*Just a moment/i.test(html) || /cf-browser-verification|cf-challenge-running|cf_chl_opt|challenge-platform/i.test(html) || /Attention Required!\s*\|\s*Cloudflare/i.test(html) || /Checking if the site connection is secure/i.test(html)) {
|
|
2895
|
+
return "cloudflare";
|
|
1250
2896
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
function detectByHtml(html) {
|
|
1254
|
-
for (const platform of ALL_PLATFORMS) {
|
|
1255
|
-
if (platform.detectByHtml(html)) return platform;
|
|
2897
|
+
if (/class="g-recaptcha"|h-captcha|hcaptcha\.com|challenges\.cloudflare\.com\/turnstile/i.test(html)) {
|
|
2898
|
+
return "captcha";
|
|
1256
2899
|
}
|
|
1257
2900
|
return null;
|
|
1258
2901
|
}
|
|
1259
|
-
function
|
|
1260
|
-
const byUrl = detectByUrl(url);
|
|
1261
|
-
if (byUrl) return byUrl;
|
|
1262
|
-
if (html) {
|
|
1263
|
-
const byHtml = detectByHtml(html);
|
|
1264
|
-
if (byHtml) return byHtml;
|
|
1265
|
-
}
|
|
1266
|
-
return framer;
|
|
1267
|
-
}
|
|
1268
|
-
async function detectByDom(page) {
|
|
2902
|
+
async function detectAntiBotPage(page) {
|
|
1269
2903
|
try {
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (/
|
|
2904
|
+
const title = await page.title();
|
|
2905
|
+
if (/just a moment/i.test(title) || /attention required/i.test(title)) {
|
|
2906
|
+
return "cloudflare";
|
|
2907
|
+
}
|
|
2908
|
+
const flagged = await page.evaluate(() => {
|
|
2909
|
+
if (document.querySelector('iframe[src*="challenges.cloudflare.com"]')) return "cloudflare";
|
|
2910
|
+
if (document.querySelector(
|
|
2911
|
+
'.g-recaptcha, .h-captcha, iframe[src*="hcaptcha.com"], iframe[src*="recaptcha"]'
|
|
2912
|
+
)) {
|
|
2913
|
+
return "captcha";
|
|
2914
|
+
}
|
|
2915
|
+
const body = document.body ? document.body.innerText || "" : "";
|
|
2916
|
+
const outer = document.documentElement.outerHTML || "";
|
|
2917
|
+
if (body.trim().length < 120 && /cf-chl|cloudflare|cf-browser-verification/i.test(outer)) {
|
|
2918
|
+
return "cloudflare";
|
|
2919
|
+
}
|
|
1284
2920
|
return "";
|
|
1285
2921
|
});
|
|
1286
|
-
|
|
1287
|
-
if (signal === "webflow") return webflow;
|
|
1288
|
-
if (signal === "framer") return framer;
|
|
2922
|
+
return flagged || null;
|
|
1289
2923
|
} catch {
|
|
2924
|
+
return null;
|
|
1290
2925
|
}
|
|
1291
|
-
return null;
|
|
1292
2926
|
}
|
|
1293
|
-
function
|
|
1294
|
-
const
|
|
1295
|
-
|
|
2927
|
+
function formatAntiBotError(err) {
|
|
2928
|
+
const label = err.type === "cloudflare" ? "Cloudflare (browser challenge)" : err.type === "captcha" ? "a captcha challenge" : "a WAF / bot filter";
|
|
2929
|
+
const lines = [
|
|
2930
|
+
"",
|
|
2931
|
+
` ${ui.error("\u2717")} ${ui.error.bold("EXPORT BLOCKED")} \u2014 ${err.platform} is protected by ${label}.`,
|
|
2932
|
+
"",
|
|
2933
|
+
` ${ui.muted("The headless browser could not pass the verification, so nothing was exported.")}`,
|
|
2934
|
+
"",
|
|
2935
|
+
` ${ui.text.bold(" What you can try:")}`,
|
|
2936
|
+
` ${ui.muted(" 1.")} Retry in a few minutes \u2014 the challenge often expires.`,
|
|
2937
|
+
` ${ui.muted(" 2.")} Use an alternative, non-protected canonical URL if one exists.`,
|
|
2938
|
+
` ${ui.muted(" 3.")} Export from a network/IP the site trusts more.`,
|
|
2939
|
+
"",
|
|
2940
|
+
` ${ui.muted("No files were written.")} ${ui.muted("(A stealth-browser mode is on the roadmap.)")}`,
|
|
2941
|
+
""
|
|
2942
|
+
];
|
|
2943
|
+
return lines.join("\n");
|
|
1296
2944
|
}
|
|
1297
|
-
var
|
|
1298
|
-
var
|
|
1299
|
-
"src/
|
|
1300
|
-
"use strict";
|
|
1301
|
-
init_framer();
|
|
1302
|
-
init_webflow();
|
|
1303
|
-
init_wix();
|
|
1304
|
-
ALL_PLATFORMS = [framer, webflow, wix];
|
|
1305
|
-
}
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
// src/platforms/index.ts
|
|
1309
|
-
var platforms_exports = {};
|
|
1310
|
-
__export(platforms_exports, {
|
|
1311
|
-
detectByDom: () => detectByDom,
|
|
1312
|
-
detectByHtml: () => detectByHtml,
|
|
1313
|
-
detectByUrl: () => detectByUrl,
|
|
1314
|
-
detectPlatform: () => detectPlatform,
|
|
1315
|
-
framer: () => framer,
|
|
1316
|
-
getPlatformByName: () => getPlatformByName,
|
|
1317
|
-
webflow: () => webflow,
|
|
1318
|
-
wix: () => wix
|
|
1319
|
-
});
|
|
1320
|
-
var init_platforms = __esm({
|
|
1321
|
-
"src/platforms/index.ts"() {
|
|
2945
|
+
var AntiBotError;
|
|
2946
|
+
var init_anti_bot = __esm({
|
|
2947
|
+
"src/exporter/anti-bot.ts"() {
|
|
1322
2948
|
"use strict";
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
2949
|
+
init_theme();
|
|
2950
|
+
AntiBotError = class extends Error {
|
|
2951
|
+
type;
|
|
2952
|
+
platform;
|
|
2953
|
+
url;
|
|
2954
|
+
constructor(type, platform, url, message) {
|
|
2955
|
+
super(message || `Anti-bot protection detected (${type})`);
|
|
2956
|
+
this.name = "AntiBotError";
|
|
2957
|
+
this.type = type;
|
|
2958
|
+
this.platform = platform;
|
|
2959
|
+
this.url = url;
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
1327
2962
|
}
|
|
1328
2963
|
});
|
|
1329
2964
|
|
|
@@ -1388,16 +3023,30 @@ async function launchAndCapture(exporter) {
|
|
|
1388
3023
|
log("Platform refined from DOM: " + domDetected.displayName + " (override: " + exporter.platform.name + ")");
|
|
1389
3024
|
exporter.platform = domDetected;
|
|
1390
3025
|
}
|
|
3026
|
+
const antiBot = await detectAntiBotPage(exporter.page);
|
|
3027
|
+
if (antiBot) {
|
|
3028
|
+
throw new AntiBotError(antiBot, exporter.platform.displayName, exporter.siteUrl);
|
|
3029
|
+
}
|
|
3030
|
+
if (exporter.platform.preCapture) {
|
|
3031
|
+
try {
|
|
3032
|
+
exporter.cooking?.update("Preparing " + exporter.platform.displayName + " page...");
|
|
3033
|
+
await exporter.platform.preCapture(exporter.page);
|
|
3034
|
+
log("preCapture hook completed");
|
|
3035
|
+
} catch (e) {
|
|
3036
|
+
log("preCapture hook error (continuing): " + e.message);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
1391
3039
|
exporter.cooking?.update("Waiting for " + exporter.platform.displayName + " hydration...");
|
|
1392
3040
|
if (exporter.platform.needsHydrationCheck) {
|
|
1393
|
-
|
|
3041
|
+
const sel = exporter.platform.hydrationSelector || "#main";
|
|
3042
|
+
log("Checking for " + sel + " element hydration...");
|
|
1394
3043
|
log("Hydration timeout: " + exporter.platform.hydrationTimeout + "ms");
|
|
1395
3044
|
const timeout = exporter.platform.hydrationTimeout;
|
|
1396
3045
|
await exporter.page.evaluate(`
|
|
1397
3046
|
new Promise(function(r) {
|
|
1398
3047
|
var start = Date.now();
|
|
1399
3048
|
var tick = function() {
|
|
1400
|
-
var m = document.
|
|
3049
|
+
var m = document.querySelector(${JSON.stringify(sel)}) || document.body;
|
|
1401
3050
|
if (m && m.children.length > 0) setTimeout(r, 2000);
|
|
1402
3051
|
else if (Date.now() - start > ${timeout}) r();
|
|
1403
3052
|
else setTimeout(tick, 200);
|
|
@@ -1411,27 +3060,38 @@ async function launchAndCapture(exporter) {
|
|
|
1411
3060
|
await new Promise((r) => setTimeout(r, exporter.platform.hydrationTimeout));
|
|
1412
3061
|
success(exporter.platform.displayName + " page rendered");
|
|
1413
3062
|
}
|
|
1414
|
-
exporter.
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
3063
|
+
const scrollStrategy = exporter.platform.scrollStrategy || "standard";
|
|
3064
|
+
if (scrollStrategy === "none") {
|
|
3065
|
+
log("Scroll skipped (scrollStrategy: none)");
|
|
3066
|
+
} else {
|
|
3067
|
+
exporter.cooking?.update("Scrolling page...");
|
|
3068
|
+
log("Starting full-page scroll (step: " + CFG.scrollStep + "px, delay: " + CFG.scrollDelay + "ms)");
|
|
3069
|
+
const scrollStep = CFG.scrollStep;
|
|
3070
|
+
const scrollDelay = CFG.scrollDelay;
|
|
3071
|
+
const pageHeight = await exporter.page.evaluate(`
|
|
3072
|
+
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
3073
|
+
`);
|
|
3074
|
+
log("Page height: " + pageHeight + "px (" + Math.ceil(pageHeight / scrollStep) + " scroll steps)");
|
|
3075
|
+
const passes = scrollStrategy === "infinite" ? 3 : 1;
|
|
3076
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
3077
|
+
await exporter.page.evaluate(`
|
|
3078
|
+
new Promise(function(r) {
|
|
3079
|
+
var y = 0;
|
|
3080
|
+
var max = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
3081
|
+
var step = function() {
|
|
3082
|
+
y += ${scrollStep};
|
|
3083
|
+
window.scrollTo({ top: y, behavior: 'instant' });
|
|
3084
|
+
y < max + 500 ? setTimeout(step, ${scrollDelay}) : (window.scrollTo(0, 0), r());
|
|
3085
|
+
};
|
|
3086
|
+
step();
|
|
3087
|
+
})
|
|
3088
|
+
`);
|
|
3089
|
+
if (scrollStrategy === "infinite" && pass < passes - 1) {
|
|
3090
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
success("Full-page scroll complete");
|
|
3094
|
+
}
|
|
1435
3095
|
exporter.cooking?.update("Waiting for lazy resources...");
|
|
1436
3096
|
log("Waiting 2s for lazy-loaded resources...");
|
|
1437
3097
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
@@ -1442,6 +3102,19 @@ async function launchAndCapture(exporter) {
|
|
|
1442
3102
|
} catch {
|
|
1443
3103
|
log("Network idle timeout reached (continuing anyway)");
|
|
1444
3104
|
}
|
|
3105
|
+
if (exporter.platform.captureRenderedDom) {
|
|
3106
|
+
try {
|
|
3107
|
+
const rendered = await exporter.page.evaluate(
|
|
3108
|
+
() => "<!DOCTYPE html>\n" + document.documentElement.outerHTML
|
|
3109
|
+
);
|
|
3110
|
+
if (rendered && rendered.length > 200) {
|
|
3111
|
+
exporter.ssrHTML = rendered;
|
|
3112
|
+
log("Captured rendered DOM as HTML source (" + (rendered.length / 1024).toFixed(1) + " KB)");
|
|
3113
|
+
}
|
|
3114
|
+
} catch (e) {
|
|
3115
|
+
log("Rendered DOM capture failed (keeping SSR HTML): " + e.message);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
1445
3118
|
const totalCaptured = exporter.assets.buffers.size;
|
|
1446
3119
|
success("Captured " + totalCaptured + " network resources total");
|
|
1447
3120
|
const cssCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.endsWith(".css")).length;
|
|
@@ -1485,6 +3158,7 @@ var init_capture = __esm({
|
|
|
1485
3158
|
init_config();
|
|
1486
3159
|
init_logger();
|
|
1487
3160
|
init_platforms();
|
|
3161
|
+
init_anti_bot();
|
|
1488
3162
|
}
|
|
1489
3163
|
});
|
|
1490
3164
|
|
|
@@ -1680,6 +3354,9 @@ const ANSI = {
|
|
|
1680
3354
|
border: '\\x1b[38;2;72;72;72m',
|
|
1681
3355
|
success: '\\x1b[38;2;127;216;143m',
|
|
1682
3356
|
info: '\\x1b[38;2;86;182;194m',
|
|
3357
|
+
accent: '\\x1b[38;2;157;124;216m',
|
|
3358
|
+
warning: '\\x1b[38;2;245;167;66m',
|
|
3359
|
+
command: '\\x1b[38;2;255;192;159m',
|
|
1683
3360
|
error: '\\x1b[38;2;224;108;117m',
|
|
1684
3361
|
};
|
|
1685
3362
|
|
|
@@ -1687,10 +3364,32 @@ function color(name, value) {
|
|
|
1687
3364
|
return ANSI[name] + value + ANSI.reset;
|
|
1688
3365
|
}
|
|
1689
3366
|
|
|
1690
|
-
function
|
|
1691
|
-
const text =
|
|
1692
|
-
|
|
1693
|
-
|
|
3367
|
+
function fitValue(value, width) {
|
|
3368
|
+
const text = String(value);
|
|
3369
|
+
if (text.length <= width) return text;
|
|
3370
|
+
if ((text.includes('\\\\') || text.includes('/')) && width > 12) {
|
|
3371
|
+
const separator = text.includes('\\\\') ? '\\\\' : '/';
|
|
3372
|
+
const prefix = text.startsWith(separator) ? separator : '';
|
|
3373
|
+
const parts = text.split(/[\\\\/]+/).filter(Boolean);
|
|
3374
|
+
const compact = prefix + parts.map((part, index) => {
|
|
3375
|
+
if (index === parts.length - 1) return part;
|
|
3376
|
+
if (/^[A-Za-z]:$/.test(part)) return part;
|
|
3377
|
+
return part.length > 5 ? part.slice(0, 3) + '..' : part;
|
|
3378
|
+
}).join(separator);
|
|
3379
|
+
if (compact.length <= width) return compact;
|
|
3380
|
+
const tail = Math.max(8, Math.floor(width * 0.45));
|
|
3381
|
+
const head = Math.max(0, width - tail - 4);
|
|
3382
|
+
return compact.slice(0, head) + separator + '..' + separator + compact.slice(-tail);
|
|
3383
|
+
}
|
|
3384
|
+
return width <= 3 ? '.'.repeat(Math.max(0, width)) : text.slice(0, width - 2) + '..';
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
function frameLine(label, value, colorName = 'text') {
|
|
3388
|
+
const labelText = label.padEnd(10);
|
|
3389
|
+
const valueWidth = Math.max(0, 56 - labelText.length);
|
|
3390
|
+
const shown = fitValue(value, valueWidth);
|
|
3391
|
+
const pad = Math.max(0, 56 - labelText.length - shown.length);
|
|
3392
|
+
console.log(' ' + color('border', '\u2502 ') + color('muted', labelText) + color(colorName, shown) + ' '.repeat(pad) + color('border', ' \u2502'));
|
|
1694
3393
|
}
|
|
1695
3394
|
|
|
1696
3395
|
function drawServerUi() {
|
|
@@ -1698,10 +3397,10 @@ function drawServerUi() {
|
|
|
1698
3397
|
console.log(' ' + color('border', '\u256D\u2500' + '\u2500'.repeat(56) + '\u2500\u256E'));
|
|
1699
3398
|
console.log(' ' + color('border', '\u2502 ') + color('text', ' Framer Export preview server') + ' '.repeat(26) + color('border', ' \u2502'));
|
|
1700
3399
|
console.log(' ' + color('border', '\u251C\u2500' + '\u2500'.repeat(56) + '\u2500\u2524'));
|
|
1701
|
-
frameLine('URL', 'http://localhost:' + PORT);
|
|
1702
|
-
frameLine('Root', ROOT);
|
|
1703
|
-
frameLine('Run', 'node serve.js');
|
|
1704
|
-
frameLine('Mode', 'static mirror + SPA fallback');
|
|
3400
|
+
frameLine('URL', 'http://localhost:' + PORT, 'primary');
|
|
3401
|
+
frameLine('Root', ROOT, 'soft');
|
|
3402
|
+
frameLine('Run', 'node serve.js', 'command');
|
|
3403
|
+
frameLine('Mode', 'static mirror + SPA fallback', 'accent');
|
|
1705
3404
|
console.log(' ' + color('border', '\u2570\u2500' + '\u2500'.repeat(56) + '\u2500\u256F'));
|
|
1706
3405
|
console.log('');
|
|
1707
3406
|
console.log(' ' + color('success', 'ready') + color('muted', ' press Ctrl+C to stop') + '\\n');
|
|
@@ -1710,11 +3409,13 @@ function drawServerUi() {
|
|
|
1710
3409
|
server.listen(PORT, () => {
|
|
1711
3410
|
const frames = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
|
|
1712
3411
|
const steps = ['Preparing server', 'Checking exported files', 'Binding local port', 'Serving static mirror'];
|
|
3412
|
+
const stepColors = ['text', 'info', 'warning', 'success'];
|
|
1713
3413
|
let i = 0;
|
|
1714
3414
|
const timer = setInterval(() => {
|
|
1715
|
-
const
|
|
3415
|
+
const stepIndex = Math.min(steps.length - 1, Math.floor(i / 6));
|
|
3416
|
+
const step = steps[stepIndex];
|
|
1716
3417
|
const dots = color('error', '.'.repeat(i % 4).padEnd(3, ' '));
|
|
1717
|
-
process.stdout.write('\\r\\x1B[2K ' + color('primary', frames[i % frames.length]) + ' ' + color(
|
|
3418
|
+
process.stdout.write('\\r\\x1B[2K ' + color('primary', frames[i % frames.length]) + ' ' + color(stepColors[stepIndex], step) + dots);
|
|
1718
3419
|
i++;
|
|
1719
3420
|
}, 80);
|
|
1720
3421
|
|
|
@@ -1871,12 +3572,36 @@ async function buildOutput(exporter) {
|
|
|
1871
3572
|
log(" Pattern removed " + removed + " chars");
|
|
1872
3573
|
}
|
|
1873
3574
|
}
|
|
3575
|
+
if (exporter.platform.stripScripts && exporter.platform.stripScripts.length > 0) {
|
|
3576
|
+
log("Applying " + exporter.platform.stripScripts.length + " script-strip patterns...");
|
|
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
|
+
}
|
|
3583
|
+
}
|
|
1874
3584
|
success("Platform badges and tracking stripped");
|
|
3585
|
+
if (exporter.platform.postCapture) {
|
|
3586
|
+
try {
|
|
3587
|
+
const before = html.length;
|
|
3588
|
+
html = exporter.platform.postCapture(html, exporter);
|
|
3589
|
+
log("postCapture hook applied (delta " + (html.length - before) + " chars)");
|
|
3590
|
+
} catch (e) {
|
|
3591
|
+
warn("postCapture hook failed: " + e.message);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
1875
3594
|
exporter.cooking?.update("Rewriting asset URLs...");
|
|
1876
3595
|
log("Rewriting " + exporter.assets.entries.size + " CDN URLs to local paths...");
|
|
1877
3596
|
const beforeRewrite = html.length;
|
|
1878
3597
|
html = exporter.assets.rewrite(html, "");
|
|
1879
3598
|
log("HTML rewrite delta: " + (html.length - beforeRewrite) + " chars");
|
|
3599
|
+
if (exporter.platform.rewriteUrlPatterns && exporter.platform.rewriteUrlPatterns.length > 0) {
|
|
3600
|
+
for (const { from, to } of exporter.platform.rewriteUrlPatterns) {
|
|
3601
|
+
html = html.replace(new RegExp(from.source, from.flags), to);
|
|
3602
|
+
}
|
|
3603
|
+
log("Applied " + exporter.platform.rewriteUrlPatterns.length + " custom URL rewrites");
|
|
3604
|
+
}
|
|
1880
3605
|
exporter.cooking?.update("Cleaning srcset references...");
|
|
1881
3606
|
html = stripSrcsetCdnUrls(html);
|
|
1882
3607
|
log("Cleaned remaining CDN URLs from srcset attributes");
|
|
@@ -2172,7 +3897,7 @@ function buildServeCommand(exporter) {
|
|
|
2172
3897
|
return `cd ${path5.basename(exporter.outDir)} && node serve.js`;
|
|
2173
3898
|
}
|
|
2174
3899
|
async function runExportCompletePrompt(exporter, serveCommand) {
|
|
2175
|
-
let copyServeCommand =
|
|
3900
|
+
let copyServeCommand = true;
|
|
2176
3901
|
while (true) {
|
|
2177
3902
|
const action = await select(
|
|
2178
3903
|
"Export complete",
|
|
@@ -2613,6 +4338,7 @@ var init_exporter = __esm({
|
|
|
2613
4338
|
init_download();
|
|
2614
4339
|
init_logger();
|
|
2615
4340
|
init_capture();
|
|
4341
|
+
init_anti_bot();
|
|
2616
4342
|
init_download2();
|
|
2617
4343
|
init_output();
|
|
2618
4344
|
init_summary();
|
|
@@ -2689,15 +4415,35 @@ var init_exporter = __esm({
|
|
|
2689
4415
|
this.platform = htmlDetected;
|
|
2690
4416
|
log("Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)");
|
|
2691
4417
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
4418
|
+
try {
|
|
4419
|
+
await launchAndCapture(this);
|
|
4420
|
+
if (includeSubpages && this.page) {
|
|
4421
|
+
await this.crawlSubpages();
|
|
4422
|
+
}
|
|
4423
|
+
await closeBrowser(this);
|
|
4424
|
+
this.cooking.update("Downloading assets...");
|
|
4425
|
+
await downloadAll(this);
|
|
4426
|
+
this.cooking.update("Building output...");
|
|
4427
|
+
await buildOutput(this);
|
|
4428
|
+
if (this.platform.postProcess) {
|
|
4429
|
+
this.cooking.update("Running " + this.platform.displayName + " post-processing...");
|
|
4430
|
+
try {
|
|
4431
|
+
await this.platform.postProcess(this);
|
|
4432
|
+
log("postProcess hook completed");
|
|
4433
|
+
} catch (e) {
|
|
4434
|
+
log("postProcess hook error (continuing): " + e.message);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
} catch (e) {
|
|
4438
|
+
this.cooking?.stop();
|
|
4439
|
+
setCooking(null);
|
|
4440
|
+
await closeBrowser(this);
|
|
4441
|
+
if (e instanceof AntiBotError) {
|
|
4442
|
+
await fs5.rm(this.outDir, { recursive: true, force: true }).catch(() => {
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
throw e;
|
|
2695
4446
|
}
|
|
2696
|
-
await closeBrowser(this);
|
|
2697
|
-
this.cooking.update("Downloading assets...");
|
|
2698
|
-
await downloadAll(this);
|
|
2699
|
-
this.cooking.update("Building output...");
|
|
2700
|
-
await buildOutput(this);
|
|
2701
4447
|
this.cooking.stop();
|
|
2702
4448
|
setCooking(null);
|
|
2703
4449
|
console.log("");
|
|
@@ -2768,6 +4514,70 @@ var init_exporter = __esm({
|
|
|
2768
4514
|
}
|
|
2769
4515
|
});
|
|
2770
4516
|
|
|
4517
|
+
// src/cli/select-grouped.ts
|
|
4518
|
+
import chalk6 from "chalk";
|
|
4519
|
+
async function selectPlatform(siteUrl) {
|
|
4520
|
+
const grouped = platformsByCategory();
|
|
4521
|
+
const detected = detectByUrl(siteUrl);
|
|
4522
|
+
if (detected) {
|
|
4523
|
+
const choice = await select(
|
|
4524
|
+
"Platform detected",
|
|
4525
|
+
[
|
|
4526
|
+
{ label: `Use ${detected.displayName}`, value: USE_DETECTED },
|
|
4527
|
+
{ label: "Browse all platforms", value: BROWSE }
|
|
4528
|
+
],
|
|
4529
|
+
0,
|
|
4530
|
+
{
|
|
4531
|
+
headerLines: [`URL: ${siteUrl}`, `Detected: ${detected.displayName}`],
|
|
4532
|
+
actions: [{ label: "Modify URL", value: MODIFY_URL }],
|
|
4533
|
+
footer: "tab focus button \xB7 enter select \xB7 mouse hover/click"
|
|
4534
|
+
}
|
|
4535
|
+
);
|
|
4536
|
+
if (choice === MODIFY_URL) return { modifyUrl: true };
|
|
4537
|
+
if (choice === USE_DETECTED) return { platform: detected.name };
|
|
4538
|
+
}
|
|
4539
|
+
let categoryDefault = detected ? Math.max(0, CATEGORY_ORDER.indexOf(detected.category)) : 0;
|
|
4540
|
+
while (true) {
|
|
4541
|
+
const categoryOptions = CATEGORY_ORDER.map((cat2) => ({
|
|
4542
|
+
label: `${CATEGORY_LABELS[cat2]} ${chalk6.gray(`(${grouped[cat2].length})`)}`,
|
|
4543
|
+
value: cat2
|
|
4544
|
+
}));
|
|
4545
|
+
const category = await select("Select a category", categoryOptions, categoryDefault, {
|
|
4546
|
+
headerLines: [`URL: ${siteUrl}`],
|
|
4547
|
+
actions: [{ label: "Modify URL", value: MODIFY_URL }],
|
|
4548
|
+
footer: "tab focus button \xB7 enter next \xB7 mouse hover/click"
|
|
4549
|
+
});
|
|
4550
|
+
if (category === MODIFY_URL) return { modifyUrl: true };
|
|
4551
|
+
const cat = category;
|
|
4552
|
+
categoryDefault = CATEGORY_ORDER.indexOf(cat);
|
|
4553
|
+
const handlers = grouped[cat];
|
|
4554
|
+
const platformOptions = handlers.map((handler) => ({
|
|
4555
|
+
label: detected && handler.name === detected.name ? `${handler.displayName}${chalk6.gray(" (detected)")}` : handler.displayName,
|
|
4556
|
+
value: handler.name
|
|
4557
|
+
}));
|
|
4558
|
+
const platformDefault = detected && detected.category === cat ? Math.max(0, handlers.findIndex((handler) => handler.name === detected.name)) : 0;
|
|
4559
|
+
const platform = await select("Select platform", platformOptions, platformDefault, {
|
|
4560
|
+
headerLines: [`Category: ${CATEGORY_LABELS[cat]}`],
|
|
4561
|
+
actions: [{ label: "Back to categories", value: BACK }],
|
|
4562
|
+
footer: "tab focus button \xB7 enter select \xB7 mouse hover/click"
|
|
4563
|
+
});
|
|
4564
|
+
if (platform === BACK) continue;
|
|
4565
|
+
return { platform };
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
var MODIFY_URL, BROWSE, USE_DETECTED, BACK;
|
|
4569
|
+
var init_select_grouped = __esm({
|
|
4570
|
+
"src/cli/select-grouped.ts"() {
|
|
4571
|
+
"use strict";
|
|
4572
|
+
init_select();
|
|
4573
|
+
init_platforms();
|
|
4574
|
+
MODIFY_URL = "__modify_url__";
|
|
4575
|
+
BROWSE = "__browse__";
|
|
4576
|
+
USE_DETECTED = "__use_detected__";
|
|
4577
|
+
BACK = "__back__";
|
|
4578
|
+
}
|
|
4579
|
+
});
|
|
4580
|
+
|
|
2771
4581
|
// src/cli/setup.ts
|
|
2772
4582
|
var setup_exports = {};
|
|
2773
4583
|
__export(setup_exports, {
|
|
@@ -2777,7 +4587,7 @@ import readline2 from "readline/promises";
|
|
|
2777
4587
|
import { stdin as stdin3, stdout as stdout3 } from "process";
|
|
2778
4588
|
import path7 from "path";
|
|
2779
4589
|
import { URL as URL4 } from "url";
|
|
2780
|
-
import
|
|
4590
|
+
import chalk7 from "chalk";
|
|
2781
4591
|
function drawHeader(title) {
|
|
2782
4592
|
const w = maxWidth();
|
|
2783
4593
|
if (w < 50) {
|
|
@@ -2794,7 +4604,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2794
4604
|
showBanner();
|
|
2795
4605
|
console.log(` ${ui.text.bold("Framer Export setup")} ${chip("interactive")}`);
|
|
2796
4606
|
console.log(
|
|
2797
|
-
` ${ui.muted("Export
|
|
4607
|
+
` ${ui.muted("Export 25+ website platforms into a clean, self-hosted local mirror.")}
|
|
2798
4608
|
`
|
|
2799
4609
|
);
|
|
2800
4610
|
const rl = legacyMode ? readline2.createInterface({ input: stdin3, output: stdout3 }) : null;
|
|
@@ -2802,7 +4612,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2802
4612
|
if (!legacyMode) {
|
|
2803
4613
|
return promptInput(question, defaultVal || "", { headerLines });
|
|
2804
4614
|
}
|
|
2805
|
-
const suffix = defaultVal ?
|
|
4615
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2806
4616
|
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
2807
4617
|
const answer = await rl.question(prompt);
|
|
2808
4618
|
return answer.trim() || defaultVal || "";
|
|
@@ -2829,7 +4639,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2829
4639
|
}
|
|
2830
4640
|
}
|
|
2831
4641
|
if (legacyMode) {
|
|
2832
|
-
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${
|
|
4642
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk7.underline(siteUrl)}
|
|
2833
4643
|
`);
|
|
2834
4644
|
}
|
|
2835
4645
|
let platformName = null;
|
|
@@ -2837,31 +4647,15 @@ async function runSetup(legacyMode = false) {
|
|
|
2837
4647
|
drawHeader("Step 2 : Platform");
|
|
2838
4648
|
const detected = detectPlatform(siteUrl);
|
|
2839
4649
|
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
2840
|
-
const platformInput = await ask("Platform
|
|
2841
|
-
|
|
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;
|
|
2842
4653
|
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
2843
4654
|
`);
|
|
2844
4655
|
} else {
|
|
2845
4656
|
while (!platformName) {
|
|
2846
|
-
const
|
|
2847
|
-
|
|
2848
|
-
{
|
|
2849
|
-
label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
|
|
2850
|
-
value: "framer"
|
|
2851
|
-
},
|
|
2852
|
-
{
|
|
2853
|
-
label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
|
|
2854
|
-
value: "webflow"
|
|
2855
|
-
},
|
|
2856
|
-
{ label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
|
|
2857
|
-
];
|
|
2858
|
-
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
2859
|
-
const platformChoice = await select("Select platform", platforms, Math.max(0, defaultIdx), {
|
|
2860
|
-
headerLines: [`URL: ${siteUrl}`],
|
|
2861
|
-
actions: [{ label: "Modify URL", value: "modify-url" }],
|
|
2862
|
-
footer: "tab focus button \xB7 mouse hover/click \xB7 enter select"
|
|
2863
|
-
});
|
|
2864
|
-
if (platformChoice === "modify-url") {
|
|
4657
|
+
const selection = await selectPlatform(siteUrl);
|
|
4658
|
+
if ("modifyUrl" in selection) {
|
|
2865
4659
|
siteUrl = "";
|
|
2866
4660
|
urlError = "";
|
|
2867
4661
|
while (!siteUrl) {
|
|
@@ -2880,7 +4674,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2880
4674
|
}
|
|
2881
4675
|
continue;
|
|
2882
4676
|
}
|
|
2883
|
-
platformName =
|
|
4677
|
+
platformName = selection.platform;
|
|
2884
4678
|
}
|
|
2885
4679
|
}
|
|
2886
4680
|
if (!platformName) {
|
|
@@ -2891,26 +4685,62 @@ async function runSetup(legacyMode = false) {
|
|
|
2891
4685
|
if (!legacyMode) {
|
|
2892
4686
|
return promptInput(question, defaultVal || "", { headerLines });
|
|
2893
4687
|
}
|
|
2894
|
-
const suffix = defaultVal ?
|
|
4688
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2895
4689
|
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
2896
4690
|
const answer = await rl2.question(prompt);
|
|
2897
4691
|
return answer.trim() || defaultVal || "";
|
|
2898
4692
|
};
|
|
2899
4693
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
2900
|
-
|
|
2901
|
-
const outDir = await ask2("Output directory", "./" + derivedName, [
|
|
2902
|
-
"Step 3 : Output Directory",
|
|
2903
|
-
`URL: ${siteUrl}`,
|
|
2904
|
-
`Platform: ${platformName}`
|
|
2905
|
-
]);
|
|
4694
|
+
let outDir = "./" + derivedName;
|
|
2906
4695
|
if (legacyMode) {
|
|
4696
|
+
drawHeader("Step 3 : Output Directory");
|
|
4697
|
+
outDir = await ask2("Output directory", outDir, [
|
|
4698
|
+
"Step 3 : Output Directory",
|
|
4699
|
+
`URL: ${siteUrl}`,
|
|
4700
|
+
`Platform: ${platformName}`
|
|
4701
|
+
]);
|
|
2907
4702
|
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
2908
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
|
+
]);
|
|
2909
4711
|
}
|
|
2910
|
-
|
|
2911
|
-
let
|
|
2912
|
-
let
|
|
2913
|
-
let
|
|
4712
|
+
let prettyPrint = true;
|
|
4713
|
+
let concurrency = 12;
|
|
4714
|
+
let includeSubpages = false;
|
|
4715
|
+
let startExport = false;
|
|
4716
|
+
const summaryRows = () => [
|
|
4717
|
+
["URL", siteUrl],
|
|
4718
|
+
["Platform", platformName],
|
|
4719
|
+
["Output", path7.resolve(outDir)],
|
|
4720
|
+
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
4721
|
+
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
4722
|
+
["Concurrency", String(concurrency)]
|
|
4723
|
+
];
|
|
4724
|
+
const summaryLines = () => summaryRows().map(([label, value]) => `${label}: ${value}`);
|
|
4725
|
+
const printSummary2 = () => {
|
|
4726
|
+
const w = maxWidth();
|
|
4727
|
+
const isSmall = w < 50;
|
|
4728
|
+
console.log("");
|
|
4729
|
+
if (!isSmall) {
|
|
4730
|
+
console.log(boxTop(w));
|
|
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
|
+
};
|
|
2914
4744
|
if (legacyMode) {
|
|
2915
4745
|
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
2916
4746
|
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
@@ -2928,67 +4758,104 @@ async function runSetup(legacyMode = false) {
|
|
|
2928
4758
|
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
2929
4759
|
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
2930
4760
|
`);
|
|
2931
|
-
|
|
2932
|
-
const prettyVal = await select("Pretty-print JS files?", [
|
|
2933
|
-
{ label: "Yes", value: "yes" },
|
|
2934
|
-
{ label: "No", value: "no" }
|
|
2935
|
-
]);
|
|
2936
|
-
prettyPrint = prettyVal === "yes";
|
|
2937
|
-
const subpagesVal = await select(
|
|
2938
|
-
"Export sub-pages?",
|
|
2939
|
-
[
|
|
2940
|
-
{ label: "No", value: "no" },
|
|
2941
|
-
{ label: "Yes, crawl and export", value: "yes" }
|
|
2942
|
-
],
|
|
2943
|
-
0
|
|
2944
|
-
);
|
|
2945
|
-
includeSubpages = subpagesVal === "yes";
|
|
2946
|
-
const concurrencyVal = await select(
|
|
2947
|
-
"Download concurrency",
|
|
2948
|
-
[
|
|
2949
|
-
{ label: "6 (slow connection)", value: "6" },
|
|
2950
|
-
{ label: "12 (default)", value: "12" },
|
|
2951
|
-
{ label: "20 (fast connection)", value: "20" }
|
|
2952
|
-
],
|
|
2953
|
-
1
|
|
2954
|
-
);
|
|
2955
|
-
concurrency = parseInt(concurrencyVal, 10);
|
|
2956
|
-
}
|
|
2957
|
-
const w = maxWidth();
|
|
2958
|
-
const isSmall = w < 50;
|
|
2959
|
-
console.log("");
|
|
2960
|
-
if (!isSmall) {
|
|
2961
|
-
console.log(boxTop(w));
|
|
2962
|
-
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
2963
|
-
console.log(boxSep(w));
|
|
2964
|
-
} else {
|
|
2965
|
-
console.log(ui.text.bold(" Summary:"));
|
|
2966
|
-
}
|
|
2967
|
-
for (const [label, value] of [
|
|
2968
|
-
["URL", siteUrl],
|
|
2969
|
-
["Platform", platformName],
|
|
2970
|
-
["Output", path7.resolve(outDir)],
|
|
2971
|
-
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
2972
|
-
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
2973
|
-
["Concurrency", String(concurrency)]
|
|
2974
|
-
]) {
|
|
2975
|
-
console.log(boxRow(w, label, value));
|
|
2976
|
-
}
|
|
2977
|
-
if (!isSmall) {
|
|
2978
|
-
console.log(boxBot(w));
|
|
2979
|
-
}
|
|
2980
|
-
console.log("");
|
|
2981
|
-
let startExport;
|
|
2982
|
-
if (legacyMode) {
|
|
4761
|
+
printSummary2();
|
|
2983
4762
|
const confirm = await ask2("Start export? (y/n)", "y");
|
|
2984
4763
|
startExport = confirm.toLowerCase().startsWith("y");
|
|
2985
4764
|
rl2.close();
|
|
2986
4765
|
} else {
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
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;
|
|
4835
|
+
}
|
|
4836
|
+
concurrency = parseInt(concurrencyVal, 10);
|
|
4837
|
+
optionStep = 3;
|
|
4838
|
+
}
|
|
4839
|
+
const confirmVal = await select(
|
|
4840
|
+
"Ready to export?",
|
|
4841
|
+
[
|
|
4842
|
+
{ label: "Previous: edit options", value: "previous" },
|
|
4843
|
+
{ label: "Next: start now", value: "next" },
|
|
4844
|
+
{ label: "Cancel", value: "cancel" }
|
|
4845
|
+
],
|
|
4846
|
+
1,
|
|
4847
|
+
{
|
|
4848
|
+
headerLines: ["Summary", ...summaryLines()],
|
|
4849
|
+
footer: "previous edit options \xB7 next start export \xB7 mouse hover/click"
|
|
4850
|
+
}
|
|
4851
|
+
);
|
|
4852
|
+
if (confirmVal === "previous") {
|
|
4853
|
+
optionStep = 2;
|
|
4854
|
+
continue;
|
|
4855
|
+
}
|
|
4856
|
+
startExport = confirmVal === "next";
|
|
4857
|
+
break;
|
|
4858
|
+
}
|
|
2992
4859
|
}
|
|
2993
4860
|
if (!startExport) {
|
|
2994
4861
|
console.log(`
|
|
@@ -3001,7 +4868,16 @@ async function runSetup(legacyMode = false) {
|
|
|
3001
4868
|
CFG2.concurrency = concurrency;
|
|
3002
4869
|
const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
|
|
3003
4870
|
exporter.prettyPrint = prettyPrint;
|
|
3004
|
-
|
|
4871
|
+
try {
|
|
4872
|
+
await exporter.run(includeSubpages);
|
|
4873
|
+
} catch (e) {
|
|
4874
|
+
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
4875
|
+
if (e instanceof AntiBotError2) {
|
|
4876
|
+
console.log(formatAntiBotError2(e));
|
|
4877
|
+
process.exit(1);
|
|
4878
|
+
}
|
|
4879
|
+
throw e;
|
|
4880
|
+
}
|
|
3005
4881
|
}
|
|
3006
4882
|
var init_setup = __esm({
|
|
3007
4883
|
"src/cli/setup.ts"() {
|
|
@@ -3010,6 +4886,7 @@ var init_setup = __esm({
|
|
|
3010
4886
|
init_exporter();
|
|
3011
4887
|
init_platforms();
|
|
3012
4888
|
init_select();
|
|
4889
|
+
init_select_grouped();
|
|
3013
4890
|
init_box();
|
|
3014
4891
|
init_theme();
|
|
3015
4892
|
}
|
|
@@ -3024,6 +4901,7 @@ import { spawnSync } from "child_process";
|
|
|
3024
4901
|
// src/cli/help.ts
|
|
3025
4902
|
init_banner();
|
|
3026
4903
|
init_theme();
|
|
4904
|
+
init_platforms();
|
|
3027
4905
|
function showHelp() {
|
|
3028
4906
|
showBanner();
|
|
3029
4907
|
console.log(`${ui.text.bold(" USAGE")} ${chip("cli")}
|
|
@@ -3038,7 +4916,7 @@ function showHelp() {
|
|
|
3038
4916
|
console.log(ui.text.bold(" OPTIONS\n"));
|
|
3039
4917
|
const opts = [
|
|
3040
4918
|
["--setup", "Launch the interactive setup assistant"],
|
|
3041
|
-
["--platform <p>", "Force platform
|
|
4919
|
+
["--platform <p>", "Force platform by id (e.g. framer, shopify, notion)"],
|
|
3042
4920
|
["--subpages", "Crawl and export sub-pages"],
|
|
3043
4921
|
["--legacy-mode", "Use text input instead of arrow selection"],
|
|
3044
4922
|
["--help, -h", "Show this help message"]
|
|
@@ -3048,13 +4926,10 @@ function showHelp() {
|
|
|
3048
4926
|
}
|
|
3049
4927
|
console.log("");
|
|
3050
4928
|
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
[
|
|
3055
|
-
];
|
|
3056
|
-
for (const [name, desc] of platforms) {
|
|
3057
|
-
console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
|
|
4929
|
+
const grouped = platformsByCategory();
|
|
4930
|
+
for (const cat of CATEGORY_ORDER) {
|
|
4931
|
+
const names = grouped[cat].map((handler) => handler.displayName).join(", ");
|
|
4932
|
+
console.log(` ${ui.primary(CATEGORY_LABELS[cat].padEnd(22))} ${ui.muted(names)}`);
|
|
3058
4933
|
}
|
|
3059
4934
|
console.log("");
|
|
3060
4935
|
console.log(ui.text.bold(" EXAMPLES\n"));
|
|
@@ -3203,20 +5078,20 @@ async function main() {
|
|
|
3203
5078
|
process.exit(0);
|
|
3204
5079
|
}
|
|
3205
5080
|
if (args.includes("--about")) {
|
|
3206
|
-
const
|
|
5081
|
+
const chalk8 = (await import("chalk")).default;
|
|
3207
5082
|
showBanner();
|
|
3208
5083
|
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
3209
5084
|
console.log(` ${ui.text(package_default.description)}
|
|
3210
5085
|
`);
|
|
3211
5086
|
console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
|
|
3212
5087
|
console.log(
|
|
3213
|
-
` ${ui.text.bold("Portfolio")} ${
|
|
5088
|
+
` ${ui.text.bold("Portfolio")} ${chalk8.underline.hex("#FAB283")("https://github.com/danbenba")}`
|
|
3214
5089
|
);
|
|
3215
5090
|
console.log(
|
|
3216
|
-
` ${ui.text.bold("GitHub")} ${
|
|
5091
|
+
` ${ui.text.bold("GitHub")} ${chalk8.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`
|
|
3217
5092
|
);
|
|
3218
5093
|
console.log(
|
|
3219
|
-
` ${ui.text.bold("npm")} ${
|
|
5094
|
+
` ${ui.text.bold("npm")} ${chalk8.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`
|
|
3220
5095
|
);
|
|
3221
5096
|
console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
|
|
3222
5097
|
console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
|
|
@@ -3263,12 +5138,17 @@ async function main() {
|
|
|
3263
5138
|
includeSubpages
|
|
3264
5139
|
);
|
|
3265
5140
|
} catch (e) {
|
|
3266
|
-
const
|
|
5141
|
+
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
5142
|
+
if (e instanceof AntiBotError2) {
|
|
5143
|
+
console.log(formatAntiBotError2(e));
|
|
5144
|
+
process.exit(1);
|
|
5145
|
+
}
|
|
5146
|
+
const chalk8 = (await import("chalk")).default;
|
|
3267
5147
|
console.log(
|
|
3268
5148
|
`
|
|
3269
5149
|
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`
|
|
3270
5150
|
);
|
|
3271
|
-
console.log(
|
|
5151
|
+
console.log(chalk8.gray(e.stack || ""));
|
|
3272
5152
|
process.exit(1);
|
|
3273
5153
|
}
|
|
3274
5154
|
}
|