framer-export 4.3.7 → 4.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +774 -285
- 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.8",
|
|
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,67 @@ 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
|
+
var THEME, ui;
|
|
107
|
+
var init_theme = __esm({
|
|
108
|
+
"src/cli/theme.ts"() {
|
|
109
|
+
"use strict";
|
|
110
|
+
THEME = {
|
|
111
|
+
background: "#0A0A0A",
|
|
112
|
+
panel: "#141414",
|
|
113
|
+
element: "#1E1E1E",
|
|
114
|
+
border: "#484848",
|
|
115
|
+
borderActive: "#606060",
|
|
116
|
+
text: "#EEEEEE",
|
|
117
|
+
muted: "#808080",
|
|
118
|
+
primary: "#FAB283",
|
|
119
|
+
primarySoft: "#FFC09F",
|
|
120
|
+
secondary: "#5C9CF5",
|
|
121
|
+
accent: "#9D7CD8",
|
|
122
|
+
success: "#7FD88F",
|
|
123
|
+
warning: "#F5A742",
|
|
124
|
+
error: "#E06C75",
|
|
125
|
+
info: "#56B6C2"
|
|
126
|
+
};
|
|
127
|
+
ui = {
|
|
128
|
+
text: chalk.hex(THEME.text),
|
|
129
|
+
muted: chalk.hex(THEME.muted),
|
|
130
|
+
primary: chalk.hex(THEME.primary),
|
|
131
|
+
primarySoft: chalk.hex(THEME.primarySoft),
|
|
132
|
+
secondary: chalk.hex(THEME.secondary),
|
|
133
|
+
accent: chalk.hex(THEME.accent),
|
|
134
|
+
success: chalk.hex(THEME.success),
|
|
135
|
+
warning: chalk.hex(THEME.warning),
|
|
136
|
+
error: chalk.hex(THEME.error),
|
|
137
|
+
info: chalk.hex(THEME.info),
|
|
138
|
+
border: chalk.hex(THEME.border),
|
|
139
|
+
borderActive: chalk.hex(THEME.borderActive),
|
|
140
|
+
panel: chalk.hex(THEME.panel)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// src/cli/banner.ts
|
|
87
146
|
function getWidth() {
|
|
88
147
|
return process.stdout.columns || 80;
|
|
89
148
|
}
|
|
@@ -92,33 +151,35 @@ function showBanner() {
|
|
|
92
151
|
const isSmall = width < 65;
|
|
93
152
|
if (isSmall) {
|
|
94
153
|
console.log(`
|
|
95
|
-
${
|
|
96
|
-
console.log(` ${
|
|
154
|
+
${ui.primary.bold("f-export")} ${ui.muted(`v${package_default.version}`)} ${chip("beta ui")}`);
|
|
155
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("for Framer, Webflow, and Wix")}
|
|
97
156
|
`);
|
|
98
157
|
return;
|
|
99
158
|
}
|
|
100
|
-
const gold = chalk.hex("#D4A017").bold;
|
|
101
|
-
const gray = chalk.gray;
|
|
102
159
|
console.log("");
|
|
103
160
|
ASCII_ART.forEach((line) => {
|
|
104
|
-
console.log(" " +
|
|
161
|
+
console.log(" " + softGradient(line));
|
|
105
162
|
});
|
|
106
163
|
console.log("");
|
|
107
|
-
console.log(
|
|
108
|
-
`)
|
|
164
|
+
console.log(
|
|
165
|
+
` ${ui.muted(`v${package_default.version}`)} ${ui.text.bold("Framer Export")} ${chip("fexport")} ${ui.muted("local mirror exporter")}`
|
|
166
|
+
);
|
|
167
|
+
console.log(
|
|
168
|
+
` ${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")}
|
|
169
|
+
`
|
|
170
|
+
);
|
|
109
171
|
}
|
|
110
172
|
var ASCII_ART;
|
|
111
173
|
var init_banner = __esm({
|
|
112
174
|
"src/cli/banner.ts"() {
|
|
113
175
|
"use strict";
|
|
114
176
|
init_package();
|
|
177
|
+
init_theme();
|
|
115
178
|
ASCII_ART = [
|
|
116
|
-
"\
|
|
117
|
-
"\u2588\
|
|
118
|
-
"\u2588\
|
|
119
|
-
"\
|
|
120
|
-
"\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 ",
|
|
121
|
-
"\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"
|
|
179
|
+
" \u2584 \u2584 ",
|
|
180
|
+
"\u2588\u2580\u2580 \u2588\u2580\u2588 \u2584\u2580\u2588 \u2588\u2580\u2584\u2580\u2588 \u2588\u2580\u2580 \u2588\u2580\u2588 \u2588\u2580\u2580 \u2580\u2584\u2580 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2580\u2588\u2580",
|
|
181
|
+
"\u2588\u2580 \u2588\u2580\u2584 \u2588\u2580\u2588 \u2588 \u2580 \u2588 \u2588\u2588\u2584 \u2588\u2580\u2584 \u2588\u2588\u2584 \u2588 \u2588 \u2588\u2580\u2580 \u2588\u2584\u2588 \u2588\u2580\u2584 \u2588 ",
|
|
182
|
+
"\u2580 \u2580 \u2580 \u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580 "
|
|
122
183
|
];
|
|
123
184
|
}
|
|
124
185
|
});
|
|
@@ -259,7 +320,7 @@ var init_download = __esm({
|
|
|
259
320
|
});
|
|
260
321
|
|
|
261
322
|
// src/logger/index.ts
|
|
262
|
-
import
|
|
323
|
+
import chalk2 from "chalk";
|
|
263
324
|
function setCooking(spinner) {
|
|
264
325
|
_cooking = spinner;
|
|
265
326
|
}
|
|
@@ -275,64 +336,51 @@ var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
|
275
336
|
var init_logger = __esm({
|
|
276
337
|
"src/logger/index.ts"() {
|
|
277
338
|
"use strict";
|
|
339
|
+
init_theme();
|
|
278
340
|
_cooking = null;
|
|
279
341
|
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
280
342
|
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)
|
|
343
|
+
(s) => chalk2.hex(THEME.primary)(s),
|
|
344
|
+
(s) => chalk2.hex(THEME.primarySoft)(s),
|
|
345
|
+
(s) => chalk2.hex(THEME.secondary)(s),
|
|
346
|
+
(s) => chalk2.hex(THEME.accent)(s),
|
|
347
|
+
(s) => chalk2.hex(THEME.info)(s),
|
|
348
|
+
(s) => chalk2.hex(THEME.text)(s)
|
|
293
349
|
];
|
|
294
350
|
_li = 0;
|
|
295
351
|
log = (m) => {
|
|
296
352
|
_li++;
|
|
297
353
|
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
298
|
-
output(`${
|
|
354
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`);
|
|
299
355
|
};
|
|
300
356
|
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)
|
|
357
|
+
(s) => chalk2.hex(THEME.info)(s),
|
|
358
|
+
(s) => chalk2.hex(THEME.secondary)(s),
|
|
359
|
+
(s) => chalk2.hex(THEME.primarySoft)(s)
|
|
307
360
|
];
|
|
308
361
|
_ii = 0;
|
|
309
362
|
info = (m) => {
|
|
310
363
|
_ii++;
|
|
311
364
|
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
312
|
-
output(`${
|
|
365
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`);
|
|
313
366
|
};
|
|
314
367
|
warn = (m) => {
|
|
315
368
|
const colors = [
|
|
316
|
-
(s) =>
|
|
317
|
-
(s) =>
|
|
318
|
-
(s) => chalk3.hex("#CC7722")(s),
|
|
319
|
-
(s) => chalk3.hex("#E87D2A")(s)
|
|
369
|
+
(s) => chalk2.hex(THEME.warning)(s),
|
|
370
|
+
(s) => chalk2.hex(THEME.primary)(s)
|
|
320
371
|
];
|
|
321
372
|
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
322
|
-
const line = `${
|
|
373
|
+
const line = `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
323
374
|
if (_cooking) _cooking.log(line);
|
|
324
375
|
else console.warn(line);
|
|
325
376
|
};
|
|
326
377
|
success = (m) => {
|
|
327
378
|
const colors = [
|
|
328
|
-
(s) =>
|
|
329
|
-
(s) =>
|
|
330
|
-
(s) => chalk3.hex("#43A047")(s),
|
|
331
|
-
(s) => chalk3.hex("#81C784")(s),
|
|
332
|
-
(s) => chalk3.hex("#2E7D32")(s)
|
|
379
|
+
(s) => chalk2.hex(THEME.success)(s),
|
|
380
|
+
(s) => chalk2.hex(THEME.info)(s)
|
|
333
381
|
];
|
|
334
382
|
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
335
|
-
output(`${
|
|
383
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`);
|
|
336
384
|
};
|
|
337
385
|
}
|
|
338
386
|
});
|
|
@@ -1186,7 +1234,7 @@ var init_output = __esm({
|
|
|
1186
1234
|
});
|
|
1187
1235
|
|
|
1188
1236
|
// src/cli/box.ts
|
|
1189
|
-
import
|
|
1237
|
+
import chalk3 from "chalk";
|
|
1190
1238
|
function maxWidth() {
|
|
1191
1239
|
return Math.min(process.stdout.columns || 80, 62);
|
|
1192
1240
|
}
|
|
@@ -1195,51 +1243,45 @@ function padRight(text, w) {
|
|
|
1195
1243
|
if (visible >= w) return text;
|
|
1196
1244
|
return text + " ".repeat(w - visible);
|
|
1197
1245
|
}
|
|
1198
|
-
function stripAnsi(s) {
|
|
1199
|
-
return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
1200
|
-
}
|
|
1201
1246
|
function boxTop(w) {
|
|
1202
1247
|
const inner = w - 4;
|
|
1203
|
-
return " " +
|
|
1248
|
+
return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
|
|
1204
1249
|
}
|
|
1205
1250
|
function boxBot(w) {
|
|
1206
1251
|
const inner = w - 4;
|
|
1207
|
-
return " " +
|
|
1252
|
+
return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
|
|
1208
1253
|
}
|
|
1209
1254
|
function boxLine(w, text) {
|
|
1210
1255
|
const inner = w - 4;
|
|
1211
1256
|
const padded = padRight(text, inner);
|
|
1212
|
-
return " " +
|
|
1257
|
+
return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
|
|
1213
1258
|
}
|
|
1214
1259
|
function boxSep(w) {
|
|
1215
1260
|
const inner = w - 4;
|
|
1216
|
-
return " " +
|
|
1261
|
+
return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
|
|
1217
1262
|
}
|
|
1218
1263
|
function boxRow(w, label, value) {
|
|
1219
1264
|
const inner = w - 4;
|
|
1220
|
-
const labelPlain = stripAnsi(
|
|
1265
|
+
const labelPlain = stripAnsi(chalk3.bold(label));
|
|
1221
1266
|
const visible = labelPlain.length + 1 + value.length;
|
|
1222
1267
|
if (visible > inner) {
|
|
1223
1268
|
const avail = inner - labelPlain.length - 2;
|
|
1224
1269
|
const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
|
|
1225
|
-
return " " +
|
|
1270
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
|
|
1226
1271
|
}
|
|
1227
1272
|
const right = inner - labelPlain.length - 1 - value.length;
|
|
1228
|
-
return " " +
|
|
1273
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
|
|
1229
1274
|
}
|
|
1230
|
-
var B, G;
|
|
1231
1275
|
var init_box = __esm({
|
|
1232
1276
|
"src/cli/box.ts"() {
|
|
1233
1277
|
"use strict";
|
|
1234
|
-
|
|
1235
|
-
G = chalk4.hex("#C4953A");
|
|
1278
|
+
init_theme();
|
|
1236
1279
|
}
|
|
1237
1280
|
});
|
|
1238
1281
|
|
|
1239
1282
|
// src/exporter/summary.ts
|
|
1240
1283
|
import fs3 from "fs/promises";
|
|
1241
1284
|
import path4 from "path";
|
|
1242
|
-
import chalk5 from "chalk";
|
|
1243
1285
|
async function printSummary(exporter) {
|
|
1244
1286
|
const w = maxWidth();
|
|
1245
1287
|
const isSmall = w < 50;
|
|
@@ -1261,17 +1303,17 @@ async function printSummary(exporter) {
|
|
|
1261
1303
|
count("data"),
|
|
1262
1304
|
count("subpages")
|
|
1263
1305
|
]);
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
const C2 =
|
|
1267
|
-
const Y =
|
|
1268
|
-
const O =
|
|
1269
|
-
const Br =
|
|
1270
|
-
const Gr =
|
|
1271
|
-
const Gn =
|
|
1306
|
+
const G = ui.primary;
|
|
1307
|
+
const G2 = ui.primarySoft;
|
|
1308
|
+
const C2 = ui.secondary;
|
|
1309
|
+
const Y = ui.accent;
|
|
1310
|
+
const O = ui.warning;
|
|
1311
|
+
const Br = ui.info;
|
|
1312
|
+
const Gr = ui.muted;
|
|
1313
|
+
const Gn = ui.success;
|
|
1272
1314
|
const entries = [
|
|
1273
|
-
["styles/", styles, "CSS",
|
|
1274
|
-
["scripts/vendor/", vendor, "JS vendor",
|
|
1315
|
+
["styles/", styles, "CSS", G],
|
|
1316
|
+
["scripts/vendor/", vendor, "JS vendor", G2],
|
|
1275
1317
|
["scripts/modules/", scripts, "JS modules", C2],
|
|
1276
1318
|
["assets/images/", imgs, "images", Y],
|
|
1277
1319
|
["assets/videos/", videos, "videos", O],
|
|
@@ -1283,23 +1325,23 @@ async function printSummary(exporter) {
|
|
|
1283
1325
|
console.log("");
|
|
1284
1326
|
if (!isSmall) {
|
|
1285
1327
|
console.log(boxTop(w));
|
|
1286
|
-
console.log(boxLine(w,
|
|
1328
|
+
console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
|
|
1287
1329
|
console.log(boxSep(w));
|
|
1288
1330
|
} else {
|
|
1289
|
-
console.log(
|
|
1331
|
+
console.log(ui.text.bold(" Export Summary:"));
|
|
1290
1332
|
}
|
|
1291
1333
|
for (const [label, cnt, type, color] of entries) {
|
|
1292
1334
|
if (cnt === 0) continue;
|
|
1293
1335
|
const inner = w - 4;
|
|
1294
1336
|
const l = label.padEnd(16);
|
|
1295
1337
|
const c = String(cnt).padStart(3);
|
|
1296
|
-
const rowText = `${color(l)}${
|
|
1338
|
+
const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
|
|
1297
1339
|
const visible = 16 + 3 + 2 + type.length;
|
|
1298
1340
|
const pad = Math.max(0, inner - visible);
|
|
1299
1341
|
if (isSmall) {
|
|
1300
|
-
console.log(` ${color(label)} ${
|
|
1342
|
+
console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
|
|
1301
1343
|
} else {
|
|
1302
|
-
console.log(" " +
|
|
1344
|
+
console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
|
|
1303
1345
|
}
|
|
1304
1346
|
}
|
|
1305
1347
|
if (!isSmall) {
|
|
@@ -1309,37 +1351,584 @@ async function printSummary(exporter) {
|
|
|
1309
1351
|
const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
|
|
1310
1352
|
if (!isSmall) {
|
|
1311
1353
|
console.log(boxTop(w));
|
|
1312
|
-
console.log(boxLine(w,
|
|
1354
|
+
console.log(boxLine(w, ui.text.bold(" To serve locally")));
|
|
1313
1355
|
console.log(boxSep(w));
|
|
1314
1356
|
const inner = w - 6;
|
|
1315
1357
|
const cmdLen = cdCmd.length;
|
|
1316
1358
|
const pad = Math.max(0, inner - cmdLen);
|
|
1317
|
-
console.log(" " +
|
|
1359
|
+
console.log(" " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502"));
|
|
1318
1360
|
console.log(boxBot(w));
|
|
1319
1361
|
} else {
|
|
1320
|
-
console.log(
|
|
1321
|
-
console.log(` ${
|
|
1362
|
+
console.log(ui.text.bold(" To serve locally:"));
|
|
1363
|
+
console.log(` ${G(cdCmd)}`);
|
|
1322
1364
|
}
|
|
1323
1365
|
console.log("");
|
|
1324
|
-
console.log(
|
|
1366
|
+
console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
|
|
1325
1367
|
console.log("");
|
|
1326
1368
|
}
|
|
1327
1369
|
var init_summary = __esm({
|
|
1328
1370
|
"src/exporter/summary.ts"() {
|
|
1329
1371
|
"use strict";
|
|
1330
1372
|
init_box();
|
|
1373
|
+
init_theme();
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
// src/cli/select.ts
|
|
1378
|
+
import readline from "readline";
|
|
1379
|
+
import { stdin, stdout } from "process";
|
|
1380
|
+
async function select(question, options, defaultIndex = 0) {
|
|
1381
|
+
const isTTY = stdin.isTTY && stdout.isTTY;
|
|
1382
|
+
if (!isTTY) {
|
|
1383
|
+
return fallbackPrompt(question, options, defaultIndex);
|
|
1384
|
+
}
|
|
1385
|
+
return arrowSelect(question, options, defaultIndex);
|
|
1386
|
+
}
|
|
1387
|
+
async function arrowSelect(question, options, defaultIndex) {
|
|
1388
|
+
return new Promise((resolve) => {
|
|
1389
|
+
const firstEnabled = options.findIndex((option) => !option.disabled);
|
|
1390
|
+
let selected = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
|
|
1391
|
+
if (selected < 0) selected = 0;
|
|
1392
|
+
const move = (direction) => {
|
|
1393
|
+
let next = selected + direction;
|
|
1394
|
+
while (next >= 0 && next < options.length) {
|
|
1395
|
+
if (!options[next].disabled) {
|
|
1396
|
+
selected = next;
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
next += direction;
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const render = (initial = false) => {
|
|
1403
|
+
if (!initial) {
|
|
1404
|
+
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1405
|
+
stdout.write("\x1B[J");
|
|
1406
|
+
}
|
|
1407
|
+
console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}`);
|
|
1408
|
+
for (let i = 0; i < options.length; i++) {
|
|
1409
|
+
if (options[i].disabled) {
|
|
1410
|
+
console.log(` ${ui.muted(stripAnsi(options[i].label))}`);
|
|
1411
|
+
} else if (i === selected) {
|
|
1412
|
+
console.log(` ${ui.primary(">")} ${ui.text.bold(options[i].label)}`);
|
|
1413
|
+
} else {
|
|
1414
|
+
console.log(` ${ui.muted(options[i].label)}`);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
render(true);
|
|
1419
|
+
readline.emitKeypressEvents(stdin);
|
|
1420
|
+
stdin.setRawMode(true);
|
|
1421
|
+
const onKeypress = (_str, key) => {
|
|
1422
|
+
if (!key) return;
|
|
1423
|
+
if (key.name === "up" && selected > 0) {
|
|
1424
|
+
move(-1);
|
|
1425
|
+
render();
|
|
1426
|
+
} else if (key.name === "down" && selected < options.length - 1) {
|
|
1427
|
+
move(1);
|
|
1428
|
+
render();
|
|
1429
|
+
} else if (key.name === "return") {
|
|
1430
|
+
stdin.setRawMode(false);
|
|
1431
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1432
|
+
stdin.pause();
|
|
1433
|
+
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1434
|
+
stdout.write("\x1B[J");
|
|
1435
|
+
console.log(
|
|
1436
|
+
` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(stripAnsi(options[selected].label))}
|
|
1437
|
+
`
|
|
1438
|
+
);
|
|
1439
|
+
resolve(options[selected].value);
|
|
1440
|
+
} else if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
1441
|
+
stdin.setRawMode(false);
|
|
1442
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1443
|
+
process.exit(0);
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
stdin.resume();
|
|
1447
|
+
stdin.on("keypress", onKeypress);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
async function fallbackPrompt(question, options, defaultIndex) {
|
|
1451
|
+
return new Promise((resolve) => {
|
|
1452
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
1453
|
+
const firstEnabled = options.findIndex((option) => !option.disabled);
|
|
1454
|
+
const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
|
|
1455
|
+
console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}
|
|
1456
|
+
`);
|
|
1457
|
+
for (let i = 0; i < options.length; i++) {
|
|
1458
|
+
const marker = i === enabledDefault ? ui.success(" \u25C6") : " ";
|
|
1459
|
+
const label = options[i].disabled ? ui.muted(stripAnsi(options[i].label)) : ui.text(options[i].label);
|
|
1460
|
+
console.log(` ${ui.muted(`[${i + 1}]`)}${marker} ${label}`);
|
|
1461
|
+
}
|
|
1462
|
+
console.log("");
|
|
1463
|
+
const def = String(enabledDefault + 1);
|
|
1464
|
+
const ask = () => {
|
|
1465
|
+
rl.question(
|
|
1466
|
+
` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
|
|
1467
|
+
(answer) => {
|
|
1468
|
+
const trimmed = answer.trim();
|
|
1469
|
+
if (!trimmed) {
|
|
1470
|
+
rl.close();
|
|
1471
|
+
const label = stripAnsi(options[enabledDefault].label);
|
|
1472
|
+
console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
|
|
1473
|
+
`);
|
|
1474
|
+
resolve(options[enabledDefault].value);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const idx = parseInt(trimmed, 10);
|
|
1478
|
+
if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
|
|
1479
|
+
rl.close();
|
|
1480
|
+
const label = stripAnsi(options[idx - 1].label);
|
|
1481
|
+
console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
|
|
1482
|
+
`);
|
|
1483
|
+
resolve(options[idx - 1].value);
|
|
1484
|
+
} else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
|
|
1485
|
+
console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
|
|
1486
|
+
`);
|
|
1487
|
+
ask();
|
|
1488
|
+
} else {
|
|
1489
|
+
console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
|
|
1490
|
+
`);
|
|
1491
|
+
ask();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
);
|
|
1495
|
+
};
|
|
1496
|
+
ask();
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
var init_select = __esm({
|
|
1500
|
+
"src/cli/select.ts"() {
|
|
1501
|
+
"use strict";
|
|
1502
|
+
init_theme();
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
// src/ai/prompt-assistant.ts
|
|
1507
|
+
import fs4 from "fs/promises";
|
|
1508
|
+
import path5 from "path";
|
|
1509
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1510
|
+
import { spawn } from "child_process";
|
|
1511
|
+
import chalk4 from "chalk";
|
|
1512
|
+
async function runAiPromptAssistant(exporter) {
|
|
1513
|
+
if (!stdin2.isTTY || !stdout2.isTTY) return;
|
|
1514
|
+
const targetOptions = [
|
|
1515
|
+
...TARGETS.map((target2) => ({ label: target2.label, value: target2.id })),
|
|
1516
|
+
{ label: "Customize with AI - BETA in development", value: "custom-ai", disabled: true },
|
|
1517
|
+
{ label: "Skip AI prompt", value: "skip" }
|
|
1518
|
+
];
|
|
1519
|
+
console.log("");
|
|
1520
|
+
const targetId = await select("AI Prompt Assistant BETA: choose target stack", targetOptions, 0);
|
|
1521
|
+
if (targetId === "skip") return;
|
|
1522
|
+
const aiToolId = await select(
|
|
1523
|
+
"Choose the AI coding tool",
|
|
1524
|
+
AI_TOOLS.map((tool) => ({ label: tool.label, value: tool.id })),
|
|
1525
|
+
0
|
|
1526
|
+
);
|
|
1527
|
+
const goalId = await select(
|
|
1528
|
+
"Choose the conversion situation",
|
|
1529
|
+
GOALS.map((goal2) => ({ label: goal2.label, value: goal2.id })),
|
|
1530
|
+
0
|
|
1531
|
+
);
|
|
1532
|
+
const target = TARGETS.find((item) => item.id === targetId) || TARGETS[0];
|
|
1533
|
+
const aiTool = AI_TOOLS.find((item) => item.id === aiToolId) || AI_TOOLS[0];
|
|
1534
|
+
const goal = GOALS.find((item) => item.id === goalId) || GOALS[0];
|
|
1535
|
+
const facts = await collectExportFacts(exporter);
|
|
1536
|
+
const prompt = buildConversionPrompt(target, aiTool, goal, facts);
|
|
1537
|
+
const aiDir = path5.join(exporter.outDir, "ai");
|
|
1538
|
+
const promptPath = path5.join(aiDir, `${aiTool.id}-${target.id}-${goal.id}-prompt.md`);
|
|
1539
|
+
await fs4.mkdir(aiDir, { recursive: true });
|
|
1540
|
+
await fs4.writeFile(promptPath, prompt, "utf-8");
|
|
1541
|
+
printPromptResult(promptPath, target, aiTool, goal);
|
|
1542
|
+
const copyChoice = await select(
|
|
1543
|
+
"Copy prompt to clipboard?",
|
|
1544
|
+
[
|
|
1545
|
+
{ label: "Copy prompt", value: "yes" },
|
|
1546
|
+
{ label: "No, keep file only", value: "no" }
|
|
1547
|
+
],
|
|
1548
|
+
0
|
|
1549
|
+
);
|
|
1550
|
+
if (copyChoice === "yes") {
|
|
1551
|
+
try {
|
|
1552
|
+
await copyToClipboard(prompt);
|
|
1553
|
+
console.log(` ${chalk4.green("\u2713")} ${chalk4.white.bold("Prompt copied to clipboard")}
|
|
1554
|
+
`);
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
console.log(
|
|
1557
|
+
` ${chalk4.yellow("!")} ${chalk4.yellow("Clipboard copy unavailable:")} ${chalk4.gray(error.message)}
|
|
1558
|
+
`
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
async function collectExportFacts(exporter) {
|
|
1564
|
+
const counts = {};
|
|
1565
|
+
const rootEntries = await safeReadDir(exporter.outDir);
|
|
1566
|
+
for (const item of IMPORTANT_DIRS) {
|
|
1567
|
+
counts[item] = item === "index.html" ? await exists(path5.join(exporter.outDir, item)) ? 1 : 0 : await countEntries(path5.join(exporter.outDir, item));
|
|
1568
|
+
}
|
|
1569
|
+
return {
|
|
1570
|
+
sourceUrl: exporter.siteUrl,
|
|
1571
|
+
outputDir: exporter.outDir,
|
|
1572
|
+
platformName: exporter.platform.displayName,
|
|
1573
|
+
rootEntries,
|
|
1574
|
+
counts
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
async function exists(filePath) {
|
|
1578
|
+
try {
|
|
1579
|
+
await fs4.access(filePath);
|
|
1580
|
+
return true;
|
|
1581
|
+
} catch {
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
async function safeReadDir(dir) {
|
|
1586
|
+
try {
|
|
1587
|
+
return (await fs4.readdir(dir)).sort();
|
|
1588
|
+
} catch {
|
|
1589
|
+
return [];
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
async function countEntries(dir) {
|
|
1593
|
+
try {
|
|
1594
|
+
return (await fs4.readdir(dir)).length;
|
|
1595
|
+
} catch {
|
|
1596
|
+
return 0;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
function buildConversionPrompt(target, aiTool, goal, facts) {
|
|
1600
|
+
const rootEntries = facts.rootEntries.length ? facts.rootEntries.join(", ") : "No root entries detected";
|
|
1601
|
+
const exportDir = quoteForPrompt(facts.outputDir);
|
|
1602
|
+
const projectDir = quoteForPrompt(path5.join(facts.outputDir, target.projectDir));
|
|
1603
|
+
const scaffoldCommand = `cd ${exportDir}; ${target.scaffold}`;
|
|
1604
|
+
const sourceAssets = quoteForPrompt(path5.join(facts.outputDir, "assets", "*"));
|
|
1605
|
+
const destinationAssets = quoteForPrompt(
|
|
1606
|
+
path5.join(facts.outputDir, target.projectDir, target.staticDir, "assets")
|
|
1607
|
+
);
|
|
1608
|
+
const windowsAssetCopyCommand = `New-Item -ItemType Directory -Force -Path ${destinationAssets}; Copy-Item -Path ${sourceAssets} -Destination ${destinationAssets} -Recurse -Force`;
|
|
1609
|
+
const unixSourceAssets = quoteForPrompt(toPosixPath(path5.join(facts.outputDir, "assets")));
|
|
1610
|
+
const unixDestinationParent = quoteForPrompt(
|
|
1611
|
+
toPosixPath(path5.join(facts.outputDir, target.projectDir, target.staticDir))
|
|
1612
|
+
);
|
|
1613
|
+
const unixAssetCopyCommand = `mkdir -p ${unixDestinationParent} && cp -R ${unixSourceAssets} ${unixDestinationParent}/assets`;
|
|
1614
|
+
const directoryLines = IMPORTANT_DIRS.map(
|
|
1615
|
+
(dir) => `Inspect ${dir}: ${facts.counts[dir]} item(s) detected in the export.`
|
|
1616
|
+
);
|
|
1617
|
+
const promptLines = [
|
|
1618
|
+
`You are ${aiTool.agentName} working inside a local export created by Cooksite / Framer Export.`,
|
|
1619
|
+
"This brief was generated by the Cooksite AI Prompt Assistant BETA.",
|
|
1620
|
+
"Treat this beta prompt as a strict conversion checklist, not as permission to skip inspection.",
|
|
1621
|
+
"Your mission is to convert this exported static mirror into a clean production project.",
|
|
1622
|
+
`Target stack: ${target.stack}.`,
|
|
1623
|
+
`Selected AI coding tool: ${aiTool.displayName}.`,
|
|
1624
|
+
`Selected conversion situation: ${goal.label}.`,
|
|
1625
|
+
`Primary instruction for this situation: ${goal.instruction}`,
|
|
1626
|
+
`Main priority: ${goal.priority}`,
|
|
1627
|
+
`Source URL from the real export: ${facts.sourceUrl}`,
|
|
1628
|
+
`Detected platform from the real export: ${facts.platformName}`,
|
|
1629
|
+
`Export folder to inspect first: ${facts.outputDir}`,
|
|
1630
|
+
`Root entries actually present: ${rootEntries}`,
|
|
1631
|
+
`Create the converted project in: ${projectDir}`,
|
|
1632
|
+
`Recommended scaffold command: ${scaffoldCommand}`,
|
|
1633
|
+
`Expected important target files: ${target.entryFiles}`,
|
|
1634
|
+
`Routing guidance: ${target.routing}`,
|
|
1635
|
+
`Static assets destination for this stack: ${target.staticDir}/assets inside the converted project.`,
|
|
1636
|
+
`PowerShell command to copy exported assets after scaffolding: ${windowsAssetCopyCommand}`,
|
|
1637
|
+
`macOS/Linux command to copy exported assets after scaffolding: ${unixAssetCopyCommand}`,
|
|
1638
|
+
"Run the asset copy command; do not recreate, redownload, rename randomly, or replace real exported assets with placeholders.",
|
|
1639
|
+
"After copying assets, update every image, video, font, CSS url(), and script reference to the new local asset path.",
|
|
1640
|
+
"Do not rush. Take time to inspect the export before writing the final implementation.",
|
|
1641
|
+
"Do not invent brand names, copy, images, links, animations, colors, or sections.",
|
|
1642
|
+
"Use only real information found in index.html, CSS files, JavaScript files, data files, and assets.",
|
|
1643
|
+
"If a detail is missing, inspect more files instead of guessing.",
|
|
1644
|
+
"If a vendor script is minified or hard to understand, identify what behavior it provides before replacing it.",
|
|
1645
|
+
"Keep the final result professional, clean, responsive, and maintainable.",
|
|
1646
|
+
"Do not simplify the site because a section, animation, page, or layout is difficult.",
|
|
1647
|
+
"If something is hard, break it into smaller components and keep working until the result matches closely.",
|
|
1648
|
+
"For every exported page, aim for the closest possible pixel-perfect result: spacing, typography, images, viewport behavior, and motion.",
|
|
1649
|
+
"Do not merge multiple pages into one generic page unless the export proves they are duplicate routes.",
|
|
1650
|
+
"Do not replace complex exported sections with summaries, cards, screenshots, or placeholder blocks.",
|
|
1651
|
+
"If a page has visual depth, overlapping layers, sticky sections, galleries, or scroll effects, recreate those behaviors instead of flattening them.",
|
|
1652
|
+
"Start by listing the files and directories that matter for the conversion.",
|
|
1653
|
+
...directoryLines,
|
|
1654
|
+
"Read index.html fully enough to understand page structure, metadata, linked assets, and scripts.",
|
|
1655
|
+
"Read the CSS files that define layout, typography, responsive rules, and visual details.",
|
|
1656
|
+
"Inspect scripts/vendor only to understand required interactions; do not blindly copy huge vendor bundles.",
|
|
1657
|
+
"Inspect scripts/modules for page-specific logic, animations, sliders, menus, and dynamic behavior.",
|
|
1658
|
+
"Inspect data files for CMS-like content, page data, configuration, or serialized props.",
|
|
1659
|
+
"Inspect assets/images and preserve the real image files that are actually used.",
|
|
1660
|
+
"Inspect assets/fonts and preserve font loading if the design depends on custom fonts.",
|
|
1661
|
+
"Inspect subpages if it contains exported pages; map them to routes only when they represent real pages.",
|
|
1662
|
+
"If subpages contains pages, convert each meaningful page with its own route and page component.",
|
|
1663
|
+
"For each converted page, compare against the original exported HTML route and adjust until it is visually close.",
|
|
1664
|
+
"Create a clean project structure instead of dumping everything into one component.",
|
|
1665
|
+
"Separate global layout, page sections, shared components, data helpers, and styles.",
|
|
1666
|
+
"Use semantic HTML for headings, navigation, buttons, forms, sections, and footer content.",
|
|
1667
|
+
"Preserve the original hierarchy of visible content unless there is a clear bug to fix.",
|
|
1668
|
+
"Preserve real URLs and links, but convert local asset paths to the new project structure.",
|
|
1669
|
+
"Move static assets into the target framework public/static asset location when appropriate.",
|
|
1670
|
+
"Do not keep broken CDN references if a local exported asset already exists.",
|
|
1671
|
+
"Do not hardcode absolute machine paths into source files; use framework-relative public asset paths after copying.",
|
|
1672
|
+
"Keep original filenames when possible so CSS and content references remain traceable.",
|
|
1673
|
+
"Do not leave unused analytics, editor badges, platform badges, or export-only scripts in the new app.",
|
|
1674
|
+
"Replace platform-specific runtime code with native framework components when possible.",
|
|
1675
|
+
"Keep interactions that users can see: menus, hover states, forms, sliders, animations, and scroll effects.",
|
|
1676
|
+
"If an interaction is too complex, implement a clean equivalent and document the difference briefly.",
|
|
1677
|
+
"Keep responsive behavior for desktop, tablet, and mobile.",
|
|
1678
|
+
"Check layout at small widths and avoid fixed desktop-only dimensions unless the original requires them.",
|
|
1679
|
+
"Use CSS variables or a clear theme file for colors, spacing, radius, shadows, and typography.",
|
|
1680
|
+
"Name components after their role: Hero, Header, FeatureGrid, Gallery, Pricing, Footer, and similar real sections.",
|
|
1681
|
+
"Avoid generic placeholder components if the exported site has specific section meaning.",
|
|
1682
|
+
"Use TypeScript types where they make the content or component props clearer.",
|
|
1683
|
+
"Do not add unnecessary libraries unless they replace a real exported behavior cleanly.",
|
|
1684
|
+
"If you add a library, explain why it is needed and where it is used.",
|
|
1685
|
+
"Keep package.json scripts standard: dev, build, preview, lint when available.",
|
|
1686
|
+
"Keep the build reproducible from a fresh install.",
|
|
1687
|
+
"After implementing, run the install/build/typecheck commands available for the target project.",
|
|
1688
|
+
"Fix any build errors instead of leaving TODOs.",
|
|
1689
|
+
"Open the generated app locally if possible and compare against the exported index.html visually.",
|
|
1690
|
+
"When there are subpages, compare every generated route against its matching exported file.",
|
|
1691
|
+
"Verify that every visible image loads from the new project.",
|
|
1692
|
+
"Verify that font rendering is close to the export.",
|
|
1693
|
+
"Verify that navigation and internal links work.",
|
|
1694
|
+
"Verify that responsive breakpoints do not overlap or hide important content.",
|
|
1695
|
+
"Verify that there are no console errors caused by missing assets or copied platform scripts.",
|
|
1696
|
+
"Keep accessibility basics: alt text when inferable, keyboard-reachable controls, visible focus states.",
|
|
1697
|
+
"Keep SEO basics: title, meta description if present, canonical only if it is correct, Open Graph when present.",
|
|
1698
|
+
"Do not fabricate SEO copy; reuse existing metadata or ask for missing copy if necessary.",
|
|
1699
|
+
"Commit to a small number of high-quality files rather than many noisy fragments.",
|
|
1700
|
+
"If a section repeats, extract a reusable component and data array.",
|
|
1701
|
+
"If a section is unique, keep it simple and local to the page.",
|
|
1702
|
+
"Document important migration decisions in a short README inside the converted project.",
|
|
1703
|
+
"The README must mention the source export folder, target stack, setup command, and known limitations.",
|
|
1704
|
+
"Do not delete the original export folder.",
|
|
1705
|
+
"Do not modify unrelated files outside the new converted project unless required for setup.",
|
|
1706
|
+
"If the worktree has existing changes, avoid reverting or overwriting them.",
|
|
1707
|
+
"Before large edits, inspect first and explain the conversion plan briefly.",
|
|
1708
|
+
"Then implement the conversion step by step until the app builds.",
|
|
1709
|
+
"Final answer must summarize what was converted, where the new project lives, and which checks passed.",
|
|
1710
|
+
"If something could not be converted, state the exact file or behavior and the reason.",
|
|
1711
|
+
"Quality bar: the result should feel like a real hand-built production app, not an automated scrape or simplified demo."
|
|
1712
|
+
];
|
|
1713
|
+
return [
|
|
1714
|
+
`# ${aiTool.displayName} Conversion Prompt - ${target.label}`,
|
|
1715
|
+
"",
|
|
1716
|
+
"Status: BETA assistant output. Review the export carefully and follow the real files.",
|
|
1717
|
+
`Generated from real export: ${facts.outputDir}`,
|
|
1718
|
+
`AI tool: ${aiTool.displayName}`,
|
|
1719
|
+
`Target: ${target.label}`,
|
|
1720
|
+
`Situation: ${goal.label}`,
|
|
1721
|
+
"",
|
|
1722
|
+
promptLines.map((line, index) => `${String(index + 1).padStart(2, "0")}. ${line}`).join("\n"),
|
|
1723
|
+
""
|
|
1724
|
+
].join("\n");
|
|
1725
|
+
}
|
|
1726
|
+
function quoteForPrompt(value) {
|
|
1727
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
1728
|
+
}
|
|
1729
|
+
function toPosixPath(value) {
|
|
1730
|
+
return value.replace(/\\/g, "/");
|
|
1731
|
+
}
|
|
1732
|
+
async function copyToClipboard(text) {
|
|
1733
|
+
const commands = process.platform === "win32" ? [{ command: "clip", args: [] }] : process.platform === "darwin" ? [{ command: "pbcopy", args: [] }] : [
|
|
1734
|
+
{ command: "wl-copy", args: [] },
|
|
1735
|
+
{ command: "xclip", args: ["-selection", "clipboard"] },
|
|
1736
|
+
{ command: "xsel", args: ["--clipboard", "--input"] }
|
|
1737
|
+
];
|
|
1738
|
+
let lastError = null;
|
|
1739
|
+
for (const item of commands) {
|
|
1740
|
+
try {
|
|
1741
|
+
await pipeToCommand(item.command, item.args, text);
|
|
1742
|
+
return;
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
lastError = error;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
throw lastError || new Error("No clipboard command found");
|
|
1748
|
+
}
|
|
1749
|
+
function pipeToCommand(command, args, input) {
|
|
1750
|
+
return new Promise((resolve, reject) => {
|
|
1751
|
+
const child = spawn(command, args, { stdio: ["pipe", "ignore", "pipe"] });
|
|
1752
|
+
let stderr = "";
|
|
1753
|
+
let settled = false;
|
|
1754
|
+
const finish = (error) => {
|
|
1755
|
+
if (settled) return;
|
|
1756
|
+
settled = true;
|
|
1757
|
+
if (error) reject(error);
|
|
1758
|
+
else resolve();
|
|
1759
|
+
};
|
|
1760
|
+
child.stderr?.on("data", (chunk) => {
|
|
1761
|
+
stderr += chunk.toString();
|
|
1762
|
+
});
|
|
1763
|
+
child.on("error", finish);
|
|
1764
|
+
child.on("close", (code) => {
|
|
1765
|
+
if (code === 0) {
|
|
1766
|
+
finish();
|
|
1767
|
+
} else {
|
|
1768
|
+
finish(new Error(`${command} exited with ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
child.stdin?.end(input);
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
function printPromptResult(promptPath, target, aiTool, goal) {
|
|
1775
|
+
const w = maxWidth();
|
|
1776
|
+
const isSmall = w < 50;
|
|
1777
|
+
const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
|
|
1778
|
+
console.log("");
|
|
1779
|
+
if (!isSmall) {
|
|
1780
|
+
console.log(boxTop(w));
|
|
1781
|
+
console.log(boxLine(w, chalk4.bold.white(" AI Prompt Ready - BETA")));
|
|
1782
|
+
console.log(boxSep(w));
|
|
1783
|
+
console.log(boxLine(w, ` Tool: ${aiTool.displayName}`));
|
|
1784
|
+
console.log(boxLine(w, ` Stack: ${target.label}`));
|
|
1785
|
+
console.log(boxLine(w, ` Situation: ${goal.label}`));
|
|
1786
|
+
console.log(boxLine(w, ` File: ${relPath}`));
|
|
1787
|
+
console.log(boxBot(w));
|
|
1788
|
+
} else {
|
|
1789
|
+
console.log(chalk4.bold.white(" AI Prompt Ready - BETA"));
|
|
1790
|
+
console.log(` Tool: ${chalk4.hex("#D4A017")(aiTool.displayName)}`);
|
|
1791
|
+
console.log(` Stack: ${chalk4.hex("#D4A017")(target.label)}`);
|
|
1792
|
+
console.log(` Situation: ${chalk4.hex("#D4A017")(goal.label)}`);
|
|
1793
|
+
console.log(` File: ${chalk4.hex("#D4A017")(relPath)}`);
|
|
1794
|
+
}
|
|
1795
|
+
console.log("");
|
|
1796
|
+
}
|
|
1797
|
+
var IMPORTANT_DIRS, AI_TOOLS, TARGETS, GOALS;
|
|
1798
|
+
var init_prompt_assistant = __esm({
|
|
1799
|
+
"src/ai/prompt-assistant.ts"() {
|
|
1800
|
+
"use strict";
|
|
1801
|
+
init_select();
|
|
1802
|
+
init_box();
|
|
1803
|
+
IMPORTANT_DIRS = [
|
|
1804
|
+
"index.html",
|
|
1805
|
+
"styles",
|
|
1806
|
+
"scripts/vendor",
|
|
1807
|
+
"scripts/modules",
|
|
1808
|
+
"assets/images",
|
|
1809
|
+
"assets/videos",
|
|
1810
|
+
"assets/fonts",
|
|
1811
|
+
"assets/misc",
|
|
1812
|
+
"data",
|
|
1813
|
+
"subpages"
|
|
1814
|
+
];
|
|
1815
|
+
AI_TOOLS = [
|
|
1816
|
+
{
|
|
1817
|
+
id: "claude-code",
|
|
1818
|
+
label: "Claude Code",
|
|
1819
|
+
displayName: "Claude Code",
|
|
1820
|
+
agentName: "Claude Code"
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
id: "codex",
|
|
1824
|
+
label: "Codex",
|
|
1825
|
+
displayName: "Codex",
|
|
1826
|
+
agentName: "Codex coding agent"
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
id: "opencode",
|
|
1830
|
+
label: "OpenCode",
|
|
1831
|
+
displayName: "OpenCode",
|
|
1832
|
+
agentName: "OpenCode"
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
id: "other-ai",
|
|
1836
|
+
label: "Other AI coding agent",
|
|
1837
|
+
displayName: "Other AI",
|
|
1838
|
+
agentName: "AI coding agent"
|
|
1839
|
+
}
|
|
1840
|
+
];
|
|
1841
|
+
TARGETS = [
|
|
1842
|
+
{
|
|
1843
|
+
id: "react-vite",
|
|
1844
|
+
label: "React + Vite + TypeScript",
|
|
1845
|
+
stack: "React 18/19, TypeScript, Vite, CSS modules or plain CSS",
|
|
1846
|
+
projectDir: "converted-react-vite",
|
|
1847
|
+
staticDir: "public",
|
|
1848
|
+
scaffold: "npm create vite@latest converted-react-vite -- --template react-ts",
|
|
1849
|
+
entryFiles: "src/main.tsx, src/App.tsx, src/components/*, src/styles/*",
|
|
1850
|
+
routing: "Use React Router only if multiple exported pages exist in subpages/."
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
id: "nextjs-app-router",
|
|
1854
|
+
label: "Next.js App Router",
|
|
1855
|
+
stack: "Next.js App Router, TypeScript, React Server Components where useful",
|
|
1856
|
+
projectDir: "converted-nextjs",
|
|
1857
|
+
staticDir: "public",
|
|
1858
|
+
scaffold: "npx create-next-app@latest converted-nextjs --ts --app --eslint",
|
|
1859
|
+
entryFiles: "app/page.tsx, app/layout.tsx, components/*, public/*",
|
|
1860
|
+
routing: "Map exported subpages to app routes and keep shared layout code reusable."
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
id: "vue-vite",
|
|
1864
|
+
label: "Vue + Vite + TypeScript",
|
|
1865
|
+
stack: "Vue 3, TypeScript, Vite, single-file components",
|
|
1866
|
+
projectDir: "converted-vue-vite",
|
|
1867
|
+
staticDir: "public",
|
|
1868
|
+
scaffold: "npm create vite@latest converted-vue-vite -- --template vue-ts",
|
|
1869
|
+
entryFiles: "src/main.ts, src/App.vue, src/components/*.vue, src/styles/*",
|
|
1870
|
+
routing: "Use Vue Router only if multiple exported pages exist in subpages/."
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
id: "sveltekit",
|
|
1874
|
+
label: "SvelteKit",
|
|
1875
|
+
stack: "SvelteKit, TypeScript, componentized routes and assets",
|
|
1876
|
+
projectDir: "converted-sveltekit",
|
|
1877
|
+
staticDir: "static",
|
|
1878
|
+
scaffold: "npm create svelte@latest converted-sveltekit",
|
|
1879
|
+
entryFiles: "src/routes/+page.svelte, src/lib/components/*, static/*",
|
|
1880
|
+
routing: "Create SvelteKit routes for meaningful exported pages when subpages/ exists."
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
id: "astro",
|
|
1884
|
+
label: "Astro",
|
|
1885
|
+
stack: "Astro, TypeScript, island components only where interactivity is needed",
|
|
1886
|
+
projectDir: "converted-astro",
|
|
1887
|
+
staticDir: "public",
|
|
1888
|
+
scaffold: "npm create astro@latest converted-astro",
|
|
1889
|
+
entryFiles: "src/pages/index.astro, src/components/*, public/*",
|
|
1890
|
+
routing: "Use Astro pages for exported subpages and avoid unnecessary client JavaScript."
|
|
1891
|
+
}
|
|
1892
|
+
];
|
|
1893
|
+
GOALS = [
|
|
1894
|
+
{
|
|
1895
|
+
id: "clean-rebuild",
|
|
1896
|
+
label: "Clean professional rebuild",
|
|
1897
|
+
instruction: "Rebuild the export as clean production code, not as a one-file HTML clone, while preserving the full page experience.",
|
|
1898
|
+
priority: "Readable structure, maintainability, complete pages, and faithful visual result."
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
id: "pixel-perfect",
|
|
1902
|
+
label: "Pixel-perfect visual migration",
|
|
1903
|
+
instruction: "Prioritize pixel-perfect fidelity before refactoring anything aggressively, even when the layout is difficult.",
|
|
1904
|
+
priority: "Spacing, typography, responsive behavior, colors, media, animations, and page-by-page fidelity."
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
id: "component-system",
|
|
1908
|
+
label: "Reusable component system",
|
|
1909
|
+
instruction: "Extract repeated UI blocks into reusable components with clean props without simplifying the original pages.",
|
|
1910
|
+
priority: "Components, layout primitives, naming, future editability, and complete section coverage."
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
id: "performance-seo",
|
|
1914
|
+
label: "Performance and SEO rebuild",
|
|
1915
|
+
instruction: "Rebuild the site while reducing unused vendor code and improving SEO basics without losing visual fidelity.",
|
|
1916
|
+
priority: "Fast loading, semantic markup, metadata, accessibility, asset hygiene, and faithful pages."
|
|
1917
|
+
}
|
|
1918
|
+
];
|
|
1331
1919
|
}
|
|
1332
1920
|
});
|
|
1333
1921
|
|
|
1334
1922
|
// src/cli/cooking.ts
|
|
1335
|
-
import
|
|
1336
|
-
var SHINE_WIDTH, FRAME_INTERVAL,
|
|
1923
|
+
import chalk5 from "chalk";
|
|
1924
|
+
var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, CookingSpinner;
|
|
1337
1925
|
var init_cooking = __esm({
|
|
1338
1926
|
"src/cli/cooking.ts"() {
|
|
1339
1927
|
"use strict";
|
|
1928
|
+
init_theme();
|
|
1340
1929
|
SHINE_WIDTH = 10;
|
|
1341
|
-
FRAME_INTERVAL =
|
|
1342
|
-
|
|
1930
|
+
FRAME_INTERVAL = 80;
|
|
1931
|
+
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1343
1932
|
CookingSpinner = class {
|
|
1344
1933
|
interval = null;
|
|
1345
1934
|
frame = 0;
|
|
@@ -1377,11 +1966,11 @@ var init_cooking = __esm({
|
|
|
1377
1966
|
}
|
|
1378
1967
|
draw() {
|
|
1379
1968
|
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 ${
|
|
1969
|
+
const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
|
|
1970
|
+
const shimmer = this.renderShimmer("f-export");
|
|
1971
|
+
const frameStr = ui.primary(spinner);
|
|
1972
|
+
const phaseStr = this.phase ? ` ${ui.muted(this.limitLen(this.phase, 52))}` : "";
|
|
1973
|
+
process.stdout.write(`\r\x1B[2K ${frameStr} ${shimmer}${phaseStr}`);
|
|
1385
1974
|
}
|
|
1386
1975
|
renderShimmer(text) {
|
|
1387
1976
|
let result = "";
|
|
@@ -1391,9 +1980,9 @@ var init_cooking = __esm({
|
|
|
1391
1980
|
if (dist < SHINE_WIDTH) {
|
|
1392
1981
|
const t = 1 - dist / SHINE_WIDTH;
|
|
1393
1982
|
const g = Math.floor(160 + t * 95);
|
|
1394
|
-
result +=
|
|
1983
|
+
result += chalk5.rgb(250, Math.min(255, g), Math.min(255, Math.floor(g * 0.75)))(text[i]);
|
|
1395
1984
|
} else {
|
|
1396
|
-
result +=
|
|
1985
|
+
result += ui.muted(text[i]);
|
|
1397
1986
|
}
|
|
1398
1987
|
}
|
|
1399
1988
|
return result;
|
|
@@ -1412,9 +2001,9 @@ __export(exporter_exports, {
|
|
|
1412
2001
|
FramerExporter: () => FramerExporter,
|
|
1413
2002
|
deriveOutputName: () => deriveOutputName
|
|
1414
2003
|
});
|
|
1415
|
-
import
|
|
1416
|
-
import
|
|
1417
|
-
import
|
|
2004
|
+
import fs5 from "fs/promises";
|
|
2005
|
+
import path6 from "path";
|
|
2006
|
+
import chalk6 from "chalk";
|
|
1418
2007
|
function deriveOutputName(url, platformName) {
|
|
1419
2008
|
try {
|
|
1420
2009
|
const parsed = new URL(url);
|
|
@@ -1446,8 +2035,10 @@ var init_exporter = __esm({
|
|
|
1446
2035
|
init_download2();
|
|
1447
2036
|
init_output();
|
|
1448
2037
|
init_summary();
|
|
2038
|
+
init_prompt_assistant();
|
|
1449
2039
|
init_platforms();
|
|
1450
2040
|
init_cooking();
|
|
2041
|
+
init_theme();
|
|
1451
2042
|
FramerExporter = class {
|
|
1452
2043
|
siteUrl;
|
|
1453
2044
|
outDir;
|
|
@@ -1473,12 +2064,14 @@ var init_exporter = __esm({
|
|
|
1473
2064
|
}
|
|
1474
2065
|
}
|
|
1475
2066
|
async run(includeSubpages = false) {
|
|
1476
|
-
console.log(
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
info("
|
|
2067
|
+
console.log(`
|
|
2068
|
+
${ui.text.bold("Framer Export")} ${chip("mirror")} ${ui.muted("v4 pipeline")}
|
|
2069
|
+
`);
|
|
2070
|
+
info("Source : " + chalk6.underline(this.siteUrl));
|
|
2071
|
+
info("Output : " + ui.primary(this.outDir));
|
|
2072
|
+
info("Platform : " + ui.primary(this.platform.displayName));
|
|
1480
2073
|
if (includeSubpages) {
|
|
1481
|
-
info("Subpages : " +
|
|
2074
|
+
info("Subpages : " + ui.success("enabled"));
|
|
1482
2075
|
}
|
|
1483
2076
|
console.log("");
|
|
1484
2077
|
this.cooking = new CookingSpinner();
|
|
@@ -1496,7 +2089,7 @@ var init_exporter = __esm({
|
|
|
1496
2089
|
"data",
|
|
1497
2090
|
"subpages"
|
|
1498
2091
|
]) {
|
|
1499
|
-
await
|
|
2092
|
+
await fs5.mkdir(path6.join(this.outDir, d), { recursive: true });
|
|
1500
2093
|
}
|
|
1501
2094
|
log("Output directory structure created");
|
|
1502
2095
|
this.cooking.update("Fetching SSR HTML...");
|
|
@@ -1506,12 +2099,14 @@ var init_exporter = __esm({
|
|
|
1506
2099
|
this.ssrHTML = buf.toString("utf-8");
|
|
1507
2100
|
success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
|
|
1508
2101
|
} catch (e) {
|
|
1509
|
-
log(
|
|
2102
|
+
log(chalk6.red("Could not fetch SSR HTML: " + e.message));
|
|
1510
2103
|
}
|
|
1511
2104
|
const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
|
|
1512
2105
|
if (htmlDetected.name !== this.platform.name) {
|
|
1513
2106
|
this.platform = htmlDetected;
|
|
1514
|
-
log(
|
|
2107
|
+
log(
|
|
2108
|
+
"Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)"
|
|
2109
|
+
);
|
|
1515
2110
|
}
|
|
1516
2111
|
await launchAndCapture(this);
|
|
1517
2112
|
if (includeSubpages && this.page) {
|
|
@@ -1527,6 +2122,7 @@ var init_exporter = __esm({
|
|
|
1527
2122
|
console.log("");
|
|
1528
2123
|
success("Export complete!");
|
|
1529
2124
|
await printSummary(this);
|
|
2125
|
+
await runAiPromptAssistant(this);
|
|
1530
2126
|
}
|
|
1531
2127
|
async crawlSubpages() {
|
|
1532
2128
|
this.cooking?.update("Discovering sub-pages...");
|
|
@@ -1539,7 +2135,8 @@ var init_exporter = __esm({
|
|
|
1539
2135
|
const hrefs = /* @__PURE__ */ new Set();
|
|
1540
2136
|
for (const a of anchors) {
|
|
1541
2137
|
const href = a.href;
|
|
1542
|
-
if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#"))
|
|
2138
|
+
if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#"))
|
|
2139
|
+
continue;
|
|
1543
2140
|
try {
|
|
1544
2141
|
const u = new URL(href);
|
|
1545
2142
|
const h = u.hostname.replace(/^www\./, "");
|
|
@@ -1567,8 +2164,8 @@ var init_exporter = __esm({
|
|
|
1567
2164
|
});
|
|
1568
2165
|
const slug = this.deriveSlug(link, baseUrl);
|
|
1569
2166
|
const filename = slug + ".html";
|
|
1570
|
-
const filepath =
|
|
1571
|
-
await
|
|
2167
|
+
const filepath = path6.join(this.outDir, "subpages", filename);
|
|
2168
|
+
await fs5.writeFile(filepath, html, "utf-8");
|
|
1572
2169
|
log(" Saved: subpages/" + filename);
|
|
1573
2170
|
} catch (e) {
|
|
1574
2171
|
log(" Skipped " + link + ": " + e.message);
|
|
@@ -1590,142 +2187,37 @@ var init_exporter = __esm({
|
|
|
1590
2187
|
}
|
|
1591
2188
|
});
|
|
1592
2189
|
|
|
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
2190
|
// src/cli/setup.ts
|
|
1700
2191
|
var setup_exports = {};
|
|
1701
2192
|
__export(setup_exports, {
|
|
1702
2193
|
runSetup: () => runSetup
|
|
1703
2194
|
});
|
|
1704
2195
|
import readline2 from "readline/promises";
|
|
1705
|
-
import { stdin as
|
|
1706
|
-
import
|
|
2196
|
+
import { stdin as stdin3, stdout as stdout3 } from "process";
|
|
2197
|
+
import path7 from "path";
|
|
1707
2198
|
import { URL as URL4 } from "url";
|
|
1708
|
-
import
|
|
2199
|
+
import chalk7 from "chalk";
|
|
1709
2200
|
function drawHeader(title) {
|
|
1710
2201
|
const w = maxWidth();
|
|
1711
2202
|
if (w < 50) {
|
|
1712
2203
|
console.log(`
|
|
1713
|
-
${
|
|
2204
|
+
${bullet("\u25CF")} ${ui.text.bold(title)}`);
|
|
1714
2205
|
return;
|
|
1715
2206
|
}
|
|
1716
2207
|
console.log(boxTop(w));
|
|
1717
|
-
console.log(boxLine(w,
|
|
2208
|
+
console.log(boxLine(w, ui.text.bold(" " + title)));
|
|
1718
2209
|
console.log(boxBot(w));
|
|
1719
2210
|
console.log("");
|
|
1720
2211
|
}
|
|
1721
2212
|
async function runSetup(legacyMode = false) {
|
|
1722
2213
|
showBanner();
|
|
1723
|
-
console.log(
|
|
1724
|
-
console.log(
|
|
1725
|
-
|
|
2214
|
+
console.log(` ${ui.text.bold("Framer Export setup")} ${chip("interactive")}`);
|
|
2215
|
+
console.log(` ${ui.muted("Export Framer, Webflow, and Wix sites into a clean local mirror.")}
|
|
2216
|
+
`);
|
|
2217
|
+
const rl = readline2.createInterface({ input: stdin3, output: stdout3 });
|
|
1726
2218
|
const ask = async (question, defaultVal) => {
|
|
1727
|
-
const suffix = defaultVal ?
|
|
1728
|
-
const prompt = ` ${
|
|
2219
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2220
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1729
2221
|
const answer = await rl.question(prompt);
|
|
1730
2222
|
return answer.trim() || defaultVal || "";
|
|
1731
2223
|
};
|
|
@@ -1737,42 +2229,42 @@ async function runSetup(legacyMode = false) {
|
|
|
1737
2229
|
new URL4(input);
|
|
1738
2230
|
siteUrl = input;
|
|
1739
2231
|
} catch {
|
|
1740
|
-
console.log(` ${
|
|
2232
|
+
console.log(` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
|
|
1741
2233
|
`);
|
|
1742
2234
|
}
|
|
1743
2235
|
}
|
|
1744
|
-
console.log(` ${
|
|
2236
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk7.underline(siteUrl)}
|
|
1745
2237
|
`);
|
|
1746
2238
|
drawHeader("Step 2 : Platform");
|
|
1747
2239
|
const detected = detectPlatform(siteUrl);
|
|
1748
2240
|
let platformName;
|
|
1749
2241
|
if (legacyMode) {
|
|
1750
|
-
console.log(` ${
|
|
2242
|
+
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
1751
2243
|
const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
|
|
1752
2244
|
platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
|
|
1753
|
-
console.log(` ${
|
|
2245
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
1754
2246
|
`);
|
|
1755
2247
|
} else {
|
|
1756
2248
|
rl.close();
|
|
1757
2249
|
const platforms = [
|
|
1758
|
-
{ label: `Framer${detected.name === "framer" ?
|
|
1759
|
-
{ label: `Webflow${detected.name === "webflow" ?
|
|
1760
|
-
{ label: `Wix${detected.name === "wix" ?
|
|
2250
|
+
{ label: `Framer${detected.name === "framer" ? chalk7.gray(" (detected)") : ""}`, value: "framer" },
|
|
2251
|
+
{ label: `Webflow${detected.name === "webflow" ? chalk7.gray(" (detected)") : ""}`, value: "webflow" },
|
|
2252
|
+
{ label: `Wix${detected.name === "wix" ? chalk7.gray(" (detected)") : ""}`, value: "wix" }
|
|
1761
2253
|
];
|
|
1762
2254
|
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
1763
2255
|
platformName = await select("Select platform", platforms, Math.max(0, defaultIdx));
|
|
1764
2256
|
}
|
|
1765
|
-
const rl2 = legacyMode ? rl : readline2.createInterface({ input:
|
|
2257
|
+
const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
|
|
1766
2258
|
const ask2 = async (question, defaultVal) => {
|
|
1767
|
-
const suffix = defaultVal ?
|
|
1768
|
-
const prompt = ` ${
|
|
2259
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2260
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1769
2261
|
const answer = await rl2.question(prompt);
|
|
1770
2262
|
return answer.trim() || defaultVal || "";
|
|
1771
2263
|
};
|
|
1772
2264
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
1773
2265
|
drawHeader("Step 3 : Output Directory");
|
|
1774
2266
|
const outDir = await ask2("Output directory", "./" + derivedName);
|
|
1775
|
-
console.log(` ${
|
|
2267
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
1776
2268
|
`);
|
|
1777
2269
|
drawHeader("Step 4 : Options");
|
|
1778
2270
|
let prettyPrint;
|
|
@@ -1781,15 +2273,15 @@ async function runSetup(legacyMode = false) {
|
|
|
1781
2273
|
if (legacyMode) {
|
|
1782
2274
|
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
1783
2275
|
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
1784
|
-
console.log(` ${
|
|
2276
|
+
console.log(` ${ui.success("\u2713")} Pretty-print: ${prettyPrint ? ui.success("yes") : ui.error("no")}
|
|
1785
2277
|
`);
|
|
1786
2278
|
const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
|
|
1787
2279
|
includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
|
|
1788
|
-
console.log(` ${
|
|
2280
|
+
console.log(` ${ui.success("\u2713")} Sub-pages: ${includeSubpages ? ui.success("yes") : ui.error("no")}
|
|
1789
2281
|
`);
|
|
1790
2282
|
const concurrencyAnswer = await ask2("Download concurrency", "12");
|
|
1791
2283
|
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
1792
|
-
console.log(` ${
|
|
2284
|
+
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
1793
2285
|
`);
|
|
1794
2286
|
} else {
|
|
1795
2287
|
rl2.close();
|
|
@@ -1812,19 +2304,18 @@ async function runSetup(legacyMode = false) {
|
|
|
1812
2304
|
}
|
|
1813
2305
|
const w = maxWidth();
|
|
1814
2306
|
const isSmall = w < 50;
|
|
1815
|
-
const G2 = chalk9.hex("#C4953A");
|
|
1816
|
-
const B2 = chalk9.hex("#8B6914");
|
|
1817
2307
|
console.log("");
|
|
1818
2308
|
if (!isSmall) {
|
|
1819
2309
|
console.log(boxTop(w));
|
|
1820
|
-
console.log(boxLine(w,
|
|
2310
|
+
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
2311
|
+
console.log(boxSep(w));
|
|
1821
2312
|
} else {
|
|
1822
|
-
console.log(
|
|
2313
|
+
console.log(ui.text.bold(" Summary:"));
|
|
1823
2314
|
}
|
|
1824
2315
|
for (const [label, value] of [
|
|
1825
2316
|
["URL", siteUrl],
|
|
1826
2317
|
["Platform", platformName],
|
|
1827
|
-
["Output",
|
|
2318
|
+
["Output", path7.resolve(outDir)],
|
|
1828
2319
|
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
1829
2320
|
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
1830
2321
|
["Concurrency", String(concurrency)]
|
|
@@ -1849,14 +2340,14 @@ async function runSetup(legacyMode = false) {
|
|
|
1849
2340
|
}
|
|
1850
2341
|
if (!startExport) {
|
|
1851
2342
|
console.log(`
|
|
1852
|
-
${
|
|
2343
|
+
${ui.warning("Export cancelled.")}
|
|
1853
2344
|
`);
|
|
1854
2345
|
return;
|
|
1855
2346
|
}
|
|
1856
2347
|
console.log("");
|
|
1857
2348
|
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1858
2349
|
CFG2.concurrency = concurrency;
|
|
1859
|
-
const exporter = new FramerExporter(siteUrl,
|
|
2350
|
+
const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
|
|
1860
2351
|
exporter.prettyPrint = prettyPrint;
|
|
1861
2352
|
await exporter.run(includeSubpages);
|
|
1862
2353
|
}
|
|
@@ -1868,24 +2359,26 @@ var init_setup = __esm({
|
|
|
1868
2359
|
init_platforms();
|
|
1869
2360
|
init_select();
|
|
1870
2361
|
init_box();
|
|
2362
|
+
init_theme();
|
|
1871
2363
|
}
|
|
1872
2364
|
});
|
|
1873
2365
|
|
|
1874
2366
|
// src/cli/index.ts
|
|
1875
2367
|
init_package();
|
|
1876
|
-
import
|
|
2368
|
+
import path8 from "path";
|
|
1877
2369
|
import { URL as URL5 } from "url";
|
|
1878
2370
|
|
|
1879
2371
|
// src/cli/help.ts
|
|
1880
2372
|
init_banner();
|
|
1881
|
-
|
|
2373
|
+
init_theme();
|
|
1882
2374
|
function showHelp() {
|
|
1883
2375
|
showBanner();
|
|
1884
|
-
console.log(
|
|
1885
|
-
|
|
1886
|
-
console.log(` ${
|
|
2376
|
+
console.log(`${ui.text.bold(" USAGE")} ${chip("cli")}
|
|
2377
|
+
`);
|
|
2378
|
+
console.log(` ${ui.primary("framer-export")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`);
|
|
2379
|
+
console.log(` ${ui.primary("fexport")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`);
|
|
1887
2380
|
console.log("");
|
|
1888
|
-
console.log(
|
|
2381
|
+
console.log(ui.text.bold(" OPTIONS\n"));
|
|
1889
2382
|
const opts = [
|
|
1890
2383
|
["--setup", "Launch the interactive setup assistant"],
|
|
1891
2384
|
["--platform <p>", "Force platform: framer, webflow, wix"],
|
|
@@ -1894,27 +2387,27 @@ function showHelp() {
|
|
|
1894
2387
|
["--help, -h", "Show this help message"]
|
|
1895
2388
|
];
|
|
1896
2389
|
for (const [flag, desc] of opts) {
|
|
1897
|
-
console.log(` ${
|
|
2390
|
+
console.log(` ${ui.success(flag.padEnd(18))} ${ui.text(desc)}`);
|
|
1898
2391
|
}
|
|
1899
2392
|
console.log("");
|
|
1900
|
-
console.log(
|
|
2393
|
+
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
1901
2394
|
const platforms = [
|
|
1902
2395
|
["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
|
|
1903
2396
|
["Webflow", "Auto-detected via .webflow.io URLs"],
|
|
1904
2397
|
["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
|
|
1905
2398
|
];
|
|
1906
2399
|
for (const [name, desc] of platforms) {
|
|
1907
|
-
console.log(` ${
|
|
2400
|
+
console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
|
|
1908
2401
|
}
|
|
1909
2402
|
console.log("");
|
|
1910
|
-
console.log(
|
|
1911
|
-
console.log(` ${
|
|
1912
|
-
console.log(` ${
|
|
1913
|
-
console.log(` ${
|
|
1914
|
-
console.log(` ${
|
|
1915
|
-
console.log(` ${
|
|
1916
|
-
console.log(` ${
|
|
1917
|
-
console.log(` ${
|
|
2403
|
+
console.log(ui.text.bold(" EXAMPLES\n"));
|
|
2404
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.framer.app")}`);
|
|
2405
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.webflow.io")}`);
|
|
2406
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://user.wixsite.com/my-site")}`);
|
|
2407
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--platform webflow")} ${ui.warning("https://custom.com")}`);
|
|
2408
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--subpages")} ${ui.warning("https://mysite.com")}`);
|
|
2409
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup")}`);
|
|
2410
|
+
console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup --legacy-mode")}`);
|
|
1918
2411
|
console.log("");
|
|
1919
2412
|
}
|
|
1920
2413
|
|
|
@@ -1953,6 +2446,7 @@ async function checkForUpdates(currentVersion) {
|
|
|
1953
2446
|
}
|
|
1954
2447
|
|
|
1955
2448
|
// src/cli/index.ts
|
|
2449
|
+
init_theme();
|
|
1956
2450
|
var VERSION = package_default.version;
|
|
1957
2451
|
function extractFlag(args, flag) {
|
|
1958
2452
|
const idx = args.indexOf(flag);
|
|
@@ -1974,30 +2468,26 @@ async function main() {
|
|
|
1974
2468
|
process.exit(0);
|
|
1975
2469
|
}
|
|
1976
2470
|
if (args.includes("--about")) {
|
|
1977
|
-
const
|
|
2471
|
+
const chalk8 = (await import("chalk")).default;
|
|
1978
2472
|
showBanner();
|
|
1979
|
-
console.log(` ${
|
|
1980
|
-
console.log(` ${
|
|
2473
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
2474
|
+
console.log(` ${ui.text(package_default.description)}
|
|
1981
2475
|
`);
|
|
1982
|
-
console.log(` ${
|
|
1983
|
-
console.log(` ${
|
|
1984
|
-
console.log(` ${
|
|
1985
|
-
console.log(` ${
|
|
1986
|
-
console.log(` ${
|
|
1987
|
-
console.log(` ${
|
|
2476
|
+
console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
|
|
2477
|
+
console.log(` ${ui.text.bold("Portfolio")} ${chalk8.underline.hex("#FAB283")("https://github.com/danbenba")}`);
|
|
2478
|
+
console.log(` ${ui.text.bold("GitHub")} ${chalk8.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`);
|
|
2479
|
+
console.log(` ${ui.text.bold("npm")} ${chalk8.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`);
|
|
2480
|
+
console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
|
|
2481
|
+
console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
|
|
1988
2482
|
console.log("");
|
|
1989
2483
|
process.exit(0);
|
|
1990
2484
|
}
|
|
1991
2485
|
checkForUpdates(VERSION).then((latest) => {
|
|
1992
2486
|
if (!latest) return;
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
);
|
|
1998
|
-
console.log(` ${chalk10.default.hex("#D4A017")(" Run:")} ${chalk10.default.hex("#B8860B")("npm i -g framer-export@latest")}`);
|
|
1999
|
-
console.log("");
|
|
2000
|
-
});
|
|
2487
|
+
console.log("");
|
|
2488
|
+
console.log(` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`);
|
|
2489
|
+
console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
|
|
2490
|
+
console.log("");
|
|
2001
2491
|
});
|
|
2002
2492
|
if (args.includes("--help") || args.includes("-h")) {
|
|
2003
2493
|
showHelp();
|
|
@@ -2022,9 +2512,8 @@ async function main() {
|
|
|
2022
2512
|
try {
|
|
2023
2513
|
new URL5(url);
|
|
2024
2514
|
} catch {
|
|
2025
|
-
|
|
2026
|
-
console.log(` ${
|
|
2027
|
-
console.log(` ${chalk10.gray("Expected: https://yoursite.framer.app")}
|
|
2515
|
+
console.log(` ${ui.error("\u2717")} ${ui.error.bold("Invalid URL:")} ${ui.text(url)}`);
|
|
2516
|
+
console.log(` ${ui.muted("Expected: https://yoursite.framer.app")}
|
|
2028
2517
|
`);
|
|
2029
2518
|
process.exit(1);
|
|
2030
2519
|
}
|
|
@@ -2034,12 +2523,12 @@ async function main() {
|
|
|
2034
2523
|
const defaultDir = deriveOutputName2(url, detected);
|
|
2035
2524
|
const out = args[1] || `./${defaultDir}`;
|
|
2036
2525
|
try {
|
|
2037
|
-
await new FramerExporter2(url,
|
|
2526
|
+
await new FramerExporter2(url, path8.resolve(out), platformOverride || void 0).run(includeSubpages);
|
|
2038
2527
|
} catch (e) {
|
|
2039
|
-
const
|
|
2528
|
+
const chalk8 = (await import("chalk")).default;
|
|
2040
2529
|
console.log(`
|
|
2041
|
-
${
|
|
2042
|
-
console.log(
|
|
2530
|
+
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`);
|
|
2531
|
+
console.log(chalk8.gray(e.stack || ""));
|
|
2043
2532
|
process.exit(1);
|
|
2044
2533
|
}
|
|
2045
2534
|
}
|