framer-export 4.0.0
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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/cli/index.js +1616 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1616 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/cli/banner.ts
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
function getWidth() {
|
|
16
|
+
return process.stdout.columns || 80;
|
|
17
|
+
}
|
|
18
|
+
function showBanner() {
|
|
19
|
+
const width = getWidth();
|
|
20
|
+
const isSmall = width < 65;
|
|
21
|
+
if (isSmall) {
|
|
22
|
+
console.log(`
|
|
23
|
+
${chalk.cyan.bold("F-EXPORT")} ${chalk.gray("v4.0.0")}`);
|
|
24
|
+
console.log(` ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
|
|
25
|
+
`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const magenta = chalk.magenta;
|
|
29
|
+
const cyan = chalk.cyan.bold;
|
|
30
|
+
const gray = chalk.gray;
|
|
31
|
+
console.log("");
|
|
32
|
+
ASCII_ART.forEach((line2) => {
|
|
33
|
+
console.log(" " + cyan(line2));
|
|
34
|
+
});
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log(` ${gray("v4.0.0")} ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
var ASCII_ART;
|
|
40
|
+
var init_banner = __esm({
|
|
41
|
+
"src/cli/banner.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
ASCII_ART = [
|
|
44
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
45
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
|
|
46
|
+
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 ",
|
|
47
|
+
"\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
48
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
49
|
+
"\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src/assets/asset-map.ts
|
|
55
|
+
import path from "path";
|
|
56
|
+
import crypto from "crypto";
|
|
57
|
+
import { URL as URL2 } from "url";
|
|
58
|
+
var AssetMap;
|
|
59
|
+
var init_asset_map = __esm({
|
|
60
|
+
"src/assets/asset-map.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
AssetMap = class {
|
|
63
|
+
entries = /* @__PURE__ */ new Map();
|
|
64
|
+
buffers = /* @__PURE__ */ new Map();
|
|
65
|
+
localPathFor(urlStr, platform) {
|
|
66
|
+
if (this.entries.has(urlStr)) return this.entries.get(urlStr).localPath;
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = new URL2(urlStr);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const host = parsed.hostname;
|
|
74
|
+
const pathname = parsed.pathname;
|
|
75
|
+
const ext = path.extname(pathname.split("?")[0]).toLowerCase();
|
|
76
|
+
let dir = null;
|
|
77
|
+
if (platform) {
|
|
78
|
+
dir = platform.mapAssetDir(host, pathname, ext);
|
|
79
|
+
}
|
|
80
|
+
if (!dir) {
|
|
81
|
+
dir = this.fallbackDir(host, ext);
|
|
82
|
+
}
|
|
83
|
+
let filename;
|
|
84
|
+
const baseName = path.basename(pathname.split("?")[0]);
|
|
85
|
+
const hash = crypto.createHash("md5").update(urlStr).digest("hex").slice(0, 6);
|
|
86
|
+
if (baseName && baseName.length > 1 && baseName !== "/") {
|
|
87
|
+
const clean = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
88
|
+
filename = clean.includes(".") ? clean : `${clean}-${hash}`;
|
|
89
|
+
} else {
|
|
90
|
+
filename = `asset-${hash}${ext || ""}`;
|
|
91
|
+
}
|
|
92
|
+
if (ext === ".mjs" || ext === ".js") {
|
|
93
|
+
filename = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
94
|
+
}
|
|
95
|
+
const localPath = `${dir}/${filename}`;
|
|
96
|
+
this.entries.set(urlStr, { localPath });
|
|
97
|
+
const base = urlStr.split("?")[0];
|
|
98
|
+
if (base !== urlStr && !this.entries.has(base)) {
|
|
99
|
+
this.entries.set(base, { localPath });
|
|
100
|
+
}
|
|
101
|
+
return localPath;
|
|
102
|
+
}
|
|
103
|
+
rewrite(text, fromDir = "") {
|
|
104
|
+
const sorted = [...this.entries.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
105
|
+
let out = text;
|
|
106
|
+
for (const [url, { localPath }] of sorted) {
|
|
107
|
+
const rel = fromDir ? path.posix.relative(fromDir, localPath) : localPath;
|
|
108
|
+
out = out.split(url).join(rel);
|
|
109
|
+
if (url.includes("&")) {
|
|
110
|
+
out = out.split(url.replace(/&/g, "&")).join(rel);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
fallbackDir(host, ext) {
|
|
116
|
+
if (host.includes("fonts.gstatic.com") || host.includes("fonts.googleapis.com")) {
|
|
117
|
+
return "assets/fonts";
|
|
118
|
+
}
|
|
119
|
+
return ext === ".mjs" || ext === ".js" ? "scripts/vendor" : "assets/misc";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// src/config/index.ts
|
|
126
|
+
var config_exports = {};
|
|
127
|
+
__export(config_exports, {
|
|
128
|
+
CFG: () => CFG
|
|
129
|
+
});
|
|
130
|
+
var CFG;
|
|
131
|
+
var init_config = __esm({
|
|
132
|
+
"src/config/index.ts"() {
|
|
133
|
+
"use strict";
|
|
134
|
+
CFG = {
|
|
135
|
+
viewport: { width: 1440, height: 900 },
|
|
136
|
+
timeout: 9e4,
|
|
137
|
+
scrollStep: 250,
|
|
138
|
+
scrollDelay: 60,
|
|
139
|
+
concurrency: 12,
|
|
140
|
+
retries: 3,
|
|
141
|
+
dlTimeout: 3e4,
|
|
142
|
+
sharedStripDomains: [
|
|
143
|
+
"sentry.io",
|
|
144
|
+
"www.googletagmanager.com",
|
|
145
|
+
"connect.facebook.net",
|
|
146
|
+
"stats.g.doubleclick.net",
|
|
147
|
+
"google-analytics.com"
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/network/download.ts
|
|
154
|
+
import https from "https";
|
|
155
|
+
import http from "http";
|
|
156
|
+
import { URL as URL3 } from "url";
|
|
157
|
+
function dlBuffer(url, retries = CFG.retries) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const proto = url.startsWith("https") ? https : http;
|
|
160
|
+
const go = (left) => {
|
|
161
|
+
const req = proto.get(url, { timeout: CFG.dlTimeout }, (res) => {
|
|
162
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
163
|
+
return dlBuffer(new URL3(res.headers.location, url).href, left).then(resolve, reject);
|
|
164
|
+
}
|
|
165
|
+
if (res.statusCode !== 200) {
|
|
166
|
+
return left > 1 ? setTimeout(() => go(left - 1), 500) : reject(new Error(`HTTP ${res.statusCode}`));
|
|
167
|
+
}
|
|
168
|
+
const ch = [];
|
|
169
|
+
res.on("data", (c) => ch.push(c));
|
|
170
|
+
res.on("end", () => resolve(Buffer.concat(ch)));
|
|
171
|
+
res.on("error", (e) => left > 1 ? setTimeout(() => go(left - 1), 500) : reject(e));
|
|
172
|
+
});
|
|
173
|
+
req.on("error", (e) => left > 1 ? setTimeout(() => go(left - 1), 500) : reject(e));
|
|
174
|
+
req.on("timeout", () => {
|
|
175
|
+
req.destroy();
|
|
176
|
+
left > 1 ? setTimeout(() => go(left - 1), 500) : reject(new Error("Timeout"));
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
go(retries);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
var init_download = __esm({
|
|
183
|
+
"src/network/download.ts"() {
|
|
184
|
+
"use strict";
|
|
185
|
+
init_config();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// src/logger/index.ts
|
|
190
|
+
import chalk3 from "chalk";
|
|
191
|
+
function setCooking(spinner) {
|
|
192
|
+
_cooking = spinner;
|
|
193
|
+
}
|
|
194
|
+
function output(line2) {
|
|
195
|
+
if (_cooking) {
|
|
196
|
+
_cooking.log(line2);
|
|
197
|
+
} else {
|
|
198
|
+
console.log(line2);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
var _cooking, T, log, info, warn, success;
|
|
202
|
+
var init_logger = __esm({
|
|
203
|
+
"src/logger/index.ts"() {
|
|
204
|
+
"use strict";
|
|
205
|
+
_cooking = null;
|
|
206
|
+
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
207
|
+
log = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.cyan("[LOG]")} ${m}`);
|
|
208
|
+
info = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.blue("[INFO]")} ${chalk3.bold(m)}`);
|
|
209
|
+
warn = (m) => {
|
|
210
|
+
const line2 = `${chalk3.gray(`[${T()}]`)} ${chalk3.yellow("[WARN]")} ${chalk3.yellow(m)}`;
|
|
211
|
+
if (_cooking) _cooking.log(line2);
|
|
212
|
+
else console.warn(line2);
|
|
213
|
+
};
|
|
214
|
+
success = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.green("[OK]")} ${chalk3.green(m)}`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// src/exporter/capture.ts
|
|
219
|
+
import puppeteer from "puppeteer";
|
|
220
|
+
async function launchAndCapture(exporter) {
|
|
221
|
+
exporter.cooking?.update("Launching browser...");
|
|
222
|
+
log("Launching headless Chromium...");
|
|
223
|
+
exporter.browser = await puppeteer.launch({
|
|
224
|
+
headless: true,
|
|
225
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
226
|
+
});
|
|
227
|
+
success("Chromium launched");
|
|
228
|
+
exporter.page = await exporter.browser.newPage();
|
|
229
|
+
await exporter.page.setViewport(CFG.viewport);
|
|
230
|
+
log("Viewport set to " + CFG.viewport.width + "x" + CFG.viewport.height);
|
|
231
|
+
await exporter.page.setUserAgent(
|
|
232
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
233
|
+
);
|
|
234
|
+
log("User agent set to Chrome 131");
|
|
235
|
+
const allStripDomains = [
|
|
236
|
+
...CFG.sharedStripDomains,
|
|
237
|
+
...exporter.platform.stripDomains
|
|
238
|
+
];
|
|
239
|
+
log("Blocking " + allStripDomains.length + " tracking domains:");
|
|
240
|
+
for (const domain of allStripDomains) {
|
|
241
|
+
log(" - " + domain);
|
|
242
|
+
}
|
|
243
|
+
let intercepted = 0;
|
|
244
|
+
let blocked = 0;
|
|
245
|
+
exporter.page.on("response", async (res) => {
|
|
246
|
+
const url = res.url();
|
|
247
|
+
if (url.startsWith("data:") || url.startsWith("blob:")) return;
|
|
248
|
+
try {
|
|
249
|
+
const host = new URL(url).hostname;
|
|
250
|
+
if (allStripDomains.some((d) => host.includes(d))) {
|
|
251
|
+
blocked++;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
exporter.assets.localPathFor(url, exporter.platform);
|
|
258
|
+
try {
|
|
259
|
+
exporter.assets.buffers.set(url, await res.buffer());
|
|
260
|
+
intercepted++;
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
log("Network interception enabled");
|
|
265
|
+
exporter.cooking?.update("Navigating to site...");
|
|
266
|
+
info("Navigating to " + exporter.siteUrl);
|
|
267
|
+
await exporter.page.goto(exporter.siteUrl, {
|
|
268
|
+
waitUntil: "networkidle2",
|
|
269
|
+
timeout: CFG.timeout
|
|
270
|
+
});
|
|
271
|
+
success("Page loaded (networkidle2)");
|
|
272
|
+
log("Intercepted " + intercepted + " resources, blocked " + blocked + " tracking requests");
|
|
273
|
+
exporter.cooking?.update("Waiting for " + exporter.platform.displayName + " hydration...");
|
|
274
|
+
if (exporter.platform.needsHydrationCheck) {
|
|
275
|
+
log("Checking for #main element hydration...");
|
|
276
|
+
log("Hydration timeout: " + exporter.platform.hydrationTimeout + "ms");
|
|
277
|
+
const timeout = exporter.platform.hydrationTimeout;
|
|
278
|
+
await exporter.page.evaluate(`
|
|
279
|
+
new Promise(function(r) {
|
|
280
|
+
var start = Date.now();
|
|
281
|
+
var tick = function() {
|
|
282
|
+
var m = document.getElementById('main') || document.body;
|
|
283
|
+
if (m && m.children.length > 0) setTimeout(r, 2000);
|
|
284
|
+
else if (Date.now() - start > ${timeout}) r();
|
|
285
|
+
else setTimeout(tick, 200);
|
|
286
|
+
};
|
|
287
|
+
tick();
|
|
288
|
+
})
|
|
289
|
+
`);
|
|
290
|
+
success("Hydration complete (SPA rendered)");
|
|
291
|
+
} else {
|
|
292
|
+
log("Static site detected, waiting " + exporter.platform.hydrationTimeout + "ms for render...");
|
|
293
|
+
await new Promise((r) => setTimeout(r, exporter.platform.hydrationTimeout));
|
|
294
|
+
success(exporter.platform.displayName + " page rendered");
|
|
295
|
+
}
|
|
296
|
+
exporter.cooking?.update("Scrolling page...");
|
|
297
|
+
log("Starting full-page scroll (step: " + CFG.scrollStep + "px, delay: " + CFG.scrollDelay + "ms)");
|
|
298
|
+
const scrollStep = CFG.scrollStep;
|
|
299
|
+
const scrollDelay = CFG.scrollDelay;
|
|
300
|
+
const pageHeight = await exporter.page.evaluate(`
|
|
301
|
+
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
302
|
+
`);
|
|
303
|
+
log("Page height: " + pageHeight + "px (" + Math.ceil(pageHeight / scrollStep) + " scroll steps)");
|
|
304
|
+
await exporter.page.evaluate(`
|
|
305
|
+
new Promise(function(r) {
|
|
306
|
+
var y = 0;
|
|
307
|
+
var max = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
308
|
+
var step = function() {
|
|
309
|
+
y += ${scrollStep};
|
|
310
|
+
window.scrollTo({ top: y, behavior: 'instant' });
|
|
311
|
+
y < max + 500 ? setTimeout(step, ${scrollDelay}) : (window.scrollTo(0, 0), r());
|
|
312
|
+
};
|
|
313
|
+
step();
|
|
314
|
+
})
|
|
315
|
+
`);
|
|
316
|
+
success("Full-page scroll complete");
|
|
317
|
+
exporter.cooking?.update("Waiting for lazy resources...");
|
|
318
|
+
log("Waiting 2s for lazy-loaded resources...");
|
|
319
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
320
|
+
log("Checking network idle (1.5s quiet, 8s timeout)...");
|
|
321
|
+
try {
|
|
322
|
+
await exporter.page.waitForNetworkIdle({ idleTime: 1500, timeout: 8e3 });
|
|
323
|
+
success("Network idle confirmed");
|
|
324
|
+
} catch {
|
|
325
|
+
log("Network idle timeout reached (continuing anyway)");
|
|
326
|
+
}
|
|
327
|
+
const totalCaptured = exporter.assets.buffers.size;
|
|
328
|
+
success("Captured " + totalCaptured + " network resources total");
|
|
329
|
+
const cssCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.endsWith(".css")).length;
|
|
330
|
+
const jsCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.endsWith(".js") || e.localPath.endsWith(".mjs")).length;
|
|
331
|
+
const imgCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/images")).length;
|
|
332
|
+
const fontCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/fonts")).length;
|
|
333
|
+
log(" CSS: " + cssCount + " | JS: " + jsCount + " | Images: " + imgCount + " | Fonts: " + fontCount);
|
|
334
|
+
await exporter.browser.close();
|
|
335
|
+
exporter.browser = null;
|
|
336
|
+
log("Browser closed");
|
|
337
|
+
}
|
|
338
|
+
var init_capture = __esm({
|
|
339
|
+
"src/exporter/capture.ts"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
init_config();
|
|
342
|
+
init_logger();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// src/network/pool.ts
|
|
347
|
+
async function pool(tasks, n) {
|
|
348
|
+
let i = 0;
|
|
349
|
+
const run = async () => {
|
|
350
|
+
while (i < tasks.length) {
|
|
351
|
+
const j = i++;
|
|
352
|
+
await tasks[j]();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
await Promise.all(Array.from({ length: Math.min(n, tasks.length) }, () => run()));
|
|
356
|
+
}
|
|
357
|
+
var init_pool = __esm({
|
|
358
|
+
"src/network/pool.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// src/exporter/download.ts
|
|
364
|
+
import fs from "fs/promises";
|
|
365
|
+
import path2 from "path";
|
|
366
|
+
async function downloadAll(exporter) {
|
|
367
|
+
const seen = /* @__PURE__ */ new Set();
|
|
368
|
+
const toDownload = [];
|
|
369
|
+
for (const [url, { localPath }] of exporter.assets.entries) {
|
|
370
|
+
if (seen.has(localPath)) continue;
|
|
371
|
+
seen.add(localPath);
|
|
372
|
+
toDownload.push({ url, localPath });
|
|
373
|
+
}
|
|
374
|
+
const total = toDownload.length;
|
|
375
|
+
log("Starting download of " + total + " unique assets");
|
|
376
|
+
log("Concurrency: " + CFG.concurrency + " parallel downloads");
|
|
377
|
+
log("Retry policy: " + CFG.retries + " attempts, " + CFG.dlTimeout + "ms timeout");
|
|
378
|
+
let ok = 0;
|
|
379
|
+
let cached = 0;
|
|
380
|
+
let fail = 0;
|
|
381
|
+
let completed = 0;
|
|
382
|
+
let lastReported = 0;
|
|
383
|
+
const tasks = toDownload.map(
|
|
384
|
+
({ url, localPath }) => async () => {
|
|
385
|
+
const dest = path2.join(exporter.outDir, localPath);
|
|
386
|
+
await fs.mkdir(path2.dirname(dest), { recursive: true });
|
|
387
|
+
try {
|
|
388
|
+
const buf = exporter.assets.buffers.get(url) || exporter.assets.buffers.get(url.split("?")[0]);
|
|
389
|
+
if (buf) {
|
|
390
|
+
await fs.writeFile(dest, buf);
|
|
391
|
+
cached++;
|
|
392
|
+
ok++;
|
|
393
|
+
} else {
|
|
394
|
+
const data = await dlBuffer(url);
|
|
395
|
+
await fs.writeFile(dest, data);
|
|
396
|
+
ok++;
|
|
397
|
+
}
|
|
398
|
+
} catch (e) {
|
|
399
|
+
fail++;
|
|
400
|
+
if (!url.includes("framer.com/edit") && !url.includes("framerstatic.com/editorbar")) {
|
|
401
|
+
warn("Download failed: " + path2.basename(localPath) + " - " + e.message);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
completed++;
|
|
405
|
+
const pct = Math.floor(completed / total * 100);
|
|
406
|
+
if (pct >= lastReported + 10 || completed === total) {
|
|
407
|
+
exporter.cooking?.update("Downloading... " + completed + "/" + total + " (" + pct + "%)");
|
|
408
|
+
log("Download progress: " + completed + "/" + total + " (" + pct + "%)");
|
|
409
|
+
lastReported = pct;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
await pool(tasks, CFG.concurrency);
|
|
414
|
+
success("Downloads complete: " + ok + " succeeded, " + cached + " from cache, " + fail + " failed");
|
|
415
|
+
const totalBytes = [...exporter.assets.entries.values()].length;
|
|
416
|
+
log("Total unique assets written to disk: " + totalBytes);
|
|
417
|
+
exporter.assets.buffers.clear();
|
|
418
|
+
log("Network buffer cache cleared");
|
|
419
|
+
}
|
|
420
|
+
var init_download2 = __esm({
|
|
421
|
+
"src/exporter/download.ts"() {
|
|
422
|
+
"use strict";
|
|
423
|
+
init_config();
|
|
424
|
+
init_logger();
|
|
425
|
+
init_download();
|
|
426
|
+
init_pool();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/formatter/prettify.ts
|
|
431
|
+
import prettier from "prettier";
|
|
432
|
+
async function prettifyJS(src) {
|
|
433
|
+
try {
|
|
434
|
+
return await prettier.format(src, {
|
|
435
|
+
parser: "babel",
|
|
436
|
+
printWidth: 100,
|
|
437
|
+
tabWidth: 2,
|
|
438
|
+
useTabs: false,
|
|
439
|
+
semi: true,
|
|
440
|
+
singleQuote: false,
|
|
441
|
+
trailingComma: "es5",
|
|
442
|
+
bracketSpacing: true,
|
|
443
|
+
arrowParens: "always"
|
|
444
|
+
});
|
|
445
|
+
} catch {
|
|
446
|
+
return src;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
var init_prettify = __esm({
|
|
450
|
+
"src/formatter/prettify.ts"() {
|
|
451
|
+
"use strict";
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// src/server/template.ts
|
|
456
|
+
var SERVE_SCRIPT;
|
|
457
|
+
var init_template = __esm({
|
|
458
|
+
"src/server/template.ts"() {
|
|
459
|
+
"use strict";
|
|
460
|
+
SERVE_SCRIPT = `#!/usr/bin/env node
|
|
461
|
+
const http = require('http');
|
|
462
|
+
const fs = require('fs');
|
|
463
|
+
const path = require('path');
|
|
464
|
+
|
|
465
|
+
const PORT = process.env.PORT || 3000;
|
|
466
|
+
const ROOT = __dirname;
|
|
467
|
+
|
|
468
|
+
const MIME = {
|
|
469
|
+
'.html': 'text/html; charset=utf-8',
|
|
470
|
+
'.css': 'text/css; charset=utf-8',
|
|
471
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
472
|
+
'.mjs': 'application/javascript; charset=utf-8',
|
|
473
|
+
'.json': 'application/json; charset=utf-8',
|
|
474
|
+
'.png': 'image/png',
|
|
475
|
+
'.jpg': 'image/jpeg',
|
|
476
|
+
'.jpeg': 'image/jpeg',
|
|
477
|
+
'.gif': 'image/gif',
|
|
478
|
+
'.svg': 'image/svg+xml',
|
|
479
|
+
'.webp': 'image/webp',
|
|
480
|
+
'.avif': 'image/avif',
|
|
481
|
+
'.ico': 'image/x-icon',
|
|
482
|
+
'.woff': 'font/woff',
|
|
483
|
+
'.woff2': 'font/woff2',
|
|
484
|
+
'.ttf': 'font/ttf',
|
|
485
|
+
'.otf': 'font/otf',
|
|
486
|
+
'.mp4': 'video/mp4',
|
|
487
|
+
'.webm': 'video/webm',
|
|
488
|
+
'.ogg': 'video/ogg',
|
|
489
|
+
'.framercms': 'application/octet-stream',
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const server = http.createServer((req, res) => {
|
|
493
|
+
let url = decodeURIComponent(req.url.split('?')[0]);
|
|
494
|
+
if (url === '/') url = '/index.html';
|
|
495
|
+
|
|
496
|
+
let filePath = path.join(ROOT, url);
|
|
497
|
+
if (!filePath.startsWith(ROOT)) {
|
|
498
|
+
res.writeHead(403);
|
|
499
|
+
res.end('Forbidden');
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const serveFile = (pathToFile) => {
|
|
504
|
+
fs.readFile(pathToFile, (err, data) => {
|
|
505
|
+
if (err) {
|
|
506
|
+
// SPA Fallback: if it's not a file, serve index.html
|
|
507
|
+
if (url !== '/index.html' && !path.extname(url)) {
|
|
508
|
+
return serveFile(path.join(ROOT, 'index.html'));
|
|
509
|
+
}
|
|
510
|
+
res.writeHead(404);
|
|
511
|
+
res.end('Not found');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const ext = path.extname(pathToFile).toLowerCase();
|
|
515
|
+
const mime = MIME[ext] || 'application/octet-stream';
|
|
516
|
+
res.writeHead(200, {
|
|
517
|
+
'Content-Type': mime,
|
|
518
|
+
'Access-Control-Allow-Origin': '*',
|
|
519
|
+
'Cache-Control': 'public, max-age=3600',
|
|
520
|
+
});
|
|
521
|
+
res.end(data);
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
serveFile(filePath);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
server.listen(PORT, () => {
|
|
529
|
+
console.log('\\x1b[32m%s\\x1b[0m', ' \u{1F680} Cooksite Local Server Running');
|
|
530
|
+
console.log('\\x1b[36m%s\\x1b[0m', ' \u251C\u2500 URL: http://localhost:' + PORT);
|
|
531
|
+
console.log('\\x1b[35m%s\\x1b[0m', ' \u2514\u2500 Directory: ' + ROOT);
|
|
532
|
+
console.log('\\n Press Ctrl+C to stop.');
|
|
533
|
+
});
|
|
534
|
+
`;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// src/exporter/output.ts
|
|
539
|
+
import fs2 from "fs/promises";
|
|
540
|
+
import path3 from "path";
|
|
541
|
+
function escapeRegex(str) {
|
|
542
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
543
|
+
}
|
|
544
|
+
function stripBySelector(html, sel) {
|
|
545
|
+
if (sel.includes('[src*="')) {
|
|
546
|
+
const match = sel.match(/\[src\*="([^"]+)"\]/);
|
|
547
|
+
if (match) {
|
|
548
|
+
const domain = escapeRegex(match[1]);
|
|
549
|
+
html = html.replace(new RegExp(`<script[^>]*${domain}[^>]*>[^<]*<\\/script>`, "g"), "");
|
|
550
|
+
html = html.replace(new RegExp(`<script[^>]*${domain}[^>]*><\\/script>`, "g"), "");
|
|
551
|
+
}
|
|
552
|
+
} else if (sel.includes('[href*="')) {
|
|
553
|
+
const match = sel.match(/\[href\*="([^"]+)"\]/);
|
|
554
|
+
if (match) {
|
|
555
|
+
const href = escapeRegex(match[1]);
|
|
556
|
+
html = html.replace(new RegExp(`<link[^>]*${href}[^>]*>`, "g"), "");
|
|
557
|
+
}
|
|
558
|
+
} else if (sel.startsWith(".")) {
|
|
559
|
+
const cls = escapeRegex(sel.slice(1));
|
|
560
|
+
html = html.replace(
|
|
561
|
+
new RegExp(`<[^>]*class="[^"]*${cls}[^"]*"[^>]*>[\\s\\S]*?<\\/[^>]*>`, "g"),
|
|
562
|
+
""
|
|
563
|
+
);
|
|
564
|
+
} else if (sel.startsWith("#")) {
|
|
565
|
+
const id = sel.slice(1);
|
|
566
|
+
html = removeElementById(html, id);
|
|
567
|
+
}
|
|
568
|
+
return html;
|
|
569
|
+
}
|
|
570
|
+
function removeElementById(html, id) {
|
|
571
|
+
const marker = `id="${id}"`;
|
|
572
|
+
let idx = html.indexOf(marker);
|
|
573
|
+
while (idx !== -1) {
|
|
574
|
+
const tagStart = html.lastIndexOf("<", idx);
|
|
575
|
+
if (tagStart === -1) break;
|
|
576
|
+
const tagNameEnd = html.indexOf(" ", tagStart + 1);
|
|
577
|
+
const tagName = html.slice(tagStart + 1, tagNameEnd).toLowerCase();
|
|
578
|
+
let depth = 0;
|
|
579
|
+
let i = tagStart;
|
|
580
|
+
while (i < html.length) {
|
|
581
|
+
if (html.startsWith(`<${tagName}`, i) && (html[i + tagName.length + 1] === " " || html[i + tagName.length + 1] === ">")) {
|
|
582
|
+
depth++;
|
|
583
|
+
i += tagName.length + 1;
|
|
584
|
+
} else if (html.startsWith(`</${tagName}>`, i)) {
|
|
585
|
+
depth--;
|
|
586
|
+
if (depth === 0) {
|
|
587
|
+
html = html.slice(0, tagStart) + html.slice(i + tagName.length + 3);
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
i += tagName.length + 3;
|
|
591
|
+
} else {
|
|
592
|
+
i++;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
idx = html.indexOf(marker);
|
|
596
|
+
}
|
|
597
|
+
return html;
|
|
598
|
+
}
|
|
599
|
+
function processSEO(html, url) {
|
|
600
|
+
const canonical = url.split("?")[0].replace(/\/$/, "");
|
|
601
|
+
if (!html.includes('rel="canonical"')) {
|
|
602
|
+
html = html.replace("</head>", ` <link rel="canonical" href="${canonical}">
|
|
603
|
+
</head>`);
|
|
604
|
+
}
|
|
605
|
+
if (!html.includes('name="description"')) {
|
|
606
|
+
html = html.replace("</head>", ` <meta name="description" content="Exported with Cooksite - Fast, SEO-optimized, and clean.">
|
|
607
|
+
</head>`);
|
|
608
|
+
}
|
|
609
|
+
if (!html.includes('property="og:')) {
|
|
610
|
+
html = html.replace("</head>", ` <meta property="og:type" content="website">
|
|
611
|
+
<meta property="og:url" content="${canonical}">
|
|
612
|
+
<meta property="og:title" content="Exported Site">
|
|
613
|
+
<meta property="og:description" content="A fast, clean version of this site, exported for performance.">
|
|
614
|
+
</head>`);
|
|
615
|
+
}
|
|
616
|
+
if (!html.includes('name="robots"')) {
|
|
617
|
+
html = html.replace("</head>", ` <meta name="robots" content="index, follow">
|
|
618
|
+
</head>`);
|
|
619
|
+
}
|
|
620
|
+
return html;
|
|
621
|
+
}
|
|
622
|
+
function stripIntegrityAndCors(html) {
|
|
623
|
+
html = html.replace(/\s+integrity="[^"]*"/g, "");
|
|
624
|
+
html = html.replace(/\s+crossorigin="[^"]*"/g, "");
|
|
625
|
+
html = html.replace(/\s+crossorigin/g, "");
|
|
626
|
+
html = html.replace(/<link[^>]*rel="preconnect"[^>]*>/g, "");
|
|
627
|
+
html = html.replace(/<link[^>]*rel="dns-prefetch"[^>]*>/g, "");
|
|
628
|
+
html = html.replace(/<meta[^>]*content-security-policy[^>]*>/gi, "");
|
|
629
|
+
return html;
|
|
630
|
+
}
|
|
631
|
+
function stripSrcsetCdnUrls(html) {
|
|
632
|
+
html = html.replace(/srcset="([^"]*)"/g, (_match, srcset) => {
|
|
633
|
+
const cleaned = srcset.split(",").map((entry) => entry.trim()).filter((entry) => !entry.startsWith("http")).join(", ");
|
|
634
|
+
return cleaned ? 'srcset="' + cleaned + '"' : "";
|
|
635
|
+
});
|
|
636
|
+
return html;
|
|
637
|
+
}
|
|
638
|
+
async function buildOutput(exporter) {
|
|
639
|
+
exporter.cooking?.update("Stripping platform badges...");
|
|
640
|
+
log("Starting HTML post-processing...");
|
|
641
|
+
log("HTML size: " + (exporter.ssrHTML.length / 1024).toFixed(1) + " KB");
|
|
642
|
+
let html = exporter.ssrHTML;
|
|
643
|
+
if (!html) {
|
|
644
|
+
warn("No SSR HTML available, cannot build output");
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
exporter.cooking?.update("Removing integrity checks...");
|
|
648
|
+
const beforeIntegrity = html.length;
|
|
649
|
+
html = stripIntegrityAndCors(html);
|
|
650
|
+
log("Stripped integrity/crossorigin/preconnect (" + (beforeIntegrity - html.length) + " chars)");
|
|
651
|
+
success("Integrity and CORS restrictions removed");
|
|
652
|
+
if (typeof processSEO === "function") {
|
|
653
|
+
exporter.cooking?.update("Optimizing SEO...");
|
|
654
|
+
html = processSEO(html, exporter.siteUrl);
|
|
655
|
+
success("SEO meta tags optimized");
|
|
656
|
+
}
|
|
657
|
+
log("Stripping " + exporter.platform.stripSelectors.length + " selectors:");
|
|
658
|
+
for (const sel of exporter.platform.stripSelectors) {
|
|
659
|
+
const before = html.length;
|
|
660
|
+
html = stripBySelector(html, sel);
|
|
661
|
+
const removed = before - html.length;
|
|
662
|
+
if (removed > 0) {
|
|
663
|
+
log(" Stripped " + sel + " (" + removed + " chars removed)");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
log("Applying " + exporter.platform.stripPatterns.length + " regex patterns...");
|
|
667
|
+
for (const pattern of exporter.platform.stripPatterns) {
|
|
668
|
+
const before = html.length;
|
|
669
|
+
html = html.replace(new RegExp(pattern.source, pattern.flags), "");
|
|
670
|
+
const removed = before - html.length;
|
|
671
|
+
if (removed > 0) {
|
|
672
|
+
log(" Pattern removed " + removed + " chars");
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
success("Platform badges and tracking stripped");
|
|
676
|
+
exporter.cooking?.update("Rewriting asset URLs...");
|
|
677
|
+
log("Rewriting " + exporter.assets.entries.size + " CDN URLs to local paths...");
|
|
678
|
+
const beforeRewrite = html.length;
|
|
679
|
+
html = exporter.assets.rewrite(html, "");
|
|
680
|
+
log("HTML rewrite delta: " + (html.length - beforeRewrite) + " chars");
|
|
681
|
+
exporter.cooking?.update("Cleaning srcset references...");
|
|
682
|
+
html = stripSrcsetCdnUrls(html);
|
|
683
|
+
log("Cleaned remaining CDN URLs from srcset attributes");
|
|
684
|
+
await rewriteDownloadedFiles(exporter);
|
|
685
|
+
success("All URLs rewritten to local paths");
|
|
686
|
+
exporter.cooking?.update("Pretty-printing JS files...");
|
|
687
|
+
await prettifyDownloadedJS(exporter);
|
|
688
|
+
exporter.cooking?.update("Writing final output...");
|
|
689
|
+
log("Writing index.html (" + (html.length / 1024).toFixed(1) + " KB)...");
|
|
690
|
+
await fs2.writeFile(path3.join(exporter.outDir, "index.html"), html);
|
|
691
|
+
success("index.html written");
|
|
692
|
+
await fs2.writeFile(path3.join(exporter.outDir, "serve.cjs"), SERVE_SCRIPT);
|
|
693
|
+
log("serve.cjs written");
|
|
694
|
+
success("Output build complete");
|
|
695
|
+
}
|
|
696
|
+
async function rewriteDownloadedFiles(exporter) {
|
|
697
|
+
const dirs = ["scripts/vendor", "scripts/modules", "styles"];
|
|
698
|
+
let rewritten = 0;
|
|
699
|
+
for (const dir of dirs) {
|
|
700
|
+
const fullDir = path3.join(exporter.outDir, dir);
|
|
701
|
+
let files;
|
|
702
|
+
try {
|
|
703
|
+
files = await fs2.readdir(fullDir);
|
|
704
|
+
} catch {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
for (const file of files) {
|
|
708
|
+
const ext = path3.extname(file).toLowerCase();
|
|
709
|
+
if (![".mjs", ".js", ".css"].includes(ext)) continue;
|
|
710
|
+
const filePath = path3.join(fullDir, file);
|
|
711
|
+
try {
|
|
712
|
+
let content = await fs2.readFile(filePath, "utf-8");
|
|
713
|
+
const before = content;
|
|
714
|
+
content = exporter.assets.rewrite(content, dir);
|
|
715
|
+
if (content !== before) {
|
|
716
|
+
await fs2.writeFile(filePath, content);
|
|
717
|
+
rewritten++;
|
|
718
|
+
}
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
log("Rewrote URLs in " + rewritten + " JS/CSS files");
|
|
724
|
+
}
|
|
725
|
+
async function prettifyDownloadedJS(exporter) {
|
|
726
|
+
const dirs = ["scripts/vendor", "scripts/modules"];
|
|
727
|
+
let count = 0;
|
|
728
|
+
let total = 0;
|
|
729
|
+
for (const dir of dirs) {
|
|
730
|
+
const fullDir = path3.join(exporter.outDir, dir);
|
|
731
|
+
let files;
|
|
732
|
+
try {
|
|
733
|
+
files = await fs2.readdir(fullDir);
|
|
734
|
+
} catch {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const jsFiles = files.filter((f) => {
|
|
738
|
+
const ext = path3.extname(f).toLowerCase();
|
|
739
|
+
return ext === ".mjs" || ext === ".js";
|
|
740
|
+
});
|
|
741
|
+
total += jsFiles.length;
|
|
742
|
+
for (const file of jsFiles) {
|
|
743
|
+
const filePath = path3.join(fullDir, file);
|
|
744
|
+
try {
|
|
745
|
+
const raw = await fs2.readFile(filePath, "utf-8");
|
|
746
|
+
const nlRatio = (raw.match(/\n/g) || []).length / raw.length;
|
|
747
|
+
if (nlRatio > 0.05) {
|
|
748
|
+
count++;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const pretty = await prettifyJS(raw);
|
|
752
|
+
await fs2.writeFile(filePath, pretty, "utf-8");
|
|
753
|
+
count++;
|
|
754
|
+
if (count % 5 === 0) {
|
|
755
|
+
exporter.cooking?.update("Pretty-printing... (" + count + "/" + total + ")");
|
|
756
|
+
}
|
|
757
|
+
} catch (e) {
|
|
758
|
+
warn("Pretty-print skipped: " + file + " - " + e.message);
|
|
759
|
+
count++;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
success("Formatted " + count + "/" + total + " JS/MJS files");
|
|
764
|
+
}
|
|
765
|
+
var init_output = __esm({
|
|
766
|
+
"src/exporter/output.ts"() {
|
|
767
|
+
"use strict";
|
|
768
|
+
init_logger();
|
|
769
|
+
init_prettify();
|
|
770
|
+
init_template();
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// src/exporter/summary.ts
|
|
775
|
+
import fs3 from "fs/promises";
|
|
776
|
+
import path4 from "path";
|
|
777
|
+
import chalk4 from "chalk";
|
|
778
|
+
function getWidth2() {
|
|
779
|
+
return process.stdout.columns || 80;
|
|
780
|
+
}
|
|
781
|
+
async function printSummary(exporter) {
|
|
782
|
+
const width = getWidth2();
|
|
783
|
+
const isSmall = width < 50;
|
|
784
|
+
const count = async (d) => {
|
|
785
|
+
try {
|
|
786
|
+
return (await fs3.readdir(path4.join(exporter.outDir, d))).length;
|
|
787
|
+
} catch {
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
const [imgs, fonts, videos, misc, scripts, vendor, styles, data] = await Promise.all([
|
|
792
|
+
count("assets/images"),
|
|
793
|
+
count("assets/fonts"),
|
|
794
|
+
count("assets/videos"),
|
|
795
|
+
count("assets/misc"),
|
|
796
|
+
count("scripts/modules"),
|
|
797
|
+
count("scripts/vendor"),
|
|
798
|
+
count("styles"),
|
|
799
|
+
count("data")
|
|
800
|
+
]);
|
|
801
|
+
console.log("");
|
|
802
|
+
if (!isSmall) {
|
|
803
|
+
console.log(chalk4.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
804
|
+
console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" Export Summary ") + chalk4.magenta("\u2502"));
|
|
805
|
+
console.log(chalk4.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
806
|
+
} else {
|
|
807
|
+
console.log(chalk4.bold.white(" Export Summary:"));
|
|
808
|
+
}
|
|
809
|
+
const row = (label, count2, type) => {
|
|
810
|
+
if (count2 === 0) return;
|
|
811
|
+
const l = label.padEnd(18);
|
|
812
|
+
const c = String(count2).padStart(3);
|
|
813
|
+
if (isSmall) {
|
|
814
|
+
console.log(` ${chalk4.yellow(label)} ${chalk4.white(String(count2))} ${chalk4.gray(type)}`);
|
|
815
|
+
} else {
|
|
816
|
+
console.log(` ${chalk4.magenta("\u2502")} ${chalk4.yellow(l)} ${chalk4.white(c)} ${chalk4.gray(type.padEnd(20))} ${chalk4.magenta("\u2502")}`);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
row("styles/", styles, "CSS files");
|
|
820
|
+
row("scripts/vendor/", vendor, "JS modules");
|
|
821
|
+
row("scripts/modules/", scripts, "components");
|
|
822
|
+
row("assets/images/", imgs, "images");
|
|
823
|
+
row("assets/videos/", videos, "videos");
|
|
824
|
+
row("assets/fonts/", fonts, "fonts");
|
|
825
|
+
row("assets/misc/", misc, "misc files");
|
|
826
|
+
row("data/", data, "data files");
|
|
827
|
+
if (!isSmall) {
|
|
828
|
+
console.log(chalk4.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
829
|
+
}
|
|
830
|
+
console.log("");
|
|
831
|
+
const quotedDir = `"${exporter.outDir}"`;
|
|
832
|
+
if (!isSmall) {
|
|
833
|
+
console.log(chalk4.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
834
|
+
console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" To serve locally ") + chalk4.magenta("\u2502"));
|
|
835
|
+
console.log(chalk4.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
836
|
+
console.log(` ${chalk4.magenta("\u2502")} ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs").padEnd(44)} ${chalk4.magenta("\u2502")}`);
|
|
837
|
+
console.log(chalk4.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
838
|
+
} else {
|
|
839
|
+
console.log(chalk4.bold.white(" To serve locally:"));
|
|
840
|
+
console.log(` ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs")}`);
|
|
841
|
+
}
|
|
842
|
+
console.log("");
|
|
843
|
+
console.log(chalk4.gray(" NOTE: Must be served via HTTP for JS modules to work."));
|
|
844
|
+
console.log("");
|
|
845
|
+
}
|
|
846
|
+
var init_summary = __esm({
|
|
847
|
+
"src/exporter/summary.ts"() {
|
|
848
|
+
"use strict";
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// src/platforms/framer.ts
|
|
853
|
+
var FONT_EXTS, framer;
|
|
854
|
+
var init_framer = __esm({
|
|
855
|
+
"src/platforms/framer.ts"() {
|
|
856
|
+
"use strict";
|
|
857
|
+
FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
|
|
858
|
+
framer = {
|
|
859
|
+
name: "framer",
|
|
860
|
+
displayName: "Framer",
|
|
861
|
+
detectByUrl(url) {
|
|
862
|
+
return /\.framer\.(app|website)|framercanvas\.com/.test(url);
|
|
863
|
+
},
|
|
864
|
+
detectByHtml(html) {
|
|
865
|
+
return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
|
|
866
|
+
},
|
|
867
|
+
stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
|
|
868
|
+
stripSelectors: [
|
|
869
|
+
'script[src*="events.framer.com"]',
|
|
870
|
+
"#__framer-badge-container",
|
|
871
|
+
"#__framer-badge",
|
|
872
|
+
'link[href*="canvas-sandbox"]',
|
|
873
|
+
'script[src*="framer.com/bootstrap"]'
|
|
874
|
+
],
|
|
875
|
+
stripPatterns: [
|
|
876
|
+
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
877
|
+
/<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
878
|
+
],
|
|
879
|
+
hydrationTimeout: 1e4,
|
|
880
|
+
needsHydrationCheck: true,
|
|
881
|
+
mapAssetDir(host, pathname, ext) {
|
|
882
|
+
if (host.includes("framerusercontent.com")) {
|
|
883
|
+
if (pathname.startsWith("/images/")) return "assets/images";
|
|
884
|
+
if (pathname.startsWith("/assets/")) {
|
|
885
|
+
return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
|
|
886
|
+
}
|
|
887
|
+
if (pathname.startsWith("/sites/")) {
|
|
888
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
889
|
+
if (ext === ".json") return "data";
|
|
890
|
+
if (ext === ".css") return "styles";
|
|
891
|
+
if (ext === ".framercms") return "data";
|
|
892
|
+
return "assets/misc";
|
|
893
|
+
}
|
|
894
|
+
if (pathname.startsWith("/modules/")) {
|
|
895
|
+
return ext === ".framercms" ? "data" : "scripts/modules";
|
|
896
|
+
}
|
|
897
|
+
return "assets/misc";
|
|
898
|
+
}
|
|
899
|
+
if (host.includes("app.framerstatic.com")) {
|
|
900
|
+
if (ext === ".css") return "styles";
|
|
901
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
902
|
+
if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
|
|
903
|
+
if (ext === ".png" || ext === ".svg") return "assets/images";
|
|
904
|
+
return "assets/misc";
|
|
905
|
+
}
|
|
906
|
+
if (host.includes("framercanvas.com") || host.includes("framer.com")) {
|
|
907
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
908
|
+
if (ext === ".css") return "styles";
|
|
909
|
+
return "assets/misc";
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// src/platforms/webflow.ts
|
|
918
|
+
var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
|
|
919
|
+
var init_webflow = __esm({
|
|
920
|
+
"src/platforms/webflow.ts"() {
|
|
921
|
+
"use strict";
|
|
922
|
+
IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
|
|
923
|
+
FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
924
|
+
VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
|
|
925
|
+
webflow = {
|
|
926
|
+
name: "webflow",
|
|
927
|
+
displayName: "Webflow",
|
|
928
|
+
detectByUrl(url) {
|
|
929
|
+
return /\.webflow\.(io|com)/.test(url);
|
|
930
|
+
},
|
|
931
|
+
detectByHtml(html) {
|
|
932
|
+
return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
|
|
933
|
+
},
|
|
934
|
+
stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
|
|
935
|
+
stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
|
|
936
|
+
stripPatterns: [
|
|
937
|
+
/<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
938
|
+
/<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
939
|
+
/<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
|
|
940
|
+
/Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
|
|
941
|
+
/<!-- This site was created in Webflow\.[^>]*-->/g,
|
|
942
|
+
/<html([^>]*) data-wf-domain="[^"]*"/g,
|
|
943
|
+
/<html([^>]*) data-wf-page="[^"]*"/g,
|
|
944
|
+
/<html([^>]*) data-wf-site="[^"]*"/g,
|
|
945
|
+
/<html([^>]*) data-wf-status="[^"]*"/g,
|
|
946
|
+
/<meta[^>]*content="Webflow"[^>]*>/g
|
|
947
|
+
],
|
|
948
|
+
hydrationTimeout: 2e3,
|
|
949
|
+
needsHydrationCheck: false,
|
|
950
|
+
mapAssetDir(host, pathname, ext) {
|
|
951
|
+
if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
|
|
952
|
+
if (VIDEO_EXTS.includes(ext)) return "assets/videos";
|
|
953
|
+
if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
|
|
954
|
+
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
955
|
+
if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
|
|
956
|
+
if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
|
|
957
|
+
if (FONT_EXTS2.includes(ext)) return "assets/fonts";
|
|
958
|
+
return "assets/misc";
|
|
959
|
+
}
|
|
960
|
+
if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
|
|
961
|
+
if (ext === ".js") return "scripts/vendor";
|
|
962
|
+
if (ext === ".css") return "styles";
|
|
963
|
+
if (IMG_EXTS.includes(ext)) return "assets/images";
|
|
964
|
+
return "assets/misc";
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// src/platforms/wix.ts
|
|
973
|
+
var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
|
|
974
|
+
var init_wix = __esm({
|
|
975
|
+
"src/platforms/wix.ts"() {
|
|
976
|
+
"use strict";
|
|
977
|
+
IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
978
|
+
FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
979
|
+
VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
|
|
980
|
+
wix = {
|
|
981
|
+
name: "wix",
|
|
982
|
+
displayName: "Wix",
|
|
983
|
+
detectByUrl(url) {
|
|
984
|
+
return /\.wixsite\.com|\.wix\.com/.test(url);
|
|
985
|
+
},
|
|
986
|
+
detectByHtml(html) {
|
|
987
|
+
return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk");
|
|
988
|
+
},
|
|
989
|
+
stripDomains: [
|
|
990
|
+
"frog.wix.com",
|
|
991
|
+
"bi.wixapi.com",
|
|
992
|
+
"fed.wixcodescheduler.com",
|
|
993
|
+
"editor.wix.com",
|
|
994
|
+
"panorama.wixapps.net"
|
|
995
|
+
],
|
|
996
|
+
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
997
|
+
stripPatterns: [
|
|
998
|
+
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
999
|
+
],
|
|
1000
|
+
hydrationTimeout: 3e3,
|
|
1001
|
+
needsHydrationCheck: false,
|
|
1002
|
+
mapAssetDir(host, pathname, ext) {
|
|
1003
|
+
if (host === "video.wixstatic.com") {
|
|
1004
|
+
return "assets/videos";
|
|
1005
|
+
}
|
|
1006
|
+
if (host.includes("wixstatic.com")) {
|
|
1007
|
+
if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
|
|
1008
|
+
if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1009
|
+
if (ext === ".css") return "styles";
|
|
1010
|
+
if (ext === ".js") return "scripts/vendor";
|
|
1011
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1012
|
+
return "assets/misc";
|
|
1013
|
+
}
|
|
1014
|
+
if (host.includes("parastorage.com")) {
|
|
1015
|
+
if (ext === ".js") return "scripts/vendor";
|
|
1016
|
+
if (ext === ".css") return "styles";
|
|
1017
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1018
|
+
if (IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1019
|
+
return "assets/misc";
|
|
1020
|
+
}
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// src/platforms/detect.ts
|
|
1028
|
+
function detectByUrl(url) {
|
|
1029
|
+
for (const platform of ALL_PLATFORMS) {
|
|
1030
|
+
if (platform.detectByUrl(url)) return platform;
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
function detectByHtml(html) {
|
|
1035
|
+
for (const platform of ALL_PLATFORMS) {
|
|
1036
|
+
if (platform.detectByHtml(html)) return platform;
|
|
1037
|
+
}
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
function detectPlatform(url, html) {
|
|
1041
|
+
const byUrl = detectByUrl(url);
|
|
1042
|
+
if (byUrl) return byUrl;
|
|
1043
|
+
if (html) {
|
|
1044
|
+
const byHtml = detectByHtml(html);
|
|
1045
|
+
if (byHtml) return byHtml;
|
|
1046
|
+
}
|
|
1047
|
+
return framer;
|
|
1048
|
+
}
|
|
1049
|
+
function getPlatformByName(name) {
|
|
1050
|
+
const map = { framer, webflow, wix };
|
|
1051
|
+
return map[name] || framer;
|
|
1052
|
+
}
|
|
1053
|
+
var ALL_PLATFORMS;
|
|
1054
|
+
var init_detect = __esm({
|
|
1055
|
+
"src/platforms/detect.ts"() {
|
|
1056
|
+
"use strict";
|
|
1057
|
+
init_framer();
|
|
1058
|
+
init_webflow();
|
|
1059
|
+
init_wix();
|
|
1060
|
+
ALL_PLATFORMS = [framer, webflow, wix];
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
// src/platforms/index.ts
|
|
1065
|
+
var platforms_exports = {};
|
|
1066
|
+
__export(platforms_exports, {
|
|
1067
|
+
detectByHtml: () => detectByHtml,
|
|
1068
|
+
detectByUrl: () => detectByUrl,
|
|
1069
|
+
detectPlatform: () => detectPlatform,
|
|
1070
|
+
framer: () => framer,
|
|
1071
|
+
getPlatformByName: () => getPlatformByName,
|
|
1072
|
+
webflow: () => webflow,
|
|
1073
|
+
wix: () => wix
|
|
1074
|
+
});
|
|
1075
|
+
var init_platforms = __esm({
|
|
1076
|
+
"src/platforms/index.ts"() {
|
|
1077
|
+
"use strict";
|
|
1078
|
+
init_framer();
|
|
1079
|
+
init_webflow();
|
|
1080
|
+
init_wix();
|
|
1081
|
+
init_detect();
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
// src/cli/cooking.ts
|
|
1086
|
+
import chalk5 from "chalk";
|
|
1087
|
+
var SHINE_WIDTH, CookingSpinner;
|
|
1088
|
+
var init_cooking = __esm({
|
|
1089
|
+
"src/cli/cooking.ts"() {
|
|
1090
|
+
"use strict";
|
|
1091
|
+
SHINE_WIDTH = 8;
|
|
1092
|
+
CookingSpinner = class {
|
|
1093
|
+
interval = null;
|
|
1094
|
+
frame = 0;
|
|
1095
|
+
phase = "";
|
|
1096
|
+
active = false;
|
|
1097
|
+
start(phase = "") {
|
|
1098
|
+
this.phase = phase;
|
|
1099
|
+
this.frame = 0;
|
|
1100
|
+
this.active = true;
|
|
1101
|
+
this.draw();
|
|
1102
|
+
this.interval = setInterval(() => {
|
|
1103
|
+
this.frame++;
|
|
1104
|
+
this.draw();
|
|
1105
|
+
}, 70);
|
|
1106
|
+
}
|
|
1107
|
+
update(phase) {
|
|
1108
|
+
this.phase = phase;
|
|
1109
|
+
}
|
|
1110
|
+
log(message) {
|
|
1111
|
+
if (this.active) {
|
|
1112
|
+
process.stdout.write("\r\x1B[2K");
|
|
1113
|
+
}
|
|
1114
|
+
process.stdout.write(message + "\n");
|
|
1115
|
+
if (this.active) {
|
|
1116
|
+
this.draw();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
stop() {
|
|
1120
|
+
this.active = false;
|
|
1121
|
+
if (this.interval) {
|
|
1122
|
+
clearInterval(this.interval);
|
|
1123
|
+
this.interval = null;
|
|
1124
|
+
}
|
|
1125
|
+
process.stdout.write("\r\x1B[2K");
|
|
1126
|
+
}
|
|
1127
|
+
draw() {
|
|
1128
|
+
if (!this.active) return;
|
|
1129
|
+
const text = "Cooking";
|
|
1130
|
+
const dotCount = 1 + this.frame % 3;
|
|
1131
|
+
const dots = ".".repeat(dotCount);
|
|
1132
|
+
const pad = " ".repeat(3 - dotCount);
|
|
1133
|
+
const shimmer = this.renderShimmer(text);
|
|
1134
|
+
const dotStr = chalk5.rgb(180, 180, 180)(dots);
|
|
1135
|
+
const phaseText = this.phase ? ` ${chalk5.gray(this.phase)}` : "";
|
|
1136
|
+
process.stdout.write(`\r\x1B[2K ${shimmer}${dotStr}${pad}${phaseText}`);
|
|
1137
|
+
}
|
|
1138
|
+
renderShimmer(text) {
|
|
1139
|
+
let result = "";
|
|
1140
|
+
const pos = this.frame % (text.length + SHINE_WIDTH * 2) - SHINE_WIDTH;
|
|
1141
|
+
for (let i = 0; i < text.length; i++) {
|
|
1142
|
+
const dist = Math.abs(i - pos);
|
|
1143
|
+
if (dist < SHINE_WIDTH) {
|
|
1144
|
+
const t = 1 - dist / SHINE_WIDTH;
|
|
1145
|
+
const gray = Math.floor(180 + t * 75);
|
|
1146
|
+
result += chalk5.rgb(gray, gray, gray)(text[i]);
|
|
1147
|
+
} else {
|
|
1148
|
+
result += chalk5.rgb(180, 180, 180)(text[i]);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return result;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
// src/exporter/index.ts
|
|
1158
|
+
var exporter_exports = {};
|
|
1159
|
+
__export(exporter_exports, {
|
|
1160
|
+
FramerExporter: () => FramerExporter,
|
|
1161
|
+
deriveOutputName: () => deriveOutputName
|
|
1162
|
+
});
|
|
1163
|
+
import fs4 from "fs/promises";
|
|
1164
|
+
import path5 from "path";
|
|
1165
|
+
import chalk6 from "chalk";
|
|
1166
|
+
function deriveOutputName(url, platformName) {
|
|
1167
|
+
try {
|
|
1168
|
+
const parsed = new URL(url);
|
|
1169
|
+
const hostname = parsed.hostname;
|
|
1170
|
+
let siteName;
|
|
1171
|
+
if (hostname.endsWith(".webflow.io")) {
|
|
1172
|
+
siteName = hostname.replace(".webflow.io", "");
|
|
1173
|
+
} else if (hostname.includes(".wixsite.com")) {
|
|
1174
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
1175
|
+
siteName = parts[0] || hostname.split(".")[0];
|
|
1176
|
+
} else if (hostname.match(/\.framer\.(app|website|ai)$/)) {
|
|
1177
|
+
siteName = hostname.split(".framer.")[0];
|
|
1178
|
+
} else {
|
|
1179
|
+
siteName = hostname.replace(/\./g, "-");
|
|
1180
|
+
}
|
|
1181
|
+
return `${platformName}-${siteName}`;
|
|
1182
|
+
} catch {
|
|
1183
|
+
return "framer-export-output";
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
var FramerExporter;
|
|
1187
|
+
var init_exporter = __esm({
|
|
1188
|
+
"src/exporter/index.ts"() {
|
|
1189
|
+
"use strict";
|
|
1190
|
+
init_asset_map();
|
|
1191
|
+
init_download();
|
|
1192
|
+
init_logger();
|
|
1193
|
+
init_capture();
|
|
1194
|
+
init_download2();
|
|
1195
|
+
init_output();
|
|
1196
|
+
init_summary();
|
|
1197
|
+
init_platforms();
|
|
1198
|
+
init_cooking();
|
|
1199
|
+
FramerExporter = class {
|
|
1200
|
+
siteUrl;
|
|
1201
|
+
outDir;
|
|
1202
|
+
assets;
|
|
1203
|
+
browser;
|
|
1204
|
+
page;
|
|
1205
|
+
ssrHTML;
|
|
1206
|
+
prettyPrint;
|
|
1207
|
+
platform;
|
|
1208
|
+
cooking;
|
|
1209
|
+
constructor(siteUrl, outDir, platformOverride) {
|
|
1210
|
+
this.siteUrl = siteUrl;
|
|
1211
|
+
this.outDir = outDir;
|
|
1212
|
+
this.assets = new AssetMap();
|
|
1213
|
+
this.browser = null;
|
|
1214
|
+
this.page = null;
|
|
1215
|
+
this.ssrHTML = "";
|
|
1216
|
+
this.prettyPrint = true;
|
|
1217
|
+
if (platformOverride && platformOverride !== "unknown") {
|
|
1218
|
+
this.platform = getPlatformByName(platformOverride);
|
|
1219
|
+
} else {
|
|
1220
|
+
this.platform = detectPlatform(siteUrl);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async run() {
|
|
1224
|
+
console.log(chalk6.bold.magenta("\n Cooksite v4 - Full Mirror & Clean Export\n"));
|
|
1225
|
+
info("Source : " + chalk6.underline(this.siteUrl));
|
|
1226
|
+
info("Output : " + chalk6.yellow(this.outDir));
|
|
1227
|
+
info("Platform : " + chalk6.magenta(this.platform.displayName));
|
|
1228
|
+
console.log("");
|
|
1229
|
+
this.cooking = new CookingSpinner();
|
|
1230
|
+
setCooking(this.cooking);
|
|
1231
|
+
this.cooking.start("Preparing directories...");
|
|
1232
|
+
for (const d of [
|
|
1233
|
+
"",
|
|
1234
|
+
"assets/images",
|
|
1235
|
+
"assets/fonts",
|
|
1236
|
+
"assets/videos",
|
|
1237
|
+
"assets/misc",
|
|
1238
|
+
"styles",
|
|
1239
|
+
"scripts/vendor",
|
|
1240
|
+
"scripts/modules",
|
|
1241
|
+
"data"
|
|
1242
|
+
]) {
|
|
1243
|
+
await fs4.mkdir(path5.join(this.outDir, d), { recursive: true });
|
|
1244
|
+
}
|
|
1245
|
+
log("Output directory structure created");
|
|
1246
|
+
this.cooking.update("Fetching SSR HTML...");
|
|
1247
|
+
log("Fetching SSR HTML from " + this.siteUrl);
|
|
1248
|
+
try {
|
|
1249
|
+
const buf = await dlBuffer(this.siteUrl);
|
|
1250
|
+
this.ssrHTML = buf.toString("utf-8");
|
|
1251
|
+
success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
|
|
1252
|
+
} catch (e) {
|
|
1253
|
+
log(chalk6.red("Could not fetch SSR HTML: " + e.message));
|
|
1254
|
+
}
|
|
1255
|
+
const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
|
|
1256
|
+
if (htmlDetected.name !== this.platform.name) {
|
|
1257
|
+
this.platform = htmlDetected;
|
|
1258
|
+
log("Platform refined: " + chalk6.magenta(this.platform.displayName) + " (from HTML analysis)");
|
|
1259
|
+
}
|
|
1260
|
+
await launchAndCapture(this);
|
|
1261
|
+
this.cooking.update("Downloading assets...");
|
|
1262
|
+
await downloadAll(this);
|
|
1263
|
+
this.cooking.update("Building output...");
|
|
1264
|
+
await buildOutput(this);
|
|
1265
|
+
this.cooking.stop();
|
|
1266
|
+
setCooking(null);
|
|
1267
|
+
console.log("");
|
|
1268
|
+
success("Export complete!");
|
|
1269
|
+
await printSummary(this);
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// src/cli/select.ts
|
|
1276
|
+
import readline from "readline";
|
|
1277
|
+
import { stdin, stdout } from "process";
|
|
1278
|
+
import chalk7 from "chalk";
|
|
1279
|
+
async function select(question, options, defaultIndex = 0) {
|
|
1280
|
+
return new Promise((resolve) => {
|
|
1281
|
+
let selected = defaultIndex;
|
|
1282
|
+
const render = (initial = false) => {
|
|
1283
|
+
if (!initial) {
|
|
1284
|
+
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1285
|
+
stdout.write("\x1B[J");
|
|
1286
|
+
}
|
|
1287
|
+
console.log(` ${chalk7.cyan("?")} ${chalk7.white.bold(question)}`);
|
|
1288
|
+
for (let i = 0; i < options.length; i++) {
|
|
1289
|
+
if (i === selected) {
|
|
1290
|
+
console.log(` ${chalk7.cyan(">")} ${chalk7.white.bold(options[i].label)}`);
|
|
1291
|
+
} else {
|
|
1292
|
+
console.log(` ${chalk7.gray(options[i].label)}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
render(true);
|
|
1297
|
+
readline.emitKeypressEvents(stdin);
|
|
1298
|
+
const wasTTY = stdin.isTTY || false;
|
|
1299
|
+
if (wasTTY) stdin.setRawMode(true);
|
|
1300
|
+
const onKeypress = (_str, key) => {
|
|
1301
|
+
if (!key) return;
|
|
1302
|
+
if (key.name === "up" && selected > 0) {
|
|
1303
|
+
selected--;
|
|
1304
|
+
render();
|
|
1305
|
+
} else if (key.name === "down" && selected < options.length - 1) {
|
|
1306
|
+
selected++;
|
|
1307
|
+
render();
|
|
1308
|
+
} else if (key.name === "return") {
|
|
1309
|
+
if (wasTTY) stdin.setRawMode(false);
|
|
1310
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1311
|
+
stdin.pause();
|
|
1312
|
+
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1313
|
+
stdout.write("\x1B[J");
|
|
1314
|
+
console.log(` ${chalk7.green("\u2713")} ${chalk7.white.bold(question)} ${chalk7.cyan(options[selected].label)}
|
|
1315
|
+
`);
|
|
1316
|
+
resolve(options[selected].value);
|
|
1317
|
+
} else if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
1318
|
+
if (wasTTY) stdin.setRawMode(false);
|
|
1319
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1320
|
+
process.exit(0);
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
stdin.resume();
|
|
1324
|
+
stdin.on("keypress", onKeypress);
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
var init_select = __esm({
|
|
1328
|
+
"src/cli/select.ts"() {
|
|
1329
|
+
"use strict";
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// src/cli/setup.ts
|
|
1334
|
+
var setup_exports = {};
|
|
1335
|
+
__export(setup_exports, {
|
|
1336
|
+
runSetup: () => runSetup
|
|
1337
|
+
});
|
|
1338
|
+
import readline2 from "readline/promises";
|
|
1339
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1340
|
+
import path6 from "path";
|
|
1341
|
+
import { URL as URL4 } from "url";
|
|
1342
|
+
import chalk8 from "chalk";
|
|
1343
|
+
function getWidth3() {
|
|
1344
|
+
return process.stdout.columns || 80;
|
|
1345
|
+
}
|
|
1346
|
+
function drawHeader(title) {
|
|
1347
|
+
const width = getWidth3();
|
|
1348
|
+
const isSmall = width < 50;
|
|
1349
|
+
const magenta = chalk8.magenta;
|
|
1350
|
+
const boldWhite = chalk8.bold.white;
|
|
1351
|
+
if (isSmall) {
|
|
1352
|
+
console.log(`
|
|
1353
|
+
${magenta("\u25CF")} ${boldWhite(title)}`);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const boxWidth = 45;
|
|
1357
|
+
const padding = Math.max(0, boxWidth - title.length - 2);
|
|
1358
|
+
const rightPad = " ".repeat(padding);
|
|
1359
|
+
console.log(magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1360
|
+
console.log(magenta(" \u2502") + boldWhite(" " + title) + rightPad + magenta("\u2502"));
|
|
1361
|
+
console.log(magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1362
|
+
console.log("");
|
|
1363
|
+
}
|
|
1364
|
+
function line(label, value) {
|
|
1365
|
+
const width = getWidth3();
|
|
1366
|
+
const isSmall = width < 50;
|
|
1367
|
+
const magenta = chalk8.magenta;
|
|
1368
|
+
if (isSmall) {
|
|
1369
|
+
console.log(` ${chalk8.bold(label)}: ${chalk8.yellow(value)}`);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
console.log(` ${magenta("\u2502")} ${chalk8.bold(label.padEnd(14))} ${chalk8.yellow(value)}`);
|
|
1373
|
+
}
|
|
1374
|
+
async function runSetup(legacyMode = false) {
|
|
1375
|
+
showBanner();
|
|
1376
|
+
console.log(chalk8.bold.white(" Welcome to Framer Export setup.\n"));
|
|
1377
|
+
console.log(chalk8.gray(" Supports Framer, Webflow, and Wix sites.\n"));
|
|
1378
|
+
const rl = readline2.createInterface({ input: stdin2, output: stdout2 });
|
|
1379
|
+
const ask = async (question, defaultVal) => {
|
|
1380
|
+
const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
|
|
1381
|
+
const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
|
|
1382
|
+
const answer = await rl.question(prompt);
|
|
1383
|
+
return answer.trim() || defaultVal || "";
|
|
1384
|
+
};
|
|
1385
|
+
drawHeader("Step 1 : Site URL");
|
|
1386
|
+
let siteUrl = "";
|
|
1387
|
+
while (!siteUrl) {
|
|
1388
|
+
const input = await ask("Enter the site URL");
|
|
1389
|
+
try {
|
|
1390
|
+
new URL4(input);
|
|
1391
|
+
siteUrl = input;
|
|
1392
|
+
} catch {
|
|
1393
|
+
console.log(` ${chalk8.red("\u2717")} ${chalk8.red("Invalid URL. Enter a valid URL (https://...)")}
|
|
1394
|
+
`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green("URL:")} ${chalk8.underline(siteUrl)}
|
|
1398
|
+
`);
|
|
1399
|
+
drawHeader("Step 2 : Platform");
|
|
1400
|
+
const detected = detectPlatform(siteUrl);
|
|
1401
|
+
let platformName;
|
|
1402
|
+
if (legacyMode) {
|
|
1403
|
+
console.log(` ${chalk8.blue("i")} Auto-detected: ${chalk8.magenta(detected.displayName)}`);
|
|
1404
|
+
const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
|
|
1405
|
+
platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
|
|
1406
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Platform:")} ${chalk8.magenta(platformName)}
|
|
1407
|
+
`);
|
|
1408
|
+
} else {
|
|
1409
|
+
rl.close();
|
|
1410
|
+
const platforms = [
|
|
1411
|
+
{ label: `Framer${detected.name === "framer" ? chalk8.gray(" (detected)") : ""}`, value: "framer" },
|
|
1412
|
+
{ label: `Webflow${detected.name === "webflow" ? chalk8.gray(" (detected)") : ""}`, value: "webflow" },
|
|
1413
|
+
{ label: `Wix${detected.name === "wix" ? chalk8.gray(" (detected)") : ""}`, value: "wix" }
|
|
1414
|
+
];
|
|
1415
|
+
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
1416
|
+
platformName = await select("Select platform", platforms, Math.max(0, defaultIdx));
|
|
1417
|
+
}
|
|
1418
|
+
const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin2, output: stdout2 });
|
|
1419
|
+
const ask2 = async (question, defaultVal) => {
|
|
1420
|
+
const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
|
|
1421
|
+
const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
|
|
1422
|
+
const answer = await rl2.question(prompt);
|
|
1423
|
+
return answer.trim() || defaultVal || "";
|
|
1424
|
+
};
|
|
1425
|
+
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
1426
|
+
drawHeader("Step 3 : Output Directory");
|
|
1427
|
+
const outDir = await ask2("Output directory", "./" + derivedName);
|
|
1428
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Output:")} ${chalk8.yellow(outDir)}
|
|
1429
|
+
`);
|
|
1430
|
+
drawHeader("Step 4 : Options");
|
|
1431
|
+
let prettyPrint;
|
|
1432
|
+
let concurrency;
|
|
1433
|
+
if (legacyMode) {
|
|
1434
|
+
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
1435
|
+
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
1436
|
+
console.log(` ${chalk8.green("\u2713")} Pretty-print: ${prettyPrint ? chalk8.green("yes") : chalk8.red("no")}
|
|
1437
|
+
`);
|
|
1438
|
+
const concurrencyAnswer = await ask2("Download concurrency", "12");
|
|
1439
|
+
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
1440
|
+
console.log(` ${chalk8.green("\u2713")} Concurrency: ${chalk8.yellow(String(concurrency))}
|
|
1441
|
+
`);
|
|
1442
|
+
} else {
|
|
1443
|
+
rl2.close();
|
|
1444
|
+
const prettyVal = await select("Pretty-print JS files?", [
|
|
1445
|
+
{ label: "Yes", value: "yes" },
|
|
1446
|
+
{ label: "No", value: "no" }
|
|
1447
|
+
]);
|
|
1448
|
+
prettyPrint = prettyVal === "yes";
|
|
1449
|
+
const concurrencyVal = await select("Download concurrency", [
|
|
1450
|
+
{ label: "6 (slow connection)", value: "6" },
|
|
1451
|
+
{ label: "12 (default)", value: "12" },
|
|
1452
|
+
{ label: "20 (fast connection)", value: "20" }
|
|
1453
|
+
], 1);
|
|
1454
|
+
concurrency = parseInt(concurrencyVal, 10);
|
|
1455
|
+
}
|
|
1456
|
+
console.log("");
|
|
1457
|
+
const width = getWidth3();
|
|
1458
|
+
const isSmall = width < 50;
|
|
1459
|
+
if (!isSmall) {
|
|
1460
|
+
console.log(chalk8.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1461
|
+
console.log(chalk8.magenta(" \u2502") + chalk8.bold.white(" Summary ") + chalk8.magenta("\u2502"));
|
|
1462
|
+
console.log(chalk8.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1463
|
+
} else {
|
|
1464
|
+
console.log(chalk8.bold.white(" Summary:"));
|
|
1465
|
+
}
|
|
1466
|
+
line("URL", siteUrl);
|
|
1467
|
+
line("Platform", platformName);
|
|
1468
|
+
line("Output", path6.resolve(outDir));
|
|
1469
|
+
line("Pretty-print", prettyPrint ? "yes" : "no");
|
|
1470
|
+
line("Concurrency", String(concurrency));
|
|
1471
|
+
if (!isSmall) {
|
|
1472
|
+
console.log(chalk8.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1473
|
+
}
|
|
1474
|
+
console.log("");
|
|
1475
|
+
let startExport;
|
|
1476
|
+
if (legacyMode) {
|
|
1477
|
+
const confirm = await ask2("Start export? (y/n)", "y");
|
|
1478
|
+
startExport = confirm.toLowerCase().startsWith("y");
|
|
1479
|
+
rl2.close();
|
|
1480
|
+
} else {
|
|
1481
|
+
const confirmVal = await select("Start export?", [
|
|
1482
|
+
{ label: "Yes, start now", value: "yes" },
|
|
1483
|
+
{ label: "Cancel", value: "no" }
|
|
1484
|
+
]);
|
|
1485
|
+
startExport = confirmVal === "yes";
|
|
1486
|
+
}
|
|
1487
|
+
if (!startExport) {
|
|
1488
|
+
console.log(`
|
|
1489
|
+
${chalk8.yellow("Export cancelled.")}
|
|
1490
|
+
`);
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
console.log("");
|
|
1494
|
+
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1495
|
+
CFG2.concurrency = concurrency;
|
|
1496
|
+
const exporter = new FramerExporter(siteUrl, path6.resolve(outDir), platformName);
|
|
1497
|
+
exporter.prettyPrint = prettyPrint;
|
|
1498
|
+
await exporter.run();
|
|
1499
|
+
}
|
|
1500
|
+
var init_setup = __esm({
|
|
1501
|
+
"src/cli/setup.ts"() {
|
|
1502
|
+
"use strict";
|
|
1503
|
+
init_banner();
|
|
1504
|
+
init_exporter();
|
|
1505
|
+
init_platforms();
|
|
1506
|
+
init_select();
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
// src/cli/index.ts
|
|
1511
|
+
import path7 from "path";
|
|
1512
|
+
import { URL as URL5 } from "url";
|
|
1513
|
+
|
|
1514
|
+
// src/cli/help.ts
|
|
1515
|
+
init_banner();
|
|
1516
|
+
import chalk2 from "chalk";
|
|
1517
|
+
function showHelp() {
|
|
1518
|
+
showBanner();
|
|
1519
|
+
console.log(chalk2.white.bold(" USAGE\n"));
|
|
1520
|
+
console.log(` ${chalk2.cyan("npm run dev")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
|
|
1521
|
+
console.log(` ${chalk2.cyan("npm start")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
|
|
1522
|
+
console.log(` ${chalk2.cyan("framer-export")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
|
|
1523
|
+
console.log("");
|
|
1524
|
+
console.log(chalk2.white.bold(" OPTIONS\n"));
|
|
1525
|
+
const opts = [
|
|
1526
|
+
["--setup", "Launch the interactive setup assistant"],
|
|
1527
|
+
["--platform <p>", "Force platform: framer, webflow, wix"],
|
|
1528
|
+
["--legacy-mode", "Use text input instead of arrow selection"],
|
|
1529
|
+
["--help, -h", "Show this help message"]
|
|
1530
|
+
];
|
|
1531
|
+
for (const [flag, desc] of opts) {
|
|
1532
|
+
console.log(` ${chalk2.green(flag.padEnd(18))} ${chalk2.white(desc)}`);
|
|
1533
|
+
}
|
|
1534
|
+
console.log("");
|
|
1535
|
+
console.log(chalk2.white.bold(" SUPPORTED PLATFORMS\n"));
|
|
1536
|
+
const platforms = [
|
|
1537
|
+
["Framer", "Auto-detected via .framer.app / .framer.ai URLs"],
|
|
1538
|
+
["Webflow", "Auto-detected via .webflow.io URLs"],
|
|
1539
|
+
["Wix", "Auto-detected via .wixsite.com URLs"]
|
|
1540
|
+
];
|
|
1541
|
+
for (const [name, desc] of platforms) {
|
|
1542
|
+
console.log(` ${chalk2.magenta(name.padEnd(12))} ${chalk2.gray(desc)}`);
|
|
1543
|
+
}
|
|
1544
|
+
console.log("");
|
|
1545
|
+
console.log(chalk2.white.bold(" EXAMPLES\n"));
|
|
1546
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.framer.app")}`);
|
|
1547
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.webflow.io")}`);
|
|
1548
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://user.wixsite.com/my-site")}`);
|
|
1549
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--platform webflow")} ${chalk2.yellow("https://custom.com")}`);
|
|
1550
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--setup")}`);
|
|
1551
|
+
console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--setup --legacy-mode")}`);
|
|
1552
|
+
console.log("");
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/cli/index.ts
|
|
1556
|
+
init_banner();
|
|
1557
|
+
function extractFlag(args, flag) {
|
|
1558
|
+
const idx = args.indexOf(flag);
|
|
1559
|
+
if (idx === -1 || idx + 1 >= args.length) return null;
|
|
1560
|
+
const value = args[idx + 1];
|
|
1561
|
+
args.splice(idx, 2);
|
|
1562
|
+
return value;
|
|
1563
|
+
}
|
|
1564
|
+
function hasFlag(args, flag) {
|
|
1565
|
+
const idx = args.indexOf(flag);
|
|
1566
|
+
if (idx === -1) return false;
|
|
1567
|
+
args.splice(idx, 1);
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
async function main() {
|
|
1571
|
+
const args = process.argv.slice(2);
|
|
1572
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1573
|
+
showHelp();
|
|
1574
|
+
process.exit(0);
|
|
1575
|
+
}
|
|
1576
|
+
if (args.includes("--setup")) {
|
|
1577
|
+
hasFlag(args, "--setup");
|
|
1578
|
+
const legacyMode = hasFlag(args, "--legacy-mode");
|
|
1579
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
1580
|
+
await runSetup2(legacyMode);
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (!args.length) {
|
|
1584
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
1585
|
+
await runSetup2(false);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const platformOverride = extractFlag(args, "--platform");
|
|
1589
|
+
showBanner();
|
|
1590
|
+
const url = args[0];
|
|
1591
|
+
try {
|
|
1592
|
+
new URL5(url);
|
|
1593
|
+
} catch {
|
|
1594
|
+
const chalk9 = (await import("chalk")).default;
|
|
1595
|
+
console.log(` ${chalk9.red("\u2717")} ${chalk9.red.bold("Invalid URL:")} ${chalk9.white(url)}`);
|
|
1596
|
+
console.log(` ${chalk9.gray("Expected: https://yoursite.framer.app")}
|
|
1597
|
+
`);
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
const { FramerExporter: FramerExporter2, deriveOutputName: deriveOutputName2 } = await Promise.resolve().then(() => (init_exporter(), exporter_exports));
|
|
1601
|
+
const { detectPlatform: detectPlatform2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
1602
|
+
const detected = platformOverride || detectPlatform2(url).name;
|
|
1603
|
+
const defaultDir = deriveOutputName2(url, detected);
|
|
1604
|
+
const out = args[1] || `./${defaultDir}`;
|
|
1605
|
+
try {
|
|
1606
|
+
await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run();
|
|
1607
|
+
} catch (e) {
|
|
1608
|
+
const chalk9 = (await import("chalk")).default;
|
|
1609
|
+
console.log(`
|
|
1610
|
+
${chalk9.red("\u2717")} ${chalk9.red.bold("FAILED:")} ${chalk9.white(e.message)}`);
|
|
1611
|
+
console.log(chalk9.gray(e.stack || ""));
|
|
1612
|
+
process.exit(1);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
main();
|
|
1616
|
+
//# sourceMappingURL=index.js.map
|