framer-export 4.3.7 → 4.3.10
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/dist/cli/index.js +1099 -309
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "framer-export",
|
|
18
|
-
version: "4.3.
|
|
18
|
+
version: "4.3.10",
|
|
19
19
|
description: "Export any Framer, Webflow, or Wix site 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",
|
|
@@ -82,8 +82,77 @@ var init_package = __esm({
|
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
// src/cli/
|
|
85
|
+
// src/cli/theme.ts
|
|
86
86
|
import chalk from "chalk";
|
|
87
|
+
function stripAnsi(s) {
|
|
88
|
+
return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
89
|
+
}
|
|
90
|
+
function softGradient(text) {
|
|
91
|
+
const colors = [THEME.primary, THEME.primarySoft, THEME.text, THEME.primarySoft, THEME.primary];
|
|
92
|
+
let cursor = 0;
|
|
93
|
+
return text.split("").map((char) => {
|
|
94
|
+
if (char === " ") return char;
|
|
95
|
+
const color = colors[cursor % colors.length];
|
|
96
|
+
cursor++;
|
|
97
|
+
return chalk.hex(color).bold(char);
|
|
98
|
+
}).join("");
|
|
99
|
+
}
|
|
100
|
+
function chip(label) {
|
|
101
|
+
return `${ui.border("[")}${ui.primary(label)}${ui.border("]")}`;
|
|
102
|
+
}
|
|
103
|
+
function bullet(label = "\u2022") {
|
|
104
|
+
return ui.primary(label);
|
|
105
|
+
}
|
|
106
|
+
function centerText(text, width) {
|
|
107
|
+
const visible = stripAnsi(text).length;
|
|
108
|
+
if (visible >= width) return text;
|
|
109
|
+
const left = Math.floor((width - visible) / 2);
|
|
110
|
+
return " ".repeat(left) + text + " ".repeat(width - visible - left);
|
|
111
|
+
}
|
|
112
|
+
function truncatePlain(text, max) {
|
|
113
|
+
if (text.length <= max) return text;
|
|
114
|
+
return text.slice(0, Math.max(0, max - 2)) + "..";
|
|
115
|
+
}
|
|
116
|
+
var THEME, ui;
|
|
117
|
+
var init_theme = __esm({
|
|
118
|
+
"src/cli/theme.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
THEME = {
|
|
121
|
+
background: "#0A0A0A",
|
|
122
|
+
panel: "#141414",
|
|
123
|
+
element: "#1E1E1E",
|
|
124
|
+
border: "#484848",
|
|
125
|
+
borderActive: "#606060",
|
|
126
|
+
text: "#EEEEEE",
|
|
127
|
+
muted: "#808080",
|
|
128
|
+
primary: "#FAB283",
|
|
129
|
+
primarySoft: "#FFC09F",
|
|
130
|
+
secondary: "#5C9CF5",
|
|
131
|
+
accent: "#9D7CD8",
|
|
132
|
+
success: "#7FD88F",
|
|
133
|
+
warning: "#F5A742",
|
|
134
|
+
error: "#E06C75",
|
|
135
|
+
info: "#56B6C2"
|
|
136
|
+
};
|
|
137
|
+
ui = {
|
|
138
|
+
text: chalk.hex(THEME.text),
|
|
139
|
+
muted: chalk.hex(THEME.muted),
|
|
140
|
+
primary: chalk.hex(THEME.primary),
|
|
141
|
+
primarySoft: chalk.hex(THEME.primarySoft),
|
|
142
|
+
secondary: chalk.hex(THEME.secondary),
|
|
143
|
+
accent: chalk.hex(THEME.accent),
|
|
144
|
+
success: chalk.hex(THEME.success),
|
|
145
|
+
warning: chalk.hex(THEME.warning),
|
|
146
|
+
error: chalk.hex(THEME.error),
|
|
147
|
+
info: chalk.hex(THEME.info),
|
|
148
|
+
border: chalk.hex(THEME.border),
|
|
149
|
+
borderActive: chalk.hex(THEME.borderActive),
|
|
150
|
+
panel: chalk.hex(THEME.panel)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// src/cli/banner.ts
|
|
87
156
|
function getWidth() {
|
|
88
157
|
return process.stdout.columns || 80;
|
|
89
158
|
}
|
|
@@ -91,34 +160,46 @@ function showBanner() {
|
|
|
91
160
|
const width = getWidth();
|
|
92
161
|
const isSmall = width < 65;
|
|
93
162
|
if (isSmall) {
|
|
94
|
-
console.log(
|
|
95
|
-
|
|
96
|
-
|
|
163
|
+
console.log(
|
|
164
|
+
`
|
|
165
|
+
${ui.primary.bold("f-export")} ${ui.muted(`v${package_default.version}`)} ${chip("beta ui")}`
|
|
166
|
+
);
|
|
167
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("for Framer, Webflow, and Wix")}
|
|
97
168
|
`);
|
|
98
169
|
return;
|
|
99
170
|
}
|
|
100
|
-
const gold = chalk.hex("#D4A017").bold;
|
|
101
|
-
const gray = chalk.gray;
|
|
102
171
|
console.log("");
|
|
103
172
|
ASCII_ART.forEach((line) => {
|
|
104
|
-
console.log(" " +
|
|
173
|
+
console.log(" " + softGradient(line));
|
|
105
174
|
});
|
|
106
175
|
console.log("");
|
|
107
|
-
console.log(
|
|
108
|
-
`)
|
|
176
|
+
console.log(
|
|
177
|
+
` ${ui.muted(`v${package_default.version}`)} ${ui.text.bold("Framer Export")} ${chip("fexport")} ${ui.muted("local mirror exporter")}`
|
|
178
|
+
);
|
|
179
|
+
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")}
|
|
181
|
+
`
|
|
182
|
+
);
|
|
109
183
|
}
|
|
110
184
|
var ASCII_ART;
|
|
111
185
|
var init_banner = __esm({
|
|
112
186
|
"src/cli/banner.ts"() {
|
|
113
187
|
"use strict";
|
|
114
188
|
init_package();
|
|
189
|
+
init_theme();
|
|
115
190
|
ASCII_ART = [
|
|
116
|
-
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\
|
|
117
|
-
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\
|
|
118
|
-
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\
|
|
119
|
-
"\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\
|
|
120
|
-
"\u2588\u2588\u2551 \u2588\u2588\
|
|
121
|
-
"\u255A\u2550\u255D \u255A\u2550\
|
|
191
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
192
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
193
|
+
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
194
|
+
"\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
195
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
196
|
+
"\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D",
|
|
197
|
+
"\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",
|
|
198
|
+
"\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",
|
|
199
|
+
"\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 ",
|
|
200
|
+
"\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 ",
|
|
201
|
+
"\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 ",
|
|
202
|
+
"\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 "
|
|
122
203
|
];
|
|
123
204
|
}
|
|
124
205
|
});
|
|
@@ -259,7 +340,7 @@ var init_download = __esm({
|
|
|
259
340
|
});
|
|
260
341
|
|
|
261
342
|
// src/logger/index.ts
|
|
262
|
-
import
|
|
343
|
+
import chalk2 from "chalk";
|
|
263
344
|
function setCooking(spinner) {
|
|
264
345
|
_cooking = spinner;
|
|
265
346
|
}
|
|
@@ -275,64 +356,57 @@ var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
|
275
356
|
var init_logger = __esm({
|
|
276
357
|
"src/logger/index.ts"() {
|
|
277
358
|
"use strict";
|
|
359
|
+
init_theme();
|
|
278
360
|
_cooking = null;
|
|
279
361
|
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
280
362
|
LOG_PALETTE = [
|
|
281
|
-
(s) =>
|
|
282
|
-
(s) =>
|
|
283
|
-
(s) =>
|
|
284
|
-
(s) =>
|
|
285
|
-
(s) =>
|
|
286
|
-
(s) =>
|
|
287
|
-
(s) => chalk3.hex("#e8c33a")(s),
|
|
288
|
-
(s) => chalk3.hex("#dab660")(s),
|
|
289
|
-
(s) => chalk3.hex("#b8960a")(s),
|
|
290
|
-
(s) => chalk3.hex("#d4a76a")(s),
|
|
291
|
-
(s) => chalk3.hex("#e0a030")(s),
|
|
292
|
-
(s) => chalk3.hex("#c0a060")(s)
|
|
363
|
+
(s) => chalk2.hex(THEME.primary)(s),
|
|
364
|
+
(s) => chalk2.hex(THEME.primarySoft)(s),
|
|
365
|
+
(s) => chalk2.hex(THEME.secondary)(s),
|
|
366
|
+
(s) => chalk2.hex(THEME.accent)(s),
|
|
367
|
+
(s) => chalk2.hex(THEME.info)(s),
|
|
368
|
+
(s) => chalk2.hex(THEME.text)(s)
|
|
293
369
|
];
|
|
294
370
|
_li = 0;
|
|
295
371
|
log = (m) => {
|
|
296
372
|
_li++;
|
|
297
373
|
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
298
|
-
output(
|
|
374
|
+
output(
|
|
375
|
+
`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
|
|
376
|
+
);
|
|
299
377
|
};
|
|
300
378
|
INFO_PALETTE = [
|
|
301
|
-
(s) =>
|
|
302
|
-
(s) =>
|
|
303
|
-
(s) =>
|
|
304
|
-
(s) => chalk3.hex("#6BC4BE")(s),
|
|
305
|
-
(s) => chalk3.hex("#48B5AD")(s),
|
|
306
|
-
(s) => chalk3.hex("#52BAB4")(s)
|
|
379
|
+
(s) => chalk2.hex(THEME.info)(s),
|
|
380
|
+
(s) => chalk2.hex(THEME.secondary)(s),
|
|
381
|
+
(s) => chalk2.hex(THEME.primarySoft)(s)
|
|
307
382
|
];
|
|
308
383
|
_ii = 0;
|
|
309
384
|
info = (m) => {
|
|
310
385
|
_ii++;
|
|
311
386
|
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
312
|
-
output(
|
|
387
|
+
output(
|
|
388
|
+
`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
|
|
389
|
+
);
|
|
313
390
|
};
|
|
314
391
|
warn = (m) => {
|
|
315
392
|
const colors = [
|
|
316
|
-
(s) =>
|
|
317
|
-
(s) =>
|
|
318
|
-
(s) => chalk3.hex("#CC7722")(s),
|
|
319
|
-
(s) => chalk3.hex("#E87D2A")(s)
|
|
393
|
+
(s) => chalk2.hex(THEME.warning)(s),
|
|
394
|
+
(s) => chalk2.hex(THEME.primary)(s)
|
|
320
395
|
];
|
|
321
396
|
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
322
|
-
const line = `${
|
|
397
|
+
const line = `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
323
398
|
if (_cooking) _cooking.log(line);
|
|
324
399
|
else console.warn(line);
|
|
325
400
|
};
|
|
326
401
|
success = (m) => {
|
|
327
402
|
const colors = [
|
|
328
|
-
(s) =>
|
|
329
|
-
(s) =>
|
|
330
|
-
(s) => chalk3.hex("#43A047")(s),
|
|
331
|
-
(s) => chalk3.hex("#81C784")(s),
|
|
332
|
-
(s) => chalk3.hex("#2E7D32")(s)
|
|
403
|
+
(s) => chalk2.hex(THEME.success)(s),
|
|
404
|
+
(s) => chalk2.hex(THEME.info)(s)
|
|
333
405
|
];
|
|
334
406
|
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
335
|
-
output(
|
|
407
|
+
output(
|
|
408
|
+
`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
|
|
409
|
+
);
|
|
336
410
|
};
|
|
337
411
|
}
|
|
338
412
|
});
|
|
@@ -939,11 +1013,64 @@ const server = http.createServer((req, res) => {
|
|
|
939
1013
|
serveFile(filePath);
|
|
940
1014
|
});
|
|
941
1015
|
|
|
1016
|
+
const ANSI = {
|
|
1017
|
+
reset: '\\x1b[0m',
|
|
1018
|
+
primary: '\\x1b[38;2;250;178;131m',
|
|
1019
|
+
soft: '\\x1b[38;2;255;192;159m',
|
|
1020
|
+
text: '\\x1b[38;2;238;238;238m',
|
|
1021
|
+
muted: '\\x1b[38;2;128;128;128m',
|
|
1022
|
+
border: '\\x1b[38;2;72;72;72m',
|
|
1023
|
+
success: '\\x1b[38;2;127;216;143m',
|
|
1024
|
+
info: '\\x1b[38;2;86;182;194m',
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
function color(name, value) {
|
|
1028
|
+
return ANSI[name] + value + ANSI.reset;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function frameLine(label, value) {
|
|
1032
|
+
const text = ' ' + label.padEnd(10) + value;
|
|
1033
|
+
const pad = Math.max(0, 56 - text.length);
|
|
1034
|
+
console.log(' ' + color('border', '\u2502 ') + color('muted', label.padEnd(10)) + color('text', value) + ' '.repeat(pad) + color('border', ' \u2502'));
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function drawServerUi() {
|
|
1038
|
+
const logo = [
|
|
1039
|
+
'\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ',
|
|
1040
|
+
'\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557',
|
|
1041
|
+
'\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D',
|
|
1042
|
+
'\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',
|
|
1043
|
+
'\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',
|
|
1044
|
+
'\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 ',
|
|
1045
|
+
];
|
|
1046
|
+
|
|
1047
|
+
console.log('');
|
|
1048
|
+
for (const line of logo) console.log(' ' + color('primary', line));
|
|
1049
|
+
console.log('');
|
|
1050
|
+
console.log(' ' + color('border', '\u256D\u2500' + '\u2500'.repeat(56) + '\u2500\u256E'));
|
|
1051
|
+
console.log(' ' + color('border', '\u2502 ') + color('text', ' Framer Export local server') + ' '.repeat(28) + color('border', ' \u2502'));
|
|
1052
|
+
console.log(' ' + color('border', '\u251C\u2500' + '\u2500'.repeat(56) + '\u2500\u2524'));
|
|
1053
|
+
frameLine('URL', 'http://localhost:' + PORT);
|
|
1054
|
+
frameLine('Root', ROOT);
|
|
1055
|
+
frameLine('Mode', 'static mirror + SPA fallback');
|
|
1056
|
+
console.log(' ' + color('border', '\u2570\u2500' + '\u2500'.repeat(56) + '\u2500\u256F'));
|
|
1057
|
+
console.log('');
|
|
1058
|
+
console.log(' ' + color('success', 'ready') + color('muted', ' press Ctrl+C to stop') + '\\n');
|
|
1059
|
+
}
|
|
1060
|
+
|
|
942
1061
|
server.listen(PORT, () => {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1062
|
+
const frames = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
|
|
1063
|
+
let i = 0;
|
|
1064
|
+
const timer = setInterval(() => {
|
|
1065
|
+
process.stdout.write('\\r\\x1B[2K ' + color('primary', frames[i % frames.length]) + ' ' + color('muted', 'starting Framer Export server'));
|
|
1066
|
+
i++;
|
|
1067
|
+
}, 80);
|
|
1068
|
+
|
|
1069
|
+
setTimeout(() => {
|
|
1070
|
+
clearInterval(timer);
|
|
1071
|
+
process.stdout.write('\\r\\x1B[2K');
|
|
1072
|
+
drawServerUi();
|
|
1073
|
+
}, 720);
|
|
947
1074
|
});
|
|
948
1075
|
`;
|
|
949
1076
|
}
|
|
@@ -1017,15 +1144,21 @@ function processSEO(html, url) {
|
|
|
1017
1144
|
</head>`);
|
|
1018
1145
|
}
|
|
1019
1146
|
if (!html.includes('name="description"')) {
|
|
1020
|
-
html = html.replace(
|
|
1021
|
-
|
|
1147
|
+
html = html.replace(
|
|
1148
|
+
"</head>",
|
|
1149
|
+
` <meta name="description" content="Exported with Framer Export - Fast, SEO-optimized, and clean.">
|
|
1150
|
+
</head>`
|
|
1151
|
+
);
|
|
1022
1152
|
}
|
|
1023
1153
|
if (!html.includes('property="og:')) {
|
|
1024
|
-
html = html.replace(
|
|
1154
|
+
html = html.replace(
|
|
1155
|
+
"</head>",
|
|
1156
|
+
` <meta property="og:type" content="website">
|
|
1025
1157
|
<meta property="og:url" content="${canonical}">
|
|
1026
1158
|
<meta property="og:title" content="Exported Site">
|
|
1027
1159
|
<meta property="og:description" content="A fast, clean version of this site, exported for performance.">
|
|
1028
|
-
</head>`
|
|
1160
|
+
</head>`
|
|
1161
|
+
);
|
|
1029
1162
|
}
|
|
1030
1163
|
if (!html.includes('name="robots"')) {
|
|
1031
1164
|
html = html.replace("</head>", ` <meta name="robots" content="index, follow">
|
|
@@ -1186,60 +1319,54 @@ var init_output = __esm({
|
|
|
1186
1319
|
});
|
|
1187
1320
|
|
|
1188
1321
|
// src/cli/box.ts
|
|
1189
|
-
import
|
|
1322
|
+
import chalk3 from "chalk";
|
|
1190
1323
|
function maxWidth() {
|
|
1191
|
-
return Math.min(process.stdout.columns || 80,
|
|
1324
|
+
return Math.min(process.stdout.columns || 80, 76);
|
|
1192
1325
|
}
|
|
1193
1326
|
function padRight(text, w) {
|
|
1194
1327
|
const visible = stripAnsi(text).length;
|
|
1195
1328
|
if (visible >= w) return text;
|
|
1196
1329
|
return text + " ".repeat(w - visible);
|
|
1197
1330
|
}
|
|
1198
|
-
function stripAnsi(s) {
|
|
1199
|
-
return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
1200
|
-
}
|
|
1201
1331
|
function boxTop(w) {
|
|
1202
1332
|
const inner = w - 4;
|
|
1203
|
-
return " " +
|
|
1333
|
+
return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
|
|
1204
1334
|
}
|
|
1205
1335
|
function boxBot(w) {
|
|
1206
1336
|
const inner = w - 4;
|
|
1207
|
-
return " " +
|
|
1337
|
+
return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
|
|
1208
1338
|
}
|
|
1209
1339
|
function boxLine(w, text) {
|
|
1210
1340
|
const inner = w - 4;
|
|
1211
1341
|
const padded = padRight(text, inner);
|
|
1212
|
-
return " " +
|
|
1342
|
+
return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
|
|
1213
1343
|
}
|
|
1214
1344
|
function boxSep(w) {
|
|
1215
1345
|
const inner = w - 4;
|
|
1216
|
-
return " " +
|
|
1346
|
+
return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
|
|
1217
1347
|
}
|
|
1218
1348
|
function boxRow(w, label, value) {
|
|
1219
1349
|
const inner = w - 4;
|
|
1220
|
-
const labelPlain = stripAnsi(
|
|
1350
|
+
const labelPlain = stripAnsi(chalk3.bold(label));
|
|
1221
1351
|
const visible = labelPlain.length + 1 + value.length;
|
|
1222
1352
|
if (visible > inner) {
|
|
1223
1353
|
const avail = inner - labelPlain.length - 2;
|
|
1224
1354
|
const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
|
|
1225
|
-
return " " +
|
|
1355
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
|
|
1226
1356
|
}
|
|
1227
1357
|
const right = inner - labelPlain.length - 1 - value.length;
|
|
1228
|
-
return " " +
|
|
1358
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
|
|
1229
1359
|
}
|
|
1230
|
-
var B, G;
|
|
1231
1360
|
var init_box = __esm({
|
|
1232
1361
|
"src/cli/box.ts"() {
|
|
1233
1362
|
"use strict";
|
|
1234
|
-
|
|
1235
|
-
G = chalk4.hex("#C4953A");
|
|
1363
|
+
init_theme();
|
|
1236
1364
|
}
|
|
1237
1365
|
});
|
|
1238
1366
|
|
|
1239
1367
|
// src/exporter/summary.ts
|
|
1240
1368
|
import fs3 from "fs/promises";
|
|
1241
1369
|
import path4 from "path";
|
|
1242
|
-
import chalk5 from "chalk";
|
|
1243
1370
|
async function printSummary(exporter) {
|
|
1244
1371
|
const w = maxWidth();
|
|
1245
1372
|
const isSmall = w < 50;
|
|
@@ -1261,17 +1388,17 @@ async function printSummary(exporter) {
|
|
|
1261
1388
|
count("data"),
|
|
1262
1389
|
count("subpages")
|
|
1263
1390
|
]);
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
const C2 =
|
|
1267
|
-
const Y =
|
|
1268
|
-
const O =
|
|
1269
|
-
const Br =
|
|
1270
|
-
const Gr =
|
|
1271
|
-
const Gn =
|
|
1391
|
+
const G = ui.primary;
|
|
1392
|
+
const G2 = ui.primarySoft;
|
|
1393
|
+
const C2 = ui.secondary;
|
|
1394
|
+
const Y = ui.accent;
|
|
1395
|
+
const O = ui.warning;
|
|
1396
|
+
const Br = ui.info;
|
|
1397
|
+
const Gr = ui.muted;
|
|
1398
|
+
const Gn = ui.success;
|
|
1272
1399
|
const entries = [
|
|
1273
|
-
["styles/", styles, "CSS",
|
|
1274
|
-
["scripts/vendor/", vendor, "JS vendor",
|
|
1400
|
+
["styles/", styles, "CSS", G],
|
|
1401
|
+
["scripts/vendor/", vendor, "JS vendor", G2],
|
|
1275
1402
|
["scripts/modules/", scripts, "JS modules", C2],
|
|
1276
1403
|
["assets/images/", imgs, "images", Y],
|
|
1277
1404
|
["assets/videos/", videos, "videos", O],
|
|
@@ -1283,23 +1410,23 @@ async function printSummary(exporter) {
|
|
|
1283
1410
|
console.log("");
|
|
1284
1411
|
if (!isSmall) {
|
|
1285
1412
|
console.log(boxTop(w));
|
|
1286
|
-
console.log(boxLine(w,
|
|
1413
|
+
console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
|
|
1287
1414
|
console.log(boxSep(w));
|
|
1288
1415
|
} else {
|
|
1289
|
-
console.log(
|
|
1416
|
+
console.log(ui.text.bold(" Export Summary:"));
|
|
1290
1417
|
}
|
|
1291
1418
|
for (const [label, cnt, type, color] of entries) {
|
|
1292
1419
|
if (cnt === 0) continue;
|
|
1293
1420
|
const inner = w - 4;
|
|
1294
1421
|
const l = label.padEnd(16);
|
|
1295
1422
|
const c = String(cnt).padStart(3);
|
|
1296
|
-
const rowText = `${color(l)}${
|
|
1423
|
+
const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
|
|
1297
1424
|
const visible = 16 + 3 + 2 + type.length;
|
|
1298
1425
|
const pad = Math.max(0, inner - visible);
|
|
1299
1426
|
if (isSmall) {
|
|
1300
|
-
console.log(` ${color(label)} ${
|
|
1427
|
+
console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
|
|
1301
1428
|
} else {
|
|
1302
|
-
console.log(" " +
|
|
1429
|
+
console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
|
|
1303
1430
|
}
|
|
1304
1431
|
}
|
|
1305
1432
|
if (!isSmall) {
|
|
@@ -1309,37 +1436,746 @@ async function printSummary(exporter) {
|
|
|
1309
1436
|
const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
|
|
1310
1437
|
if (!isSmall) {
|
|
1311
1438
|
console.log(boxTop(w));
|
|
1312
|
-
console.log(boxLine(w,
|
|
1439
|
+
console.log(boxLine(w, ui.text.bold(" To serve locally")));
|
|
1313
1440
|
console.log(boxSep(w));
|
|
1314
1441
|
const inner = w - 6;
|
|
1315
1442
|
const cmdLen = cdCmd.length;
|
|
1316
1443
|
const pad = Math.max(0, inner - cmdLen);
|
|
1317
|
-
console.log(
|
|
1444
|
+
console.log(
|
|
1445
|
+
" " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502")
|
|
1446
|
+
);
|
|
1318
1447
|
console.log(boxBot(w));
|
|
1319
1448
|
} else {
|
|
1320
|
-
console.log(
|
|
1321
|
-
console.log(` ${
|
|
1449
|
+
console.log(ui.text.bold(" To serve locally:"));
|
|
1450
|
+
console.log(` ${G(cdCmd)}`);
|
|
1322
1451
|
}
|
|
1323
1452
|
console.log("");
|
|
1324
|
-
console.log(
|
|
1453
|
+
console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
|
|
1325
1454
|
console.log("");
|
|
1326
1455
|
}
|
|
1327
1456
|
var init_summary = __esm({
|
|
1328
1457
|
"src/exporter/summary.ts"() {
|
|
1329
1458
|
"use strict";
|
|
1330
1459
|
init_box();
|
|
1460
|
+
init_theme();
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
// src/cli/select.ts
|
|
1465
|
+
import readline from "readline";
|
|
1466
|
+
import { stdin, stdout } from "process";
|
|
1467
|
+
async function select(question, options, defaultIndex = 0) {
|
|
1468
|
+
const isTTY = stdin.isTTY && stdout.isTTY;
|
|
1469
|
+
if (!isTTY) {
|
|
1470
|
+
return fallbackPrompt(question, options, defaultIndex);
|
|
1471
|
+
}
|
|
1472
|
+
return arrowSelect(question, options, defaultIndex);
|
|
1473
|
+
}
|
|
1474
|
+
async function arrowSelect(question, options, defaultIndex) {
|
|
1475
|
+
const panelTopRow = await queryCursorRow();
|
|
1476
|
+
const width = Math.min(maxWidth(), 72);
|
|
1477
|
+
const inner = width - 4;
|
|
1478
|
+
const optionStartOffset = 3;
|
|
1479
|
+
const lineCount = options.length + 4;
|
|
1480
|
+
return new Promise((resolve) => {
|
|
1481
|
+
const firstEnabled = options.findIndex((option) => !option.disabled);
|
|
1482
|
+
let selected = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
|
|
1483
|
+
if (selected < 0) selected = 0;
|
|
1484
|
+
const move = (direction) => {
|
|
1485
|
+
let next = selected + direction;
|
|
1486
|
+
while (next >= 0 && next < options.length) {
|
|
1487
|
+
if (!options[next].disabled) {
|
|
1488
|
+
selected = next;
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
next += direction;
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
const render = (initial = false) => {
|
|
1495
|
+
if (!initial) {
|
|
1496
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
1497
|
+
stdout.write("\x1B[J");
|
|
1498
|
+
}
|
|
1499
|
+
console.log(boxTop(width));
|
|
1500
|
+
console.log(
|
|
1501
|
+
boxLine(width, centerText(`${ui.primary("\u25CF")} ${ui.text.bold(question)}`, inner))
|
|
1502
|
+
);
|
|
1503
|
+
console.log(boxSep(width));
|
|
1504
|
+
for (let i = 0; i < options.length; i++) {
|
|
1505
|
+
console.log(
|
|
1506
|
+
boxLine(width, centerText(renderOption(options[i], i === selected, inner), inner))
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
console.log(boxBot(width));
|
|
1510
|
+
};
|
|
1511
|
+
const choose = () => {
|
|
1512
|
+
cleanup();
|
|
1513
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
1514
|
+
stdout.write("\x1B[J");
|
|
1515
|
+
console.log(
|
|
1516
|
+
` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(stripAnsi(options[selected].label))}
|
|
1517
|
+
`
|
|
1518
|
+
);
|
|
1519
|
+
resolve(options[selected].value);
|
|
1520
|
+
};
|
|
1521
|
+
const onMouseData = (chunk) => {
|
|
1522
|
+
const mouse = parseMouseEvent(chunk);
|
|
1523
|
+
if (!mouse) return;
|
|
1524
|
+
if (mouse.kind === "wheel-up") {
|
|
1525
|
+
move(-1);
|
|
1526
|
+
render();
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (mouse.kind === "wheel-down") {
|
|
1530
|
+
move(1);
|
|
1531
|
+
render();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (panelTopRow === null) return;
|
|
1535
|
+
const idx = mouse.y - panelTopRow - optionStartOffset;
|
|
1536
|
+
if (idx < 0 || idx >= options.length || options[idx].disabled) return;
|
|
1537
|
+
if (selected !== idx) {
|
|
1538
|
+
selected = idx;
|
|
1539
|
+
render();
|
|
1540
|
+
}
|
|
1541
|
+
if (mouse.kind === "click") {
|
|
1542
|
+
choose();
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
const cleanup = () => {
|
|
1546
|
+
disableMouse();
|
|
1547
|
+
stdin.setRawMode(false);
|
|
1548
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1549
|
+
stdin.removeListener("data", onMouseData);
|
|
1550
|
+
stdin.pause();
|
|
1551
|
+
};
|
|
1552
|
+
render(true);
|
|
1553
|
+
readline.emitKeypressEvents(stdin);
|
|
1554
|
+
stdin.setRawMode(true);
|
|
1555
|
+
enableInteractiveInput();
|
|
1556
|
+
const onKeypress = (_str, key) => {
|
|
1557
|
+
if (!key) return;
|
|
1558
|
+
if (key.name === "up" && selected > 0) {
|
|
1559
|
+
move(-1);
|
|
1560
|
+
render();
|
|
1561
|
+
} else if (key.name === "down" && selected < options.length - 1) {
|
|
1562
|
+
move(1);
|
|
1563
|
+
render();
|
|
1564
|
+
} else if (key.name === "return") {
|
|
1565
|
+
choose();
|
|
1566
|
+
} else if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
1567
|
+
cleanup();
|
|
1568
|
+
process.exit(0);
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
stdin.resume();
|
|
1572
|
+
stdin.on("data", onMouseData);
|
|
1573
|
+
stdin.on("keypress", onKeypress);
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
async function fallbackPrompt(question, options, defaultIndex) {
|
|
1577
|
+
return new Promise((resolve) => {
|
|
1578
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
1579
|
+
const firstEnabled = options.findIndex((option) => !option.disabled);
|
|
1580
|
+
const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
|
|
1581
|
+
console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}
|
|
1582
|
+
`);
|
|
1583
|
+
for (let i = 0; i < options.length; i++) {
|
|
1584
|
+
const marker = i === enabledDefault ? ui.success(" \u25C6") : " ";
|
|
1585
|
+
const label = options[i].disabled ? ui.muted(stripAnsi(options[i].label)) : ui.text(options[i].label);
|
|
1586
|
+
console.log(` ${ui.muted(`[${i + 1}]`)}${marker} ${label}`);
|
|
1587
|
+
}
|
|
1588
|
+
console.log("");
|
|
1589
|
+
const def = String(enabledDefault + 1);
|
|
1590
|
+
const ask = () => {
|
|
1591
|
+
rl.question(
|
|
1592
|
+
` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
|
|
1593
|
+
(answer) => {
|
|
1594
|
+
const trimmed = answer.trim();
|
|
1595
|
+
if (!trimmed) {
|
|
1596
|
+
rl.close();
|
|
1597
|
+
const label = stripAnsi(options[enabledDefault].label);
|
|
1598
|
+
console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
|
|
1599
|
+
`);
|
|
1600
|
+
resolve(options[enabledDefault].value);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
const idx = parseInt(trimmed, 10);
|
|
1604
|
+
if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
|
|
1605
|
+
rl.close();
|
|
1606
|
+
const label = stripAnsi(options[idx - 1].label);
|
|
1607
|
+
console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
|
|
1608
|
+
`);
|
|
1609
|
+
resolve(options[idx - 1].value);
|
|
1610
|
+
} else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
|
|
1611
|
+
console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
|
|
1612
|
+
`);
|
|
1613
|
+
ask();
|
|
1614
|
+
} else {
|
|
1615
|
+
console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
|
|
1616
|
+
`);
|
|
1617
|
+
ask();
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
);
|
|
1621
|
+
};
|
|
1622
|
+
ask();
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
function renderOption(option, selected, width) {
|
|
1626
|
+
const plain = truncatePlain(stripAnsi(option.label), Math.max(10, width - 12));
|
|
1627
|
+
if (option.disabled) {
|
|
1628
|
+
return `${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")}`;
|
|
1629
|
+
}
|
|
1630
|
+
if (selected) {
|
|
1631
|
+
return `${ui.primary("\u203A")} ${ui.primary("[")} ${ui.text.bold(plain)} ${ui.primary("]")} ${ui.primary("\u2039")}`;
|
|
1632
|
+
}
|
|
1633
|
+
return `${ui.muted(" ")} ${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")} ${ui.muted(" ")}`;
|
|
1634
|
+
}
|
|
1635
|
+
function enableInteractiveInput() {
|
|
1636
|
+
stdout.write("\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1003h\x1B[?1006h");
|
|
1637
|
+
}
|
|
1638
|
+
function disableMouse() {
|
|
1639
|
+
stdout.write("\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?25h");
|
|
1640
|
+
}
|
|
1641
|
+
function parseMouseEvent(chunk) {
|
|
1642
|
+
const text = chunk.toString("utf-8");
|
|
1643
|
+
const match = text.match(/\x1B\[<(\d+);(\d+);(\d+)([mM])/);
|
|
1644
|
+
if (!match) return null;
|
|
1645
|
+
const code = Number(match[1]);
|
|
1646
|
+
const x = Number(match[2]);
|
|
1647
|
+
const y = Number(match[3]);
|
|
1648
|
+
const state = match[4];
|
|
1649
|
+
if (code === 64) return { kind: "wheel-up", x, y };
|
|
1650
|
+
if (code === 65) return { kind: "wheel-down", x, y };
|
|
1651
|
+
if (state === "m") return { kind: "click", x, y };
|
|
1652
|
+
if ((code & 32) === 32 || code === 35) return { kind: "hover", x, y };
|
|
1653
|
+
if ((code & 3) === 0) return { kind: "hover", x, y };
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
function queryCursorRow() {
|
|
1657
|
+
if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve(null);
|
|
1658
|
+
return new Promise((resolve) => {
|
|
1659
|
+
const wasRaw = stdin.isRaw;
|
|
1660
|
+
let done = false;
|
|
1661
|
+
const finish = (row) => {
|
|
1662
|
+
if (done) return;
|
|
1663
|
+
done = true;
|
|
1664
|
+
clearTimeout(timer);
|
|
1665
|
+
stdin.removeListener("data", onData);
|
|
1666
|
+
if (!wasRaw) stdin.setRawMode(false);
|
|
1667
|
+
resolve(row);
|
|
1668
|
+
};
|
|
1669
|
+
const onData = (chunk) => {
|
|
1670
|
+
const match = chunk.toString("utf-8").match(/\x1B\[(\d+);\d+R/);
|
|
1671
|
+
if (!match) return;
|
|
1672
|
+
finish(Number(match[1]));
|
|
1673
|
+
};
|
|
1674
|
+
const timer = setTimeout(() => finish(null), 80);
|
|
1675
|
+
stdin.setRawMode(true);
|
|
1676
|
+
stdin.resume();
|
|
1677
|
+
stdin.on("data", onData);
|
|
1678
|
+
stdout.write("\x1B[6n");
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
var init_select = __esm({
|
|
1682
|
+
"src/cli/select.ts"() {
|
|
1683
|
+
"use strict";
|
|
1684
|
+
init_box();
|
|
1685
|
+
init_theme();
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// src/ai/prompt-assistant.ts
|
|
1690
|
+
import fs4 from "fs/promises";
|
|
1691
|
+
import path5 from "path";
|
|
1692
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1693
|
+
import { spawn } from "child_process";
|
|
1694
|
+
async function runAiPromptAssistant(exporter) {
|
|
1695
|
+
if (!stdin2.isTTY || !stdout2.isTTY) return;
|
|
1696
|
+
printConvertPanel(exporter);
|
|
1697
|
+
const action = await select(
|
|
1698
|
+
"Framer Export AI Convert",
|
|
1699
|
+
[
|
|
1700
|
+
{ label: `${buttonLabel("Convert")} open AI prompt modal`, value: "convert" },
|
|
1701
|
+
{ label: `${buttonLabel("Skip")} finish export`, value: "skip" }
|
|
1702
|
+
],
|
|
1703
|
+
0
|
|
1704
|
+
);
|
|
1705
|
+
if (action === "skip") return;
|
|
1706
|
+
printAssistantModal("AI conversion prompt", [
|
|
1707
|
+
"Choose a target stack, AI tool, and conversion situation.",
|
|
1708
|
+
"Mouse clicks are supported in the terminal when available.",
|
|
1709
|
+
"A detailed prompt file will be generated inside the export folder."
|
|
1710
|
+
]);
|
|
1711
|
+
const targetOptions = [
|
|
1712
|
+
...TARGETS.map((target2) => ({ label: target2.label, value: target2.id })),
|
|
1713
|
+
{ label: "Customize with AI - BETA in development", value: "custom-ai", disabled: true }
|
|
1714
|
+
];
|
|
1715
|
+
const targetId = await select("Choose target stack", targetOptions, 0);
|
|
1716
|
+
const aiToolId = await select(
|
|
1717
|
+
"Choose the AI coding tool",
|
|
1718
|
+
AI_TOOLS.map((tool) => ({ label: tool.label, value: tool.id })),
|
|
1719
|
+
0
|
|
1720
|
+
);
|
|
1721
|
+
const goalId = await select(
|
|
1722
|
+
"Choose the conversion situation",
|
|
1723
|
+
GOALS.map((goal2) => ({ label: goal2.label, value: goal2.id })),
|
|
1724
|
+
0
|
|
1725
|
+
);
|
|
1726
|
+
const target = TARGETS.find((item) => item.id === targetId) || TARGETS[0];
|
|
1727
|
+
const aiTool = AI_TOOLS.find((item) => item.id === aiToolId) || AI_TOOLS[0];
|
|
1728
|
+
const goal = GOALS.find((item) => item.id === goalId) || GOALS[0];
|
|
1729
|
+
const facts = await collectExportFacts(exporter);
|
|
1730
|
+
const prompt = buildConversionPrompt(target, aiTool, goal, facts);
|
|
1731
|
+
const aiDir = path5.join(exporter.outDir, "ai");
|
|
1732
|
+
const promptPath = path5.join(aiDir, `${aiTool.id}-${target.id}-${goal.id}-prompt.md`);
|
|
1733
|
+
await fs4.mkdir(aiDir, { recursive: true });
|
|
1734
|
+
await fs4.writeFile(promptPath, prompt, "utf-8");
|
|
1735
|
+
printPromptResult(promptPath, target, aiTool, goal);
|
|
1736
|
+
const promptAction = await select(
|
|
1737
|
+
"Prompt actions",
|
|
1738
|
+
[
|
|
1739
|
+
{ label: `${buttonLabel("Copy prompt")} clipboard`, value: "copy" },
|
|
1740
|
+
{ label: `${buttonLabel("Done")} keep file only`, value: "done" }
|
|
1741
|
+
],
|
|
1742
|
+
0
|
|
1743
|
+
);
|
|
1744
|
+
if (promptAction === "copy") {
|
|
1745
|
+
try {
|
|
1746
|
+
await copyToClipboard(prompt);
|
|
1747
|
+
console.log(` ${ui.success("\u2713")} ${ui.text.bold("Prompt copied to clipboard")}
|
|
1748
|
+
`);
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
console.log(
|
|
1751
|
+
` ${ui.warning("!")} ${ui.warning("Clipboard copy unavailable:")} ${ui.muted(error.message)}
|
|
1752
|
+
`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
async function collectExportFacts(exporter) {
|
|
1758
|
+
const counts = {};
|
|
1759
|
+
const rootEntries = await safeReadDir(exporter.outDir);
|
|
1760
|
+
for (const item of IMPORTANT_DIRS) {
|
|
1761
|
+
counts[item] = item === "index.html" ? await exists(path5.join(exporter.outDir, item)) ? 1 : 0 : await countEntries(path5.join(exporter.outDir, item));
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
sourceUrl: exporter.siteUrl,
|
|
1765
|
+
outputDir: exporter.outDir,
|
|
1766
|
+
platformName: exporter.platform.displayName,
|
|
1767
|
+
rootEntries,
|
|
1768
|
+
counts
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
async function exists(filePath) {
|
|
1772
|
+
try {
|
|
1773
|
+
await fs4.access(filePath);
|
|
1774
|
+
return true;
|
|
1775
|
+
} catch {
|
|
1776
|
+
return false;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
async function safeReadDir(dir) {
|
|
1780
|
+
try {
|
|
1781
|
+
return (await fs4.readdir(dir)).sort();
|
|
1782
|
+
} catch {
|
|
1783
|
+
return [];
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
async function countEntries(dir) {
|
|
1787
|
+
try {
|
|
1788
|
+
return (await fs4.readdir(dir)).length;
|
|
1789
|
+
} catch {
|
|
1790
|
+
return 0;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
function buttonLabel(label) {
|
|
1794
|
+
return `${ui.border("[")} ${ui.text.bold(label)} ${ui.border("]")}`;
|
|
1795
|
+
}
|
|
1796
|
+
function centerText2(text, width) {
|
|
1797
|
+
const visible = stripAnsi(text).length;
|
|
1798
|
+
if (visible >= width) return text;
|
|
1799
|
+
const left = Math.floor((width - visible) / 2);
|
|
1800
|
+
return " ".repeat(left) + text + " ".repeat(width - visible - left);
|
|
1801
|
+
}
|
|
1802
|
+
function printConvertPanel(exporter) {
|
|
1803
|
+
const w = maxWidth();
|
|
1804
|
+
const inner = w - 4;
|
|
1805
|
+
const rows = [
|
|
1806
|
+
centerText2(`${ui.text.bold("AI Convert")} ${ui.muted("BETA")}`, inner),
|
|
1807
|
+
centerText2(ui.muted("Generate a conversion prompt after the export."), inner),
|
|
1808
|
+
centerText2(`${buttonLabel("Convert")} ${ui.muted("or")} ${buttonLabel("Skip")}`, inner),
|
|
1809
|
+
centerText2(ui.muted(path5.basename(exporter.outDir)), inner)
|
|
1810
|
+
];
|
|
1811
|
+
console.log("");
|
|
1812
|
+
console.log(boxTop(w));
|
|
1813
|
+
for (const row of rows) console.log(boxLine(w, row));
|
|
1814
|
+
console.log(boxBot(w));
|
|
1815
|
+
console.log("");
|
|
1816
|
+
}
|
|
1817
|
+
function printAssistantModal(title, lines) {
|
|
1818
|
+
const w = maxWidth();
|
|
1819
|
+
const inner = w - 4;
|
|
1820
|
+
console.log("");
|
|
1821
|
+
console.log(boxTop(w));
|
|
1822
|
+
console.log(boxLine(w, centerText2(`${ui.primary("\u25C6")} ${ui.text.bold(title)}`, inner)));
|
|
1823
|
+
console.log(boxSep(w));
|
|
1824
|
+
for (const line of lines) {
|
|
1825
|
+
console.log(boxLine(w, centerText2(ui.muted(line), inner)));
|
|
1826
|
+
}
|
|
1827
|
+
console.log(boxBot(w));
|
|
1828
|
+
console.log("");
|
|
1829
|
+
}
|
|
1830
|
+
function buildConversionPrompt(target, aiTool, goal, facts) {
|
|
1831
|
+
const rootEntries = facts.rootEntries.length ? facts.rootEntries.join(", ") : "No root entries detected";
|
|
1832
|
+
const exportDir = quoteForPrompt(facts.outputDir);
|
|
1833
|
+
const projectDir = quoteForPrompt(path5.join(facts.outputDir, target.projectDir));
|
|
1834
|
+
const scaffoldCommand = `cd ${exportDir}; ${target.scaffold}`;
|
|
1835
|
+
const sourceAssets = quoteForPrompt(path5.join(facts.outputDir, "assets", "*"));
|
|
1836
|
+
const destinationAssets = quoteForPrompt(
|
|
1837
|
+
path5.join(facts.outputDir, target.projectDir, target.staticDir, "assets")
|
|
1838
|
+
);
|
|
1839
|
+
const windowsAssetCopyCommand = `New-Item -ItemType Directory -Force -Path ${destinationAssets}; Copy-Item -Path ${sourceAssets} -Destination ${destinationAssets} -Recurse -Force`;
|
|
1840
|
+
const unixSourceAssets = quoteForPrompt(toPosixPath(path5.join(facts.outputDir, "assets")));
|
|
1841
|
+
const unixDestinationParent = quoteForPrompt(
|
|
1842
|
+
toPosixPath(path5.join(facts.outputDir, target.projectDir, target.staticDir))
|
|
1843
|
+
);
|
|
1844
|
+
const unixAssetCopyCommand = `mkdir -p ${unixDestinationParent} && cp -R ${unixSourceAssets} ${unixDestinationParent}/assets`;
|
|
1845
|
+
const directoryLines = IMPORTANT_DIRS.map(
|
|
1846
|
+
(dir) => `Inspect ${dir}: ${facts.counts[dir]} item(s) detected in the export.`
|
|
1847
|
+
);
|
|
1848
|
+
const promptLines = [
|
|
1849
|
+
`You are ${aiTool.agentName} working inside a local export created by Framer Export.`,
|
|
1850
|
+
"This brief was generated by the Framer Export AI Prompt Assistant BETA.",
|
|
1851
|
+
"Treat this beta prompt as a strict conversion checklist, not as permission to skip inspection.",
|
|
1852
|
+
"Your mission is to convert this exported static mirror into a clean production project.",
|
|
1853
|
+
`Target stack: ${target.stack}.`,
|
|
1854
|
+
`Selected AI coding tool: ${aiTool.displayName}.`,
|
|
1855
|
+
`Selected conversion situation: ${goal.label}.`,
|
|
1856
|
+
`Primary instruction for this situation: ${goal.instruction}`,
|
|
1857
|
+
`Main priority: ${goal.priority}`,
|
|
1858
|
+
`Source URL from the real export: ${facts.sourceUrl}`,
|
|
1859
|
+
`Detected platform from the real export: ${facts.platformName}`,
|
|
1860
|
+
`Export folder to inspect first: ${facts.outputDir}`,
|
|
1861
|
+
`Root entries actually present: ${rootEntries}`,
|
|
1862
|
+
`Create the converted project in: ${projectDir}`,
|
|
1863
|
+
`Recommended scaffold command: ${scaffoldCommand}`,
|
|
1864
|
+
`Expected important target files: ${target.entryFiles}`,
|
|
1865
|
+
`Routing guidance: ${target.routing}`,
|
|
1866
|
+
`Static assets destination for this stack: ${target.staticDir}/assets inside the converted project.`,
|
|
1867
|
+
`PowerShell command to copy exported assets after scaffolding: ${windowsAssetCopyCommand}`,
|
|
1868
|
+
`macOS/Linux command to copy exported assets after scaffolding: ${unixAssetCopyCommand}`,
|
|
1869
|
+
"Run the asset copy command; do not recreate, redownload, rename randomly, or replace real exported assets with placeholders.",
|
|
1870
|
+
"After copying assets, update every image, video, font, CSS url(), and script reference to the new local asset path.",
|
|
1871
|
+
"Do not rush. Take time to inspect the export before writing the final implementation.",
|
|
1872
|
+
"Do not invent brand names, copy, images, links, animations, colors, or sections.",
|
|
1873
|
+
"Use only real information found in index.html, CSS files, JavaScript files, data files, and assets.",
|
|
1874
|
+
"If a detail is missing, inspect more files instead of guessing.",
|
|
1875
|
+
"If a vendor script is minified or hard to understand, identify what behavior it provides before replacing it.",
|
|
1876
|
+
"Keep the final result professional, clean, responsive, and maintainable.",
|
|
1877
|
+
"Do not simplify the site because a section, animation, page, or layout is difficult.",
|
|
1878
|
+
"If something is hard, break it into smaller components and keep working until the result matches closely.",
|
|
1879
|
+
"For every exported page, aim for the closest possible pixel-perfect result: spacing, typography, images, viewport behavior, and motion.",
|
|
1880
|
+
"Do not merge multiple pages into one generic page unless the export proves they are duplicate routes.",
|
|
1881
|
+
"Do not replace complex exported sections with summaries, cards, screenshots, or placeholder blocks.",
|
|
1882
|
+
"If a page has visual depth, overlapping layers, sticky sections, galleries, or scroll effects, recreate those behaviors instead of flattening them.",
|
|
1883
|
+
"Start by listing the files and directories that matter for the conversion.",
|
|
1884
|
+
...directoryLines,
|
|
1885
|
+
"Read index.html fully enough to understand page structure, metadata, linked assets, and scripts.",
|
|
1886
|
+
"Read the CSS files that define layout, typography, responsive rules, and visual details.",
|
|
1887
|
+
"Inspect scripts/vendor only to understand required interactions; do not blindly copy huge vendor bundles.",
|
|
1888
|
+
"Inspect scripts/modules for page-specific logic, animations, sliders, menus, and dynamic behavior.",
|
|
1889
|
+
"Inspect data files for CMS-like content, page data, configuration, or serialized props.",
|
|
1890
|
+
"Inspect assets/images and preserve the real image files that are actually used.",
|
|
1891
|
+
"Inspect assets/fonts and preserve font loading if the design depends on custom fonts.",
|
|
1892
|
+
"Inspect subpages if it contains exported pages; map them to routes only when they represent real pages.",
|
|
1893
|
+
"If subpages contains pages, convert each meaningful page with its own route and page component.",
|
|
1894
|
+
"For each converted page, compare against the original exported HTML route and adjust until it is visually close.",
|
|
1895
|
+
"Create a clean project structure instead of dumping everything into one component.",
|
|
1896
|
+
"Separate global layout, page sections, shared components, data helpers, and styles.",
|
|
1897
|
+
"Use semantic HTML for headings, navigation, buttons, forms, sections, and footer content.",
|
|
1898
|
+
"Preserve the original hierarchy of visible content unless there is a clear bug to fix.",
|
|
1899
|
+
"Preserve real URLs and links, but convert local asset paths to the new project structure.",
|
|
1900
|
+
"Move static assets into the target framework public/static asset location when appropriate.",
|
|
1901
|
+
"Do not keep broken CDN references if a local exported asset already exists.",
|
|
1902
|
+
"Do not hardcode absolute machine paths into source files; use framework-relative public asset paths after copying.",
|
|
1903
|
+
"Keep original filenames when possible so CSS and content references remain traceable.",
|
|
1904
|
+
"Do not leave unused analytics, editor badges, platform badges, or export-only scripts in the new app.",
|
|
1905
|
+
"Replace platform-specific runtime code with native framework components when possible.",
|
|
1906
|
+
"Keep interactions that users can see: menus, hover states, forms, sliders, animations, and scroll effects.",
|
|
1907
|
+
"If an interaction is too complex, implement a clean equivalent and document the difference briefly.",
|
|
1908
|
+
"Keep responsive behavior for desktop, tablet, and mobile.",
|
|
1909
|
+
"Check layout at small widths and avoid fixed desktop-only dimensions unless the original requires them.",
|
|
1910
|
+
"Use CSS variables or a clear theme file for colors, spacing, radius, shadows, and typography.",
|
|
1911
|
+
"Name components after their role: Hero, Header, FeatureGrid, Gallery, Pricing, Footer, and similar real sections.",
|
|
1912
|
+
"Avoid generic placeholder components if the exported site has specific section meaning.",
|
|
1913
|
+
"Use TypeScript types where they make the content or component props clearer.",
|
|
1914
|
+
"Do not add unnecessary libraries unless they replace a real exported behavior cleanly.",
|
|
1915
|
+
"If you add a library, explain why it is needed and where it is used.",
|
|
1916
|
+
"Keep package.json scripts standard: dev, build, preview, lint when available.",
|
|
1917
|
+
"Keep the build reproducible from a fresh install.",
|
|
1918
|
+
"After implementing, run the install/build/typecheck commands available for the target project.",
|
|
1919
|
+
"Fix any build errors instead of leaving TODOs.",
|
|
1920
|
+
"Open the generated app locally if possible and compare against the exported index.html visually.",
|
|
1921
|
+
"When there are subpages, compare every generated route against its matching exported file.",
|
|
1922
|
+
"Verify that every visible image loads from the new project.",
|
|
1923
|
+
"Verify that font rendering is close to the export.",
|
|
1924
|
+
"Verify that navigation and internal links work.",
|
|
1925
|
+
"Verify that responsive breakpoints do not overlap or hide important content.",
|
|
1926
|
+
"Verify that there are no console errors caused by missing assets or copied platform scripts.",
|
|
1927
|
+
"Keep accessibility basics: alt text when inferable, keyboard-reachable controls, visible focus states.",
|
|
1928
|
+
"Keep SEO basics: title, meta description if present, canonical only if it is correct, Open Graph when present.",
|
|
1929
|
+
"Do not fabricate SEO copy; reuse existing metadata or ask for missing copy if necessary.",
|
|
1930
|
+
"Commit to a small number of high-quality files rather than many noisy fragments.",
|
|
1931
|
+
"If a section repeats, extract a reusable component and data array.",
|
|
1932
|
+
"If a section is unique, keep it simple and local to the page.",
|
|
1933
|
+
"Document important migration decisions in a short README inside the converted project.",
|
|
1934
|
+
"The README must mention the source export folder, target stack, setup command, and known limitations.",
|
|
1935
|
+
"Do not delete the original export folder.",
|
|
1936
|
+
"Do not modify unrelated files outside the new converted project unless required for setup.",
|
|
1937
|
+
"If the worktree has existing changes, avoid reverting or overwriting them.",
|
|
1938
|
+
"Before large edits, inspect first and explain the conversion plan briefly.",
|
|
1939
|
+
"Then implement the conversion step by step until the app builds.",
|
|
1940
|
+
"Final answer must summarize what was converted, where the new project lives, and which checks passed.",
|
|
1941
|
+
"If something could not be converted, state the exact file or behavior and the reason.",
|
|
1942
|
+
"Quality bar: the result should feel like a real hand-built production app, not an automated scrape or simplified demo."
|
|
1943
|
+
];
|
|
1944
|
+
return [
|
|
1945
|
+
`# ${aiTool.displayName} Conversion Prompt - ${target.label}`,
|
|
1946
|
+
"",
|
|
1947
|
+
"Status: BETA assistant output. Review the export carefully and follow the real files.",
|
|
1948
|
+
`Generated from real export: ${facts.outputDir}`,
|
|
1949
|
+
`AI tool: ${aiTool.displayName}`,
|
|
1950
|
+
`Target: ${target.label}`,
|
|
1951
|
+
`Situation: ${goal.label}`,
|
|
1952
|
+
"",
|
|
1953
|
+
promptLines.map((line, index) => `${String(index + 1).padStart(2, "0")}. ${line}`).join("\n"),
|
|
1954
|
+
""
|
|
1955
|
+
].join("\n");
|
|
1956
|
+
}
|
|
1957
|
+
function quoteForPrompt(value) {
|
|
1958
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
1959
|
+
}
|
|
1960
|
+
function toPosixPath(value) {
|
|
1961
|
+
return value.replace(/\\/g, "/");
|
|
1962
|
+
}
|
|
1963
|
+
async function copyToClipboard(text) {
|
|
1964
|
+
const commands = process.platform === "win32" ? [{ command: "clip", args: [] }] : process.platform === "darwin" ? [{ command: "pbcopy", args: [] }] : [
|
|
1965
|
+
{ command: "wl-copy", args: [] },
|
|
1966
|
+
{ command: "xclip", args: ["-selection", "clipboard"] },
|
|
1967
|
+
{ command: "xsel", args: ["--clipboard", "--input"] }
|
|
1968
|
+
];
|
|
1969
|
+
let lastError = null;
|
|
1970
|
+
for (const item of commands) {
|
|
1971
|
+
try {
|
|
1972
|
+
await pipeToCommand(item.command, item.args, text);
|
|
1973
|
+
return;
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
lastError = error;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
throw lastError || new Error("No clipboard command found");
|
|
1979
|
+
}
|
|
1980
|
+
function pipeToCommand(command, args, input) {
|
|
1981
|
+
return new Promise((resolve, reject) => {
|
|
1982
|
+
const child = spawn(command, args, { stdio: ["pipe", "ignore", "pipe"] });
|
|
1983
|
+
let stderr = "";
|
|
1984
|
+
let settled = false;
|
|
1985
|
+
const finish = (error) => {
|
|
1986
|
+
if (settled) return;
|
|
1987
|
+
settled = true;
|
|
1988
|
+
if (error) reject(error);
|
|
1989
|
+
else resolve();
|
|
1990
|
+
};
|
|
1991
|
+
child.stderr?.on("data", (chunk) => {
|
|
1992
|
+
stderr += chunk.toString();
|
|
1993
|
+
});
|
|
1994
|
+
child.on("error", finish);
|
|
1995
|
+
child.on("close", (code) => {
|
|
1996
|
+
if (code === 0) {
|
|
1997
|
+
finish();
|
|
1998
|
+
} else {
|
|
1999
|
+
finish(new Error(`${command} exited with ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
child.stdin?.end(input);
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
function printPromptResult(promptPath, target, aiTool, goal) {
|
|
2006
|
+
const w = maxWidth();
|
|
2007
|
+
const isSmall = w < 50;
|
|
2008
|
+
const inner = w - 4;
|
|
2009
|
+
const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
|
|
2010
|
+
console.log("");
|
|
2011
|
+
if (!isSmall) {
|
|
2012
|
+
console.log(boxTop(w));
|
|
2013
|
+
console.log(
|
|
2014
|
+
boxLine(
|
|
2015
|
+
w,
|
|
2016
|
+
centerText2(
|
|
2017
|
+
`${ui.success("\u2713")} ${ui.text.bold("AI Prompt Ready")} ${ui.muted("BETA")}`,
|
|
2018
|
+
inner
|
|
2019
|
+
)
|
|
2020
|
+
)
|
|
2021
|
+
);
|
|
2022
|
+
console.log(boxSep(w));
|
|
2023
|
+
console.log(
|
|
2024
|
+
boxLine(w, centerText2(`${ui.muted("Tool")} ${ui.primary(aiTool.displayName)}`, inner))
|
|
2025
|
+
);
|
|
2026
|
+
console.log(boxLine(w, centerText2(`${ui.muted("Stack")} ${ui.primary(target.label)}`, inner)));
|
|
2027
|
+
console.log(boxLine(w, centerText2(`${ui.muted("Mode")} ${ui.primary(goal.label)}`, inner)));
|
|
2028
|
+
console.log(boxLine(w, centerText2(`${ui.muted("File")} ${ui.primary(relPath)}`, inner)));
|
|
2029
|
+
console.log(boxSep(w));
|
|
2030
|
+
console.log(
|
|
2031
|
+
boxLine(w, centerText2(`${buttonLabel("Copy prompt")} ${buttonLabel("Done")}`, inner))
|
|
2032
|
+
);
|
|
2033
|
+
console.log(boxBot(w));
|
|
2034
|
+
} else {
|
|
2035
|
+
console.log(ui.text.bold(" AI Prompt Ready - BETA"));
|
|
2036
|
+
console.log(` Tool: ${ui.primary(aiTool.displayName)}`);
|
|
2037
|
+
console.log(` Stack: ${ui.primary(target.label)}`);
|
|
2038
|
+
console.log(` Situation: ${ui.primary(goal.label)}`);
|
|
2039
|
+
console.log(` File: ${ui.primary(relPath)}`);
|
|
2040
|
+
}
|
|
2041
|
+
console.log("");
|
|
2042
|
+
}
|
|
2043
|
+
var IMPORTANT_DIRS, AI_TOOLS, TARGETS, GOALS;
|
|
2044
|
+
var init_prompt_assistant = __esm({
|
|
2045
|
+
"src/ai/prompt-assistant.ts"() {
|
|
2046
|
+
"use strict";
|
|
2047
|
+
init_select();
|
|
2048
|
+
init_box();
|
|
2049
|
+
init_theme();
|
|
2050
|
+
IMPORTANT_DIRS = [
|
|
2051
|
+
"index.html",
|
|
2052
|
+
"styles",
|
|
2053
|
+
"scripts/vendor",
|
|
2054
|
+
"scripts/modules",
|
|
2055
|
+
"assets/images",
|
|
2056
|
+
"assets/videos",
|
|
2057
|
+
"assets/fonts",
|
|
2058
|
+
"assets/misc",
|
|
2059
|
+
"data",
|
|
2060
|
+
"subpages"
|
|
2061
|
+
];
|
|
2062
|
+
AI_TOOLS = [
|
|
2063
|
+
{
|
|
2064
|
+
id: "claude-code",
|
|
2065
|
+
label: "Claude Code",
|
|
2066
|
+
displayName: "Claude Code",
|
|
2067
|
+
agentName: "Claude Code"
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
id: "codex",
|
|
2071
|
+
label: "Codex",
|
|
2072
|
+
displayName: "Codex",
|
|
2073
|
+
agentName: "Codex coding agent"
|
|
2074
|
+
},
|
|
2075
|
+
{
|
|
2076
|
+
id: "opencode",
|
|
2077
|
+
label: "OpenCode",
|
|
2078
|
+
displayName: "OpenCode",
|
|
2079
|
+
agentName: "OpenCode"
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
id: "other-ai",
|
|
2083
|
+
label: "Other AI coding agent",
|
|
2084
|
+
displayName: "Other AI",
|
|
2085
|
+
agentName: "AI coding agent"
|
|
2086
|
+
}
|
|
2087
|
+
];
|
|
2088
|
+
TARGETS = [
|
|
2089
|
+
{
|
|
2090
|
+
id: "react-vite",
|
|
2091
|
+
label: "React + Vite + TypeScript",
|
|
2092
|
+
stack: "React 18/19, TypeScript, Vite, CSS modules or plain CSS",
|
|
2093
|
+
projectDir: "converted-react-vite",
|
|
2094
|
+
staticDir: "public",
|
|
2095
|
+
scaffold: "npm create vite@latest converted-react-vite -- --template react-ts",
|
|
2096
|
+
entryFiles: "src/main.tsx, src/App.tsx, src/components/*, src/styles/*",
|
|
2097
|
+
routing: "Use React Router only if multiple exported pages exist in subpages/."
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
id: "nextjs-app-router",
|
|
2101
|
+
label: "Next.js App Router",
|
|
2102
|
+
stack: "Next.js App Router, TypeScript, React Server Components where useful",
|
|
2103
|
+
projectDir: "converted-nextjs",
|
|
2104
|
+
staticDir: "public",
|
|
2105
|
+
scaffold: "npx create-next-app@latest converted-nextjs --ts --app --eslint",
|
|
2106
|
+
entryFiles: "app/page.tsx, app/layout.tsx, components/*, public/*",
|
|
2107
|
+
routing: "Map exported subpages to app routes and keep shared layout code reusable."
|
|
2108
|
+
},
|
|
2109
|
+
{
|
|
2110
|
+
id: "vue-vite",
|
|
2111
|
+
label: "Vue + Vite + TypeScript",
|
|
2112
|
+
stack: "Vue 3, TypeScript, Vite, single-file components",
|
|
2113
|
+
projectDir: "converted-vue-vite",
|
|
2114
|
+
staticDir: "public",
|
|
2115
|
+
scaffold: "npm create vite@latest converted-vue-vite -- --template vue-ts",
|
|
2116
|
+
entryFiles: "src/main.ts, src/App.vue, src/components/*.vue, src/styles/*",
|
|
2117
|
+
routing: "Use Vue Router only if multiple exported pages exist in subpages/."
|
|
2118
|
+
},
|
|
2119
|
+
{
|
|
2120
|
+
id: "sveltekit",
|
|
2121
|
+
label: "SvelteKit",
|
|
2122
|
+
stack: "SvelteKit, TypeScript, componentized routes and assets",
|
|
2123
|
+
projectDir: "converted-sveltekit",
|
|
2124
|
+
staticDir: "static",
|
|
2125
|
+
scaffold: "npm create svelte@latest converted-sveltekit",
|
|
2126
|
+
entryFiles: "src/routes/+page.svelte, src/lib/components/*, static/*",
|
|
2127
|
+
routing: "Create SvelteKit routes for meaningful exported pages when subpages/ exists."
|
|
2128
|
+
},
|
|
2129
|
+
{
|
|
2130
|
+
id: "astro",
|
|
2131
|
+
label: "Astro",
|
|
2132
|
+
stack: "Astro, TypeScript, island components only where interactivity is needed",
|
|
2133
|
+
projectDir: "converted-astro",
|
|
2134
|
+
staticDir: "public",
|
|
2135
|
+
scaffold: "npm create astro@latest converted-astro",
|
|
2136
|
+
entryFiles: "src/pages/index.astro, src/components/*, public/*",
|
|
2137
|
+
routing: "Use Astro pages for exported subpages and avoid unnecessary client JavaScript."
|
|
2138
|
+
}
|
|
2139
|
+
];
|
|
2140
|
+
GOALS = [
|
|
2141
|
+
{
|
|
2142
|
+
id: "clean-rebuild",
|
|
2143
|
+
label: "Clean professional rebuild",
|
|
2144
|
+
instruction: "Rebuild the export as clean production code, not as a one-file HTML clone, while preserving the full page experience.",
|
|
2145
|
+
priority: "Readable structure, maintainability, complete pages, and faithful visual result."
|
|
2146
|
+
},
|
|
2147
|
+
{
|
|
2148
|
+
id: "pixel-perfect",
|
|
2149
|
+
label: "Pixel-perfect visual migration",
|
|
2150
|
+
instruction: "Prioritize pixel-perfect fidelity before refactoring anything aggressively, even when the layout is difficult.",
|
|
2151
|
+
priority: "Spacing, typography, responsive behavior, colors, media, animations, and page-by-page fidelity."
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
id: "component-system",
|
|
2155
|
+
label: "Reusable component system",
|
|
2156
|
+
instruction: "Extract repeated UI blocks into reusable components with clean props without simplifying the original pages.",
|
|
2157
|
+
priority: "Components, layout primitives, naming, future editability, and complete section coverage."
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
id: "performance-seo",
|
|
2161
|
+
label: "Performance and SEO rebuild",
|
|
2162
|
+
instruction: "Rebuild the site while reducing unused vendor code and improving SEO basics without losing visual fidelity.",
|
|
2163
|
+
priority: "Fast loading, semantic markup, metadata, accessibility, asset hygiene, and faithful pages."
|
|
2164
|
+
}
|
|
2165
|
+
];
|
|
1331
2166
|
}
|
|
1332
2167
|
});
|
|
1333
2168
|
|
|
1334
2169
|
// src/cli/cooking.ts
|
|
1335
|
-
import
|
|
1336
|
-
var SHINE_WIDTH, FRAME_INTERVAL,
|
|
2170
|
+
import chalk4 from "chalk";
|
|
2171
|
+
var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, CookingSpinner;
|
|
1337
2172
|
var init_cooking = __esm({
|
|
1338
2173
|
"src/cli/cooking.ts"() {
|
|
1339
2174
|
"use strict";
|
|
2175
|
+
init_theme();
|
|
1340
2176
|
SHINE_WIDTH = 10;
|
|
1341
|
-
FRAME_INTERVAL =
|
|
1342
|
-
|
|
2177
|
+
FRAME_INTERVAL = 80;
|
|
2178
|
+
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1343
2179
|
CookingSpinner = class {
|
|
1344
2180
|
interval = null;
|
|
1345
2181
|
frame = 0;
|
|
@@ -1377,11 +2213,11 @@ var init_cooking = __esm({
|
|
|
1377
2213
|
}
|
|
1378
2214
|
draw() {
|
|
1379
2215
|
if (!this.active) return;
|
|
1380
|
-
const
|
|
1381
|
-
const shimmer = this.renderShimmer("
|
|
1382
|
-
const
|
|
1383
|
-
const phaseStr = this.phase ? ` ${
|
|
1384
|
-
process.stdout.write(`\r\x1B[2K ${
|
|
2216
|
+
const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
|
|
2217
|
+
const shimmer = this.renderShimmer("Exporting");
|
|
2218
|
+
const frameStr = ui.primary(spinner);
|
|
2219
|
+
const phaseStr = this.phase ? ` ${ui.muted(this.limitLen(this.phase, 52))}` : "";
|
|
2220
|
+
process.stdout.write(`\r\x1B[2K ${frameStr} ${shimmer}${phaseStr}`);
|
|
1385
2221
|
}
|
|
1386
2222
|
renderShimmer(text) {
|
|
1387
2223
|
let result = "";
|
|
@@ -1391,9 +2227,9 @@ var init_cooking = __esm({
|
|
|
1391
2227
|
if (dist < SHINE_WIDTH) {
|
|
1392
2228
|
const t = 1 - dist / SHINE_WIDTH;
|
|
1393
2229
|
const g = Math.floor(160 + t * 95);
|
|
1394
|
-
result +=
|
|
2230
|
+
result += chalk4.rgb(250, Math.min(255, g), Math.min(255, Math.floor(g * 0.75)))(text[i]);
|
|
1395
2231
|
} else {
|
|
1396
|
-
result +=
|
|
2232
|
+
result += ui.muted(text[i]);
|
|
1397
2233
|
}
|
|
1398
2234
|
}
|
|
1399
2235
|
return result;
|
|
@@ -1412,9 +2248,9 @@ __export(exporter_exports, {
|
|
|
1412
2248
|
FramerExporter: () => FramerExporter,
|
|
1413
2249
|
deriveOutputName: () => deriveOutputName
|
|
1414
2250
|
});
|
|
1415
|
-
import
|
|
1416
|
-
import
|
|
1417
|
-
import
|
|
2251
|
+
import fs5 from "fs/promises";
|
|
2252
|
+
import path6 from "path";
|
|
2253
|
+
import chalk5 from "chalk";
|
|
1418
2254
|
function deriveOutputName(url, platformName) {
|
|
1419
2255
|
try {
|
|
1420
2256
|
const parsed = new URL(url);
|
|
@@ -1446,8 +2282,10 @@ var init_exporter = __esm({
|
|
|
1446
2282
|
init_download2();
|
|
1447
2283
|
init_output();
|
|
1448
2284
|
init_summary();
|
|
2285
|
+
init_prompt_assistant();
|
|
1449
2286
|
init_platforms();
|
|
1450
2287
|
init_cooking();
|
|
2288
|
+
init_theme();
|
|
1451
2289
|
FramerExporter = class {
|
|
1452
2290
|
siteUrl;
|
|
1453
2291
|
outDir;
|
|
@@ -1473,12 +2311,16 @@ var init_exporter = __esm({
|
|
|
1473
2311
|
}
|
|
1474
2312
|
}
|
|
1475
2313
|
async run(includeSubpages = false) {
|
|
1476
|
-
console.log(
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
2314
|
+
console.log(
|
|
2315
|
+
`
|
|
2316
|
+
${ui.text.bold("Framer Export")} ${chip("mirror")} ${ui.muted("v4 pipeline")}
|
|
2317
|
+
`
|
|
2318
|
+
);
|
|
2319
|
+
info("Source : " + chalk5.underline(this.siteUrl));
|
|
2320
|
+
info("Output : " + ui.primary(this.outDir));
|
|
2321
|
+
info("Platform : " + ui.primary(this.platform.displayName));
|
|
1480
2322
|
if (includeSubpages) {
|
|
1481
|
-
info("Subpages : " +
|
|
2323
|
+
info("Subpages : " + ui.success("enabled"));
|
|
1482
2324
|
}
|
|
1483
2325
|
console.log("");
|
|
1484
2326
|
this.cooking = new CookingSpinner();
|
|
@@ -1496,7 +2338,7 @@ var init_exporter = __esm({
|
|
|
1496
2338
|
"data",
|
|
1497
2339
|
"subpages"
|
|
1498
2340
|
]) {
|
|
1499
|
-
await
|
|
2341
|
+
await fs5.mkdir(path6.join(this.outDir, d), { recursive: true });
|
|
1500
2342
|
}
|
|
1501
2343
|
log("Output directory structure created");
|
|
1502
2344
|
this.cooking.update("Fetching SSR HTML...");
|
|
@@ -1506,12 +2348,12 @@ var init_exporter = __esm({
|
|
|
1506
2348
|
this.ssrHTML = buf.toString("utf-8");
|
|
1507
2349
|
success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
|
|
1508
2350
|
} catch (e) {
|
|
1509
|
-
log(
|
|
2351
|
+
log(chalk5.red("Could not fetch SSR HTML: " + e.message));
|
|
1510
2352
|
}
|
|
1511
2353
|
const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
|
|
1512
2354
|
if (htmlDetected.name !== this.platform.name) {
|
|
1513
2355
|
this.platform = htmlDetected;
|
|
1514
|
-
log("Platform refined: " +
|
|
2356
|
+
log("Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)");
|
|
1515
2357
|
}
|
|
1516
2358
|
await launchAndCapture(this);
|
|
1517
2359
|
if (includeSubpages && this.page) {
|
|
@@ -1527,6 +2369,7 @@ var init_exporter = __esm({
|
|
|
1527
2369
|
console.log("");
|
|
1528
2370
|
success("Export complete!");
|
|
1529
2371
|
await printSummary(this);
|
|
2372
|
+
await runAiPromptAssistant(this);
|
|
1530
2373
|
}
|
|
1531
2374
|
async crawlSubpages() {
|
|
1532
2375
|
this.cooking?.update("Discovering sub-pages...");
|
|
@@ -1539,7 +2382,8 @@ var init_exporter = __esm({
|
|
|
1539
2382
|
const hrefs = /* @__PURE__ */ new Set();
|
|
1540
2383
|
for (const a of anchors) {
|
|
1541
2384
|
const href = a.href;
|
|
1542
|
-
if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#"))
|
|
2385
|
+
if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#"))
|
|
2386
|
+
continue;
|
|
1543
2387
|
try {
|
|
1544
2388
|
const u = new URL(href);
|
|
1545
2389
|
const h = u.hostname.replace(/^www\./, "");
|
|
@@ -1567,8 +2411,8 @@ var init_exporter = __esm({
|
|
|
1567
2411
|
});
|
|
1568
2412
|
const slug = this.deriveSlug(link, baseUrl);
|
|
1569
2413
|
const filename = slug + ".html";
|
|
1570
|
-
const filepath =
|
|
1571
|
-
await
|
|
2414
|
+
const filepath = path6.join(this.outDir, "subpages", filename);
|
|
2415
|
+
await fs5.writeFile(filepath, html, "utf-8");
|
|
1572
2416
|
log(" Saved: subpages/" + filename);
|
|
1573
2417
|
} catch (e) {
|
|
1574
2418
|
log(" Skipped " + link + ": " + e.message);
|
|
@@ -1590,142 +2434,39 @@ var init_exporter = __esm({
|
|
|
1590
2434
|
}
|
|
1591
2435
|
});
|
|
1592
2436
|
|
|
1593
|
-
// src/cli/select.ts
|
|
1594
|
-
import readline from "readline";
|
|
1595
|
-
import { stdin, stdout } from "process";
|
|
1596
|
-
import chalk8 from "chalk";
|
|
1597
|
-
function stripAnsi2(s) {
|
|
1598
|
-
return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
1599
|
-
}
|
|
1600
|
-
async function select(question, options, defaultIndex = 0) {
|
|
1601
|
-
const isTTY = stdin.isTTY && stdout.isTTY;
|
|
1602
|
-
if (!isTTY) {
|
|
1603
|
-
return fallbackPrompt(question, options, defaultIndex);
|
|
1604
|
-
}
|
|
1605
|
-
return arrowSelect(question, options, defaultIndex);
|
|
1606
|
-
}
|
|
1607
|
-
async function arrowSelect(question, options, defaultIndex) {
|
|
1608
|
-
return new Promise((resolve) => {
|
|
1609
|
-
let selected = defaultIndex;
|
|
1610
|
-
const render = (initial = false) => {
|
|
1611
|
-
if (!initial) {
|
|
1612
|
-
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1613
|
-
stdout.write("\x1B[J");
|
|
1614
|
-
}
|
|
1615
|
-
console.log(` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}`);
|
|
1616
|
-
for (let i = 0; i < options.length; i++) {
|
|
1617
|
-
if (i === selected) {
|
|
1618
|
-
console.log(` ${chalk8.hex("#D4A017")(">")} ${chalk8.white.bold(options[i].label)}`);
|
|
1619
|
-
} else {
|
|
1620
|
-
console.log(` ${chalk8.gray(options[i].label)}`);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
render(true);
|
|
1625
|
-
readline.emitKeypressEvents(stdin);
|
|
1626
|
-
stdin.setRawMode(true);
|
|
1627
|
-
const onKeypress = (_str, key) => {
|
|
1628
|
-
if (!key) return;
|
|
1629
|
-
if (key.name === "up" && selected > 0) {
|
|
1630
|
-
selected--;
|
|
1631
|
-
render();
|
|
1632
|
-
} else if (key.name === "down" && selected < options.length - 1) {
|
|
1633
|
-
selected++;
|
|
1634
|
-
render();
|
|
1635
|
-
} else if (key.name === "return") {
|
|
1636
|
-
stdin.setRawMode(false);
|
|
1637
|
-
stdin.removeListener("keypress", onKeypress);
|
|
1638
|
-
stdin.pause();
|
|
1639
|
-
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1640
|
-
stdout.write("\x1B[J");
|
|
1641
|
-
console.log(` ${chalk8.green("\u2713")} ${chalk8.white.bold(question)} ${chalk8.hex("#D4A017")(stripAnsi2(options[selected].label))}
|
|
1642
|
-
`);
|
|
1643
|
-
resolve(options[selected].value);
|
|
1644
|
-
} else if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
1645
|
-
stdin.setRawMode(false);
|
|
1646
|
-
stdin.removeListener("keypress", onKeypress);
|
|
1647
|
-
process.exit(0);
|
|
1648
|
-
}
|
|
1649
|
-
};
|
|
1650
|
-
stdin.resume();
|
|
1651
|
-
stdin.on("keypress", onKeypress);
|
|
1652
|
-
});
|
|
1653
|
-
}
|
|
1654
|
-
async function fallbackPrompt(question, options, defaultIndex) {
|
|
1655
|
-
return new Promise((resolve) => {
|
|
1656
|
-
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
1657
|
-
console.log(` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}
|
|
1658
|
-
`);
|
|
1659
|
-
for (let i = 0; i < options.length; i++) {
|
|
1660
|
-
const marker = i === defaultIndex ? chalk8.green(" \u2605") : " ";
|
|
1661
|
-
console.log(` ${chalk8.gray(`[${i + 1}]`)}${marker} ${chalk8.white(options[i].label)}`);
|
|
1662
|
-
}
|
|
1663
|
-
console.log("");
|
|
1664
|
-
const def = String(defaultIndex + 1);
|
|
1665
|
-
const ask = () => {
|
|
1666
|
-
rl.question(` ${chalk8.hex("#D4A017")(">")} ${chalk8.gray(`Choose [1-${options.length}] (${def})`)}: `, (answer) => {
|
|
1667
|
-
const trimmed = answer.trim();
|
|
1668
|
-
if (!trimmed) {
|
|
1669
|
-
rl.close();
|
|
1670
|
-
const label = stripAnsi2(options[defaultIndex].label);
|
|
1671
|
-
console.log(` ${chalk8.green("\u2713")} ${chalk8.hex("#D4A017")(label)}
|
|
1672
|
-
`);
|
|
1673
|
-
resolve(options[defaultIndex].value);
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
const idx = parseInt(trimmed, 10);
|
|
1677
|
-
if (idx >= 1 && idx <= options.length) {
|
|
1678
|
-
rl.close();
|
|
1679
|
-
const label = stripAnsi2(options[idx - 1].label);
|
|
1680
|
-
console.log(` ${chalk8.green("\u2713")} ${chalk8.hex("#D4A017")(label)}
|
|
1681
|
-
`);
|
|
1682
|
-
resolve(options[idx - 1].value);
|
|
1683
|
-
} else {
|
|
1684
|
-
console.log(` ${chalk8.red("\u2717")} Enter 1-${options.length}
|
|
1685
|
-
`);
|
|
1686
|
-
ask();
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
};
|
|
1690
|
-
ask();
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
var init_select = __esm({
|
|
1694
|
-
"src/cli/select.ts"() {
|
|
1695
|
-
"use strict";
|
|
1696
|
-
}
|
|
1697
|
-
});
|
|
1698
|
-
|
|
1699
2437
|
// src/cli/setup.ts
|
|
1700
2438
|
var setup_exports = {};
|
|
1701
2439
|
__export(setup_exports, {
|
|
1702
2440
|
runSetup: () => runSetup
|
|
1703
2441
|
});
|
|
1704
2442
|
import readline2 from "readline/promises";
|
|
1705
|
-
import { stdin as
|
|
1706
|
-
import
|
|
2443
|
+
import { stdin as stdin3, stdout as stdout3 } from "process";
|
|
2444
|
+
import path7 from "path";
|
|
1707
2445
|
import { URL as URL4 } from "url";
|
|
1708
|
-
import
|
|
2446
|
+
import chalk6 from "chalk";
|
|
1709
2447
|
function drawHeader(title) {
|
|
1710
2448
|
const w = maxWidth();
|
|
1711
2449
|
if (w < 50) {
|
|
1712
2450
|
console.log(`
|
|
1713
|
-
${
|
|
2451
|
+
${bullet("\u25CF")} ${ui.text.bold(title)}`);
|
|
1714
2452
|
return;
|
|
1715
2453
|
}
|
|
1716
2454
|
console.log(boxTop(w));
|
|
1717
|
-
console.log(boxLine(w,
|
|
2455
|
+
console.log(boxLine(w, ui.text.bold(" " + title)));
|
|
1718
2456
|
console.log(boxBot(w));
|
|
1719
2457
|
console.log("");
|
|
1720
2458
|
}
|
|
1721
2459
|
async function runSetup(legacyMode = false) {
|
|
1722
2460
|
showBanner();
|
|
1723
|
-
console.log(
|
|
1724
|
-
console.log(
|
|
1725
|
-
|
|
2461
|
+
console.log(` ${ui.text.bold("Framer Export setup")} ${chip("interactive")}`);
|
|
2462
|
+
console.log(
|
|
2463
|
+
` ${ui.muted("Export Framer, Webflow, and Wix sites into a clean local mirror.")}
|
|
2464
|
+
`
|
|
2465
|
+
);
|
|
2466
|
+
const rl = readline2.createInterface({ input: stdin3, output: stdout3 });
|
|
1726
2467
|
const ask = async (question, defaultVal) => {
|
|
1727
|
-
const suffix = defaultVal ?
|
|
1728
|
-
const prompt = ` ${
|
|
2468
|
+
const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
|
|
2469
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1729
2470
|
const answer = await rl.question(prompt);
|
|
1730
2471
|
return answer.trim() || defaultVal || "";
|
|
1731
2472
|
};
|
|
@@ -1737,42 +2478,54 @@ async function runSetup(legacyMode = false) {
|
|
|
1737
2478
|
new URL4(input);
|
|
1738
2479
|
siteUrl = input;
|
|
1739
2480
|
} catch {
|
|
1740
|
-
console.log(
|
|
1741
|
-
`)
|
|
2481
|
+
console.log(
|
|
2482
|
+
` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
|
|
2483
|
+
`
|
|
2484
|
+
);
|
|
1742
2485
|
}
|
|
1743
2486
|
}
|
|
1744
|
-
console.log(` ${
|
|
2487
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk6.underline(siteUrl)}
|
|
1745
2488
|
`);
|
|
1746
2489
|
drawHeader("Step 2 : Platform");
|
|
1747
2490
|
const detected = detectPlatform(siteUrl);
|
|
1748
2491
|
let platformName;
|
|
1749
2492
|
if (legacyMode) {
|
|
1750
|
-
console.log(` ${
|
|
2493
|
+
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
1751
2494
|
const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
|
|
1752
2495
|
platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
|
|
1753
|
-
console.log(` ${
|
|
2496
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
1754
2497
|
`);
|
|
1755
2498
|
} else {
|
|
1756
2499
|
rl.close();
|
|
1757
2500
|
const platforms = [
|
|
1758
|
-
{
|
|
1759
|
-
|
|
1760
|
-
|
|
2501
|
+
{
|
|
2502
|
+
label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
|
|
2503
|
+
value: "framer"
|
|
2504
|
+
},
|
|
2505
|
+
{
|
|
2506
|
+
label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
|
|
2507
|
+
value: "webflow"
|
|
2508
|
+
},
|
|
2509
|
+
{ label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
|
|
1761
2510
|
];
|
|
1762
2511
|
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
1763
|
-
platformName = await select(
|
|
2512
|
+
platformName = await select(
|
|
2513
|
+
"Select platform",
|
|
2514
|
+
platforms,
|
|
2515
|
+
Math.max(0, defaultIdx)
|
|
2516
|
+
);
|
|
1764
2517
|
}
|
|
1765
|
-
const rl2 = legacyMode ? rl : readline2.createInterface({ input:
|
|
2518
|
+
const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
|
|
1766
2519
|
const ask2 = async (question, defaultVal) => {
|
|
1767
|
-
const suffix = defaultVal ?
|
|
1768
|
-
const prompt = ` ${
|
|
2520
|
+
const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
|
|
2521
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1769
2522
|
const answer = await rl2.question(prompt);
|
|
1770
2523
|
return answer.trim() || defaultVal || "";
|
|
1771
2524
|
};
|
|
1772
2525
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
1773
2526
|
drawHeader("Step 3 : Output Directory");
|
|
1774
2527
|
const outDir = await ask2("Output directory", "./" + derivedName);
|
|
1775
|
-
console.log(` ${
|
|
2528
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
1776
2529
|
`);
|
|
1777
2530
|
drawHeader("Step 4 : Options");
|
|
1778
2531
|
let prettyPrint;
|
|
@@ -1781,15 +2534,19 @@ async function runSetup(legacyMode = false) {
|
|
|
1781
2534
|
if (legacyMode) {
|
|
1782
2535
|
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
1783
2536
|
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
1784
|
-
console.log(
|
|
1785
|
-
`)
|
|
2537
|
+
console.log(
|
|
2538
|
+
` ${ui.success("\u2713")} Pretty-print: ${prettyPrint ? ui.success("yes") : ui.error("no")}
|
|
2539
|
+
`
|
|
2540
|
+
);
|
|
1786
2541
|
const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
|
|
1787
2542
|
includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
|
|
1788
|
-
console.log(
|
|
1789
|
-
`)
|
|
2543
|
+
console.log(
|
|
2544
|
+
` ${ui.success("\u2713")} Sub-pages: ${includeSubpages ? ui.success("yes") : ui.error("no")}
|
|
2545
|
+
`
|
|
2546
|
+
);
|
|
1790
2547
|
const concurrencyAnswer = await ask2("Download concurrency", "12");
|
|
1791
2548
|
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
1792
|
-
console.log(` ${
|
|
2549
|
+
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
1793
2550
|
`);
|
|
1794
2551
|
} else {
|
|
1795
2552
|
rl2.close();
|
|
@@ -1798,33 +2555,40 @@ async function runSetup(legacyMode = false) {
|
|
|
1798
2555
|
{ label: "No", value: "no" }
|
|
1799
2556
|
]);
|
|
1800
2557
|
prettyPrint = prettyVal === "yes";
|
|
1801
|
-
const subpagesVal = await select(
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2558
|
+
const subpagesVal = await select(
|
|
2559
|
+
"Export sub-pages?",
|
|
2560
|
+
[
|
|
2561
|
+
{ label: "No", value: "no" },
|
|
2562
|
+
{ label: "Yes, crawl and export", value: "yes" }
|
|
2563
|
+
],
|
|
2564
|
+
0
|
|
2565
|
+
);
|
|
1805
2566
|
includeSubpages = subpagesVal === "yes";
|
|
1806
|
-
const concurrencyVal = await select(
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2567
|
+
const concurrencyVal = await select(
|
|
2568
|
+
"Download concurrency",
|
|
2569
|
+
[
|
|
2570
|
+
{ label: "6 (slow connection)", value: "6" },
|
|
2571
|
+
{ label: "12 (default)", value: "12" },
|
|
2572
|
+
{ label: "20 (fast connection)", value: "20" }
|
|
2573
|
+
],
|
|
2574
|
+
1
|
|
2575
|
+
);
|
|
1811
2576
|
concurrency = parseInt(concurrencyVal, 10);
|
|
1812
2577
|
}
|
|
1813
2578
|
const w = maxWidth();
|
|
1814
2579
|
const isSmall = w < 50;
|
|
1815
|
-
const G2 = chalk9.hex("#C4953A");
|
|
1816
|
-
const B2 = chalk9.hex("#8B6914");
|
|
1817
2580
|
console.log("");
|
|
1818
2581
|
if (!isSmall) {
|
|
1819
2582
|
console.log(boxTop(w));
|
|
1820
|
-
console.log(boxLine(w,
|
|
2583
|
+
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
2584
|
+
console.log(boxSep(w));
|
|
1821
2585
|
} else {
|
|
1822
|
-
console.log(
|
|
2586
|
+
console.log(ui.text.bold(" Summary:"));
|
|
1823
2587
|
}
|
|
1824
2588
|
for (const [label, value] of [
|
|
1825
2589
|
["URL", siteUrl],
|
|
1826
2590
|
["Platform", platformName],
|
|
1827
|
-
["Output",
|
|
2591
|
+
["Output", path7.resolve(outDir)],
|
|
1828
2592
|
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
1829
2593
|
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
1830
2594
|
["Concurrency", String(concurrency)]
|
|
@@ -1849,14 +2613,14 @@ async function runSetup(legacyMode = false) {
|
|
|
1849
2613
|
}
|
|
1850
2614
|
if (!startExport) {
|
|
1851
2615
|
console.log(`
|
|
1852
|
-
${
|
|
2616
|
+
${ui.warning("Export cancelled.")}
|
|
1853
2617
|
`);
|
|
1854
2618
|
return;
|
|
1855
2619
|
}
|
|
1856
2620
|
console.log("");
|
|
1857
2621
|
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1858
2622
|
CFG2.concurrency = concurrency;
|
|
1859
|
-
const exporter = new FramerExporter(siteUrl,
|
|
2623
|
+
const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
|
|
1860
2624
|
exporter.prettyPrint = prettyPrint;
|
|
1861
2625
|
await exporter.run(includeSubpages);
|
|
1862
2626
|
}
|
|
@@ -1868,24 +2632,30 @@ var init_setup = __esm({
|
|
|
1868
2632
|
init_platforms();
|
|
1869
2633
|
init_select();
|
|
1870
2634
|
init_box();
|
|
2635
|
+
init_theme();
|
|
1871
2636
|
}
|
|
1872
2637
|
});
|
|
1873
2638
|
|
|
1874
2639
|
// src/cli/index.ts
|
|
1875
2640
|
init_package();
|
|
1876
|
-
import
|
|
2641
|
+
import path8 from "path";
|
|
1877
2642
|
import { URL as URL5 } from "url";
|
|
1878
2643
|
|
|
1879
2644
|
// src/cli/help.ts
|
|
1880
2645
|
init_banner();
|
|
1881
|
-
|
|
2646
|
+
init_theme();
|
|
1882
2647
|
function showHelp() {
|
|
1883
2648
|
showBanner();
|
|
1884
|
-
console.log(
|
|
1885
|
-
|
|
1886
|
-
console.log(
|
|
2649
|
+
console.log(`${ui.text.bold(" USAGE")} ${chip("cli")}
|
|
2650
|
+
`);
|
|
2651
|
+
console.log(
|
|
2652
|
+
` ${ui.primary("framer-export")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`
|
|
2653
|
+
);
|
|
2654
|
+
console.log(
|
|
2655
|
+
` ${ui.primary("fexport")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`
|
|
2656
|
+
);
|
|
1887
2657
|
console.log("");
|
|
1888
|
-
console.log(
|
|
2658
|
+
console.log(ui.text.bold(" OPTIONS\n"));
|
|
1889
2659
|
const opts = [
|
|
1890
2660
|
["--setup", "Launch the interactive setup assistant"],
|
|
1891
2661
|
["--platform <p>", "Force platform: framer, webflow, wix"],
|
|
@@ -1894,27 +2664,39 @@ function showHelp() {
|
|
|
1894
2664
|
["--help, -h", "Show this help message"]
|
|
1895
2665
|
];
|
|
1896
2666
|
for (const [flag, desc] of opts) {
|
|
1897
|
-
console.log(` ${
|
|
2667
|
+
console.log(` ${ui.success(flag.padEnd(18))} ${ui.text(desc)}`);
|
|
1898
2668
|
}
|
|
1899
2669
|
console.log("");
|
|
1900
|
-
console.log(
|
|
2670
|
+
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
1901
2671
|
const platforms = [
|
|
1902
2672
|
["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
|
|
1903
2673
|
["Webflow", "Auto-detected via .webflow.io URLs"],
|
|
1904
2674
|
["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
|
|
1905
2675
|
];
|
|
1906
2676
|
for (const [name, desc] of platforms) {
|
|
1907
|
-
console.log(` ${
|
|
2677
|
+
console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
|
|
1908
2678
|
}
|
|
1909
2679
|
console.log("");
|
|
1910
|
-
console.log(
|
|
1911
|
-
console.log(
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
console.log(
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
console.log(
|
|
2680
|
+
console.log(ui.text.bold(" EXAMPLES\n"));
|
|
2681
|
+
console.log(
|
|
2682
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.framer.app")}`
|
|
2683
|
+
);
|
|
2684
|
+
console.log(
|
|
2685
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.webflow.io")}`
|
|
2686
|
+
);
|
|
2687
|
+
console.log(
|
|
2688
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://user.wixsite.com/my-site")}`
|
|
2689
|
+
);
|
|
2690
|
+
console.log(
|
|
2691
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--platform webflow")} ${ui.warning("https://custom.com")}`
|
|
2692
|
+
);
|
|
2693
|
+
console.log(
|
|
2694
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--subpages")} ${ui.warning("https://mysite.com")}`
|
|
2695
|
+
);
|
|
2696
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup")}`);
|
|
2697
|
+
console.log(
|
|
2698
|
+
` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup --legacy-mode")}`
|
|
2699
|
+
);
|
|
1918
2700
|
console.log("");
|
|
1919
2701
|
}
|
|
1920
2702
|
|
|
@@ -1953,6 +2735,7 @@ async function checkForUpdates(currentVersion) {
|
|
|
1953
2735
|
}
|
|
1954
2736
|
|
|
1955
2737
|
// src/cli/index.ts
|
|
2738
|
+
init_theme();
|
|
1956
2739
|
var VERSION = package_default.version;
|
|
1957
2740
|
function extractFlag(args, flag) {
|
|
1958
2741
|
const idx = args.indexOf(flag);
|
|
@@ -1974,30 +2757,34 @@ async function main() {
|
|
|
1974
2757
|
process.exit(0);
|
|
1975
2758
|
}
|
|
1976
2759
|
if (args.includes("--about")) {
|
|
1977
|
-
const
|
|
2760
|
+
const chalk7 = (await import("chalk")).default;
|
|
1978
2761
|
showBanner();
|
|
1979
|
-
console.log(` ${
|
|
1980
|
-
console.log(` ${
|
|
2762
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
2763
|
+
console.log(` ${ui.text(package_default.description)}
|
|
1981
2764
|
`);
|
|
1982
|
-
console.log(` ${
|
|
1983
|
-
console.log(
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
console.log(
|
|
1987
|
-
|
|
2765
|
+
console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
|
|
2766
|
+
console.log(
|
|
2767
|
+
` ${ui.text.bold("Portfolio")} ${chalk7.underline.hex("#FAB283")("https://github.com/danbenba")}`
|
|
2768
|
+
);
|
|
2769
|
+
console.log(
|
|
2770
|
+
` ${ui.text.bold("GitHub")} ${chalk7.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`
|
|
2771
|
+
);
|
|
2772
|
+
console.log(
|
|
2773
|
+
` ${ui.text.bold("npm")} ${chalk7.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`
|
|
2774
|
+
);
|
|
2775
|
+
console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
|
|
2776
|
+
console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
|
|
1988
2777
|
console.log("");
|
|
1989
2778
|
process.exit(0);
|
|
1990
2779
|
}
|
|
1991
2780
|
checkForUpdates(VERSION).then((latest) => {
|
|
1992
2781
|
if (!latest) return;
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
console.log("");
|
|
2000
|
-
});
|
|
2782
|
+
console.log("");
|
|
2783
|
+
console.log(
|
|
2784
|
+
` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`
|
|
2785
|
+
);
|
|
2786
|
+
console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
|
|
2787
|
+
console.log("");
|
|
2001
2788
|
});
|
|
2002
2789
|
if (args.includes("--help") || args.includes("-h")) {
|
|
2003
2790
|
showHelp();
|
|
@@ -2022,9 +2809,8 @@ async function main() {
|
|
|
2022
2809
|
try {
|
|
2023
2810
|
new URL5(url);
|
|
2024
2811
|
} catch {
|
|
2025
|
-
|
|
2026
|
-
console.log(` ${
|
|
2027
|
-
console.log(` ${chalk10.gray("Expected: https://yoursite.framer.app")}
|
|
2812
|
+
console.log(` ${ui.error("\u2717")} ${ui.error.bold("Invalid URL:")} ${ui.text(url)}`);
|
|
2813
|
+
console.log(` ${ui.muted("Expected: https://yoursite.framer.app")}
|
|
2028
2814
|
`);
|
|
2029
2815
|
process.exit(1);
|
|
2030
2816
|
}
|
|
@@ -2034,12 +2820,16 @@ async function main() {
|
|
|
2034
2820
|
const defaultDir = deriveOutputName2(url, detected);
|
|
2035
2821
|
const out = args[1] || `./${defaultDir}`;
|
|
2036
2822
|
try {
|
|
2037
|
-
await new FramerExporter2(url,
|
|
2823
|
+
await new FramerExporter2(url, path8.resolve(out), platformOverride || void 0).run(
|
|
2824
|
+
includeSubpages
|
|
2825
|
+
);
|
|
2038
2826
|
} catch (e) {
|
|
2039
|
-
const
|
|
2040
|
-
console.log(
|
|
2041
|
-
|
|
2042
|
-
|
|
2827
|
+
const chalk7 = (await import("chalk")).default;
|
|
2828
|
+
console.log(
|
|
2829
|
+
`
|
|
2830
|
+
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`
|
|
2831
|
+
);
|
|
2832
|
+
console.log(chalk7.gray(e.stack || ""));
|
|
2043
2833
|
process.exit(1);
|
|
2044
2834
|
}
|
|
2045
2835
|
}
|