framer-export 4.3.2 → 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 +797 -278
- 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
|
}
|
|
@@ -267,41 +328,60 @@ function trunc(text, max) {
|
|
|
267
328
|
if (text.length <= max) return text;
|
|
268
329
|
return text.slice(0, max - 2) + "..";
|
|
269
330
|
}
|
|
270
|
-
function nextColor() {
|
|
271
|
-
return MSG_COLORS[_msgIdx++ % MSG_COLORS.length];
|
|
272
|
-
}
|
|
273
331
|
function output(line) {
|
|
274
|
-
if (_cooking)
|
|
275
|
-
|
|
276
|
-
} else {
|
|
277
|
-
console.log(line);
|
|
278
|
-
}
|
|
332
|
+
if (_cooking) _cooking.log(line);
|
|
333
|
+
else console.log(line);
|
|
279
334
|
}
|
|
280
|
-
var _cooking, T,
|
|
335
|
+
var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
281
336
|
var init_logger = __esm({
|
|
282
337
|
"src/logger/index.ts"() {
|
|
283
338
|
"use strict";
|
|
339
|
+
init_theme();
|
|
284
340
|
_cooking = null;
|
|
285
341
|
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
286
|
-
|
|
287
|
-
(s) =>
|
|
288
|
-
(s) =>
|
|
289
|
-
(s) =>
|
|
290
|
-
(s) =>
|
|
291
|
-
(s) =>
|
|
292
|
-
(s) =>
|
|
293
|
-
|
|
294
|
-
|
|
342
|
+
LOG_PALETTE = [
|
|
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)
|
|
349
|
+
];
|
|
350
|
+
_li = 0;
|
|
351
|
+
log = (m) => {
|
|
352
|
+
_li++;
|
|
353
|
+
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
354
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`);
|
|
355
|
+
};
|
|
356
|
+
INFO_PALETTE = [
|
|
357
|
+
(s) => chalk2.hex(THEME.info)(s),
|
|
358
|
+
(s) => chalk2.hex(THEME.secondary)(s),
|
|
359
|
+
(s) => chalk2.hex(THEME.primarySoft)(s)
|
|
295
360
|
];
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
361
|
+
_ii = 0;
|
|
362
|
+
info = (m) => {
|
|
363
|
+
_ii++;
|
|
364
|
+
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
365
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`);
|
|
366
|
+
};
|
|
299
367
|
warn = (m) => {
|
|
300
|
-
const
|
|
368
|
+
const colors = [
|
|
369
|
+
(s) => chalk2.hex(THEME.warning)(s),
|
|
370
|
+
(s) => chalk2.hex(THEME.primary)(s)
|
|
371
|
+
];
|
|
372
|
+
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
373
|
+
const line = `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
301
374
|
if (_cooking) _cooking.log(line);
|
|
302
375
|
else console.warn(line);
|
|
303
376
|
};
|
|
304
|
-
success = (m) =>
|
|
377
|
+
success = (m) => {
|
|
378
|
+
const colors = [
|
|
379
|
+
(s) => chalk2.hex(THEME.success)(s),
|
|
380
|
+
(s) => chalk2.hex(THEME.info)(s)
|
|
381
|
+
];
|
|
382
|
+
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
383
|
+
output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`);
|
|
384
|
+
};
|
|
305
385
|
}
|
|
306
386
|
});
|
|
307
387
|
|
|
@@ -1154,7 +1234,7 @@ var init_output = __esm({
|
|
|
1154
1234
|
});
|
|
1155
1235
|
|
|
1156
1236
|
// src/cli/box.ts
|
|
1157
|
-
import
|
|
1237
|
+
import chalk3 from "chalk";
|
|
1158
1238
|
function maxWidth() {
|
|
1159
1239
|
return Math.min(process.stdout.columns || 80, 62);
|
|
1160
1240
|
}
|
|
@@ -1163,51 +1243,45 @@ function padRight(text, w) {
|
|
|
1163
1243
|
if (visible >= w) return text;
|
|
1164
1244
|
return text + " ".repeat(w - visible);
|
|
1165
1245
|
}
|
|
1166
|
-
function stripAnsi(s) {
|
|
1167
|
-
return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
1168
|
-
}
|
|
1169
1246
|
function boxTop(w) {
|
|
1170
1247
|
const inner = w - 4;
|
|
1171
|
-
return " " +
|
|
1248
|
+
return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
|
|
1172
1249
|
}
|
|
1173
1250
|
function boxBot(w) {
|
|
1174
1251
|
const inner = w - 4;
|
|
1175
|
-
return " " +
|
|
1252
|
+
return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
|
|
1176
1253
|
}
|
|
1177
1254
|
function boxLine(w, text) {
|
|
1178
1255
|
const inner = w - 4;
|
|
1179
1256
|
const padded = padRight(text, inner);
|
|
1180
|
-
return " " +
|
|
1257
|
+
return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
|
|
1181
1258
|
}
|
|
1182
1259
|
function boxSep(w) {
|
|
1183
1260
|
const inner = w - 4;
|
|
1184
|
-
return " " +
|
|
1261
|
+
return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
|
|
1185
1262
|
}
|
|
1186
1263
|
function boxRow(w, label, value) {
|
|
1187
1264
|
const inner = w - 4;
|
|
1188
|
-
const labelPlain = stripAnsi(
|
|
1265
|
+
const labelPlain = stripAnsi(chalk3.bold(label));
|
|
1189
1266
|
const visible = labelPlain.length + 1 + value.length;
|
|
1190
1267
|
if (visible > inner) {
|
|
1191
1268
|
const avail = inner - labelPlain.length - 2;
|
|
1192
1269
|
const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
|
|
1193
|
-
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");
|
|
1194
1271
|
}
|
|
1195
1272
|
const right = inner - labelPlain.length - 1 - value.length;
|
|
1196
|
-
return " " +
|
|
1273
|
+
return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
|
|
1197
1274
|
}
|
|
1198
|
-
var B, G;
|
|
1199
1275
|
var init_box = __esm({
|
|
1200
1276
|
"src/cli/box.ts"() {
|
|
1201
1277
|
"use strict";
|
|
1202
|
-
|
|
1203
|
-
G = chalk4.hex("#C4953A");
|
|
1278
|
+
init_theme();
|
|
1204
1279
|
}
|
|
1205
1280
|
});
|
|
1206
1281
|
|
|
1207
1282
|
// src/exporter/summary.ts
|
|
1208
1283
|
import fs3 from "fs/promises";
|
|
1209
1284
|
import path4 from "path";
|
|
1210
|
-
import chalk5 from "chalk";
|
|
1211
1285
|
async function printSummary(exporter) {
|
|
1212
1286
|
const w = maxWidth();
|
|
1213
1287
|
const isSmall = w < 50;
|
|
@@ -1229,17 +1303,17 @@ async function printSummary(exporter) {
|
|
|
1229
1303
|
count("data"),
|
|
1230
1304
|
count("subpages")
|
|
1231
1305
|
]);
|
|
1232
|
-
const
|
|
1233
|
-
const
|
|
1234
|
-
const C2 =
|
|
1235
|
-
const Y =
|
|
1236
|
-
const O =
|
|
1237
|
-
const Br =
|
|
1238
|
-
const Gr =
|
|
1239
|
-
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;
|
|
1240
1314
|
const entries = [
|
|
1241
|
-
["styles/", styles, "CSS",
|
|
1242
|
-
["scripts/vendor/", vendor, "JS vendor",
|
|
1315
|
+
["styles/", styles, "CSS", G],
|
|
1316
|
+
["scripts/vendor/", vendor, "JS vendor", G2],
|
|
1243
1317
|
["scripts/modules/", scripts, "JS modules", C2],
|
|
1244
1318
|
["assets/images/", imgs, "images", Y],
|
|
1245
1319
|
["assets/videos/", videos, "videos", O],
|
|
@@ -1251,23 +1325,23 @@ async function printSummary(exporter) {
|
|
|
1251
1325
|
console.log("");
|
|
1252
1326
|
if (!isSmall) {
|
|
1253
1327
|
console.log(boxTop(w));
|
|
1254
|
-
console.log(boxLine(w,
|
|
1328
|
+
console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
|
|
1255
1329
|
console.log(boxSep(w));
|
|
1256
1330
|
} else {
|
|
1257
|
-
console.log(
|
|
1331
|
+
console.log(ui.text.bold(" Export Summary:"));
|
|
1258
1332
|
}
|
|
1259
1333
|
for (const [label, cnt, type, color] of entries) {
|
|
1260
1334
|
if (cnt === 0) continue;
|
|
1261
1335
|
const inner = w - 4;
|
|
1262
1336
|
const l = label.padEnd(16);
|
|
1263
1337
|
const c = String(cnt).padStart(3);
|
|
1264
|
-
const rowText = `${color(l)}${
|
|
1338
|
+
const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
|
|
1265
1339
|
const visible = 16 + 3 + 2 + type.length;
|
|
1266
1340
|
const pad = Math.max(0, inner - visible);
|
|
1267
1341
|
if (isSmall) {
|
|
1268
|
-
console.log(` ${color(label)} ${
|
|
1342
|
+
console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
|
|
1269
1343
|
} else {
|
|
1270
|
-
console.log(" " +
|
|
1344
|
+
console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
|
|
1271
1345
|
}
|
|
1272
1346
|
}
|
|
1273
1347
|
if (!isSmall) {
|
|
@@ -1277,74 +1351,601 @@ async function printSummary(exporter) {
|
|
|
1277
1351
|
const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
|
|
1278
1352
|
if (!isSmall) {
|
|
1279
1353
|
console.log(boxTop(w));
|
|
1280
|
-
console.log(boxLine(w,
|
|
1354
|
+
console.log(boxLine(w, ui.text.bold(" To serve locally")));
|
|
1281
1355
|
console.log(boxSep(w));
|
|
1282
1356
|
const inner = w - 6;
|
|
1283
1357
|
const cmdLen = cdCmd.length;
|
|
1284
1358
|
const pad = Math.max(0, inner - cmdLen);
|
|
1285
|
-
console.log(" " +
|
|
1359
|
+
console.log(" " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502"));
|
|
1286
1360
|
console.log(boxBot(w));
|
|
1287
1361
|
} else {
|
|
1288
|
-
console.log(
|
|
1289
|
-
console.log(` ${
|
|
1362
|
+
console.log(ui.text.bold(" To serve locally:"));
|
|
1363
|
+
console.log(` ${G(cdCmd)}`);
|
|
1290
1364
|
}
|
|
1291
1365
|
console.log("");
|
|
1292
|
-
console.log(
|
|
1366
|
+
console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
|
|
1293
1367
|
console.log("");
|
|
1294
1368
|
}
|
|
1295
1369
|
var init_summary = __esm({
|
|
1296
1370
|
"src/exporter/summary.ts"() {
|
|
1297
1371
|
"use strict";
|
|
1298
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
|
+
];
|
|
1299
1919
|
}
|
|
1300
1920
|
});
|
|
1301
1921
|
|
|
1302
1922
|
// src/cli/cooking.ts
|
|
1303
|
-
import
|
|
1304
|
-
var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES,
|
|
1923
|
+
import chalk5 from "chalk";
|
|
1924
|
+
var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, CookingSpinner;
|
|
1305
1925
|
var init_cooking = __esm({
|
|
1306
1926
|
"src/cli/cooking.ts"() {
|
|
1307
1927
|
"use strict";
|
|
1928
|
+
init_theme();
|
|
1308
1929
|
SHINE_WIDTH = 10;
|
|
1309
|
-
FRAME_INTERVAL =
|
|
1930
|
+
FRAME_INTERVAL = 80;
|
|
1310
1931
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1311
|
-
PALETTE = [
|
|
1312
|
-
(s) => chalk6.hex("#D4A017")(s),
|
|
1313
|
-
(s) => chalk6.hex("#E8A317")(s),
|
|
1314
|
-
(s) => chalk6.hex("#DAA520")(s),
|
|
1315
|
-
(s) => chalk6.hex("#F0C040")(s),
|
|
1316
|
-
(s) => chalk6.hex("#C4953A")(s),
|
|
1317
|
-
(s) => chalk6.hex("#E6B83C")(s),
|
|
1318
|
-
(s) => chalk6.hex("#C8A951")(s),
|
|
1319
|
-
(s) => chalk6.hex("#D9B44D")(s)
|
|
1320
|
-
];
|
|
1321
1932
|
CookingSpinner = class {
|
|
1322
1933
|
interval = null;
|
|
1323
|
-
bgInterval = null;
|
|
1324
1934
|
frame = 0;
|
|
1325
1935
|
phase = "";
|
|
1326
1936
|
active = false;
|
|
1327
|
-
bar = 0;
|
|
1328
|
-
barDir = 1;
|
|
1329
1937
|
start(phase = "") {
|
|
1330
1938
|
this.phase = phase;
|
|
1331
1939
|
this.frame = 0;
|
|
1332
|
-
this.bar = 0;
|
|
1333
|
-
this.barDir = 1;
|
|
1334
1940
|
this.active = true;
|
|
1335
1941
|
this.draw();
|
|
1336
1942
|
this.interval = setInterval(() => {
|
|
1337
1943
|
this.frame++;
|
|
1338
1944
|
this.draw();
|
|
1339
1945
|
}, FRAME_INTERVAL);
|
|
1340
|
-
this.bgInterval = setInterval(() => {
|
|
1341
|
-
this.bar++;
|
|
1342
|
-
this.draw();
|
|
1343
|
-
}, 800);
|
|
1344
1946
|
}
|
|
1345
1947
|
update(phase) {
|
|
1346
1948
|
this.phase = phase;
|
|
1347
|
-
if (!this.active) this.draw();
|
|
1348
1949
|
}
|
|
1349
1950
|
log(message) {
|
|
1350
1951
|
if (this.active) {
|
|
@@ -1361,27 +1962,15 @@ var init_cooking = __esm({
|
|
|
1361
1962
|
clearInterval(this.interval);
|
|
1362
1963
|
this.interval = null;
|
|
1363
1964
|
}
|
|
1364
|
-
if (this.bgInterval) {
|
|
1365
|
-
clearInterval(this.bgInterval);
|
|
1366
|
-
this.bgInterval = null;
|
|
1367
|
-
}
|
|
1368
1965
|
process.stdout.write("\r\x1B[2K");
|
|
1369
1966
|
}
|
|
1370
1967
|
draw() {
|
|
1371
1968
|
if (!this.active) return;
|
|
1372
1969
|
const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
|
|
1373
|
-
const
|
|
1374
|
-
const
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
const spinnerChar = chalk6.hex("#F0C040")(spinner);
|
|
1378
|
-
const barLen = 12;
|
|
1379
|
-
const barPos = this.bar % (barLen * 2);
|
|
1380
|
-
const barStr = this.renderBar(barLen, barPos);
|
|
1381
|
-
const phaseStr = this.phase ? ` ${chalk6.gray(this.limitLen(this.phase, 38))}` : "";
|
|
1382
|
-
process.stdout.write(
|
|
1383
|
-
`\r\x1B[2K ${spinnerChar} ${shimmer} ${paletteFn(dotStr)} ${barStr}${phaseStr}`
|
|
1384
|
-
);
|
|
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,34 +1980,16 @@ 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;
|
|
1400
1989
|
}
|
|
1401
|
-
renderBar(len, pos) {
|
|
1402
|
-
let s = "";
|
|
1403
|
-
for (let i = 0; i < len; i++) {
|
|
1404
|
-
const dist = Math.abs(i - pos);
|
|
1405
|
-
if (dist < 3) {
|
|
1406
|
-
const t = 1 - dist / 3;
|
|
1407
|
-
const r = Math.floor(200 + t * 55);
|
|
1408
|
-
const g = Math.floor(160 + t * 50);
|
|
1409
|
-
const b = Math.floor(20 + t * 30);
|
|
1410
|
-
s += chalk6.rgb(r, g, b)("\u258C");
|
|
1411
|
-
} else if (dist < 6) {
|
|
1412
|
-
s += chalk6.rgb(140, 100, 20)("\u258C");
|
|
1413
|
-
} else {
|
|
1414
|
-
s += chalk6.rgb(50, 35, 10)(" ");
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
return `[${s}]`;
|
|
1418
|
-
}
|
|
1419
1990
|
limitLen(s, max) {
|
|
1420
1991
|
if (s.length <= max) return s;
|
|
1421
|
-
return s.slice(0, max -
|
|
1992
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
1422
1993
|
}
|
|
1423
1994
|
};
|
|
1424
1995
|
}
|
|
@@ -1430,9 +2001,9 @@ __export(exporter_exports, {
|
|
|
1430
2001
|
FramerExporter: () => FramerExporter,
|
|
1431
2002
|
deriveOutputName: () => deriveOutputName
|
|
1432
2003
|
});
|
|
1433
|
-
import
|
|
1434
|
-
import
|
|
1435
|
-
import
|
|
2004
|
+
import fs5 from "fs/promises";
|
|
2005
|
+
import path6 from "path";
|
|
2006
|
+
import chalk6 from "chalk";
|
|
1436
2007
|
function deriveOutputName(url, platformName) {
|
|
1437
2008
|
try {
|
|
1438
2009
|
const parsed = new URL(url);
|
|
@@ -1464,8 +2035,10 @@ var init_exporter = __esm({
|
|
|
1464
2035
|
init_download2();
|
|
1465
2036
|
init_output();
|
|
1466
2037
|
init_summary();
|
|
2038
|
+
init_prompt_assistant();
|
|
1467
2039
|
init_platforms();
|
|
1468
2040
|
init_cooking();
|
|
2041
|
+
init_theme();
|
|
1469
2042
|
FramerExporter = class {
|
|
1470
2043
|
siteUrl;
|
|
1471
2044
|
outDir;
|
|
@@ -1491,12 +2064,14 @@ var init_exporter = __esm({
|
|
|
1491
2064
|
}
|
|
1492
2065
|
}
|
|
1493
2066
|
async run(includeSubpages = false) {
|
|
1494
|
-
console.log(
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
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));
|
|
1498
2073
|
if (includeSubpages) {
|
|
1499
|
-
info("Subpages : " +
|
|
2074
|
+
info("Subpages : " + ui.success("enabled"));
|
|
1500
2075
|
}
|
|
1501
2076
|
console.log("");
|
|
1502
2077
|
this.cooking = new CookingSpinner();
|
|
@@ -1514,7 +2089,7 @@ var init_exporter = __esm({
|
|
|
1514
2089
|
"data",
|
|
1515
2090
|
"subpages"
|
|
1516
2091
|
]) {
|
|
1517
|
-
await
|
|
2092
|
+
await fs5.mkdir(path6.join(this.outDir, d), { recursive: true });
|
|
1518
2093
|
}
|
|
1519
2094
|
log("Output directory structure created");
|
|
1520
2095
|
this.cooking.update("Fetching SSR HTML...");
|
|
@@ -1524,12 +2099,14 @@ var init_exporter = __esm({
|
|
|
1524
2099
|
this.ssrHTML = buf.toString("utf-8");
|
|
1525
2100
|
success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
|
|
1526
2101
|
} catch (e) {
|
|
1527
|
-
log(
|
|
2102
|
+
log(chalk6.red("Could not fetch SSR HTML: " + e.message));
|
|
1528
2103
|
}
|
|
1529
2104
|
const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
|
|
1530
2105
|
if (htmlDetected.name !== this.platform.name) {
|
|
1531
2106
|
this.platform = htmlDetected;
|
|
1532
|
-
log(
|
|
2107
|
+
log(
|
|
2108
|
+
"Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)"
|
|
2109
|
+
);
|
|
1533
2110
|
}
|
|
1534
2111
|
await launchAndCapture(this);
|
|
1535
2112
|
if (includeSubpages && this.page) {
|
|
@@ -1545,6 +2122,7 @@ var init_exporter = __esm({
|
|
|
1545
2122
|
console.log("");
|
|
1546
2123
|
success("Export complete!");
|
|
1547
2124
|
await printSummary(this);
|
|
2125
|
+
await runAiPromptAssistant(this);
|
|
1548
2126
|
}
|
|
1549
2127
|
async crawlSubpages() {
|
|
1550
2128
|
this.cooking?.update("Discovering sub-pages...");
|
|
@@ -1557,7 +2135,8 @@ var init_exporter = __esm({
|
|
|
1557
2135
|
const hrefs = /* @__PURE__ */ new Set();
|
|
1558
2136
|
for (const a of anchors) {
|
|
1559
2137
|
const href = a.href;
|
|
1560
|
-
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;
|
|
1561
2140
|
try {
|
|
1562
2141
|
const u = new URL(href);
|
|
1563
2142
|
const h = u.hostname.replace(/^www\./, "");
|
|
@@ -1585,8 +2164,8 @@ var init_exporter = __esm({
|
|
|
1585
2164
|
});
|
|
1586
2165
|
const slug = this.deriveSlug(link, baseUrl);
|
|
1587
2166
|
const filename = slug + ".html";
|
|
1588
|
-
const filepath =
|
|
1589
|
-
await
|
|
2167
|
+
const filepath = path6.join(this.outDir, "subpages", filename);
|
|
2168
|
+
await fs5.writeFile(filepath, html, "utf-8");
|
|
1590
2169
|
log(" Saved: subpages/" + filename);
|
|
1591
2170
|
} catch (e) {
|
|
1592
2171
|
log(" Skipped " + link + ": " + e.message);
|
|
@@ -1608,94 +2187,37 @@ var init_exporter = __esm({
|
|
|
1608
2187
|
}
|
|
1609
2188
|
});
|
|
1610
2189
|
|
|
1611
|
-
// src/cli/select.ts
|
|
1612
|
-
import readline from "readline";
|
|
1613
|
-
import { stdin, stdout } from "process";
|
|
1614
|
-
import chalk8 from "chalk";
|
|
1615
|
-
async function select(question, options, defaultIndex = 0) {
|
|
1616
|
-
return new Promise((resolve) => {
|
|
1617
|
-
let selected = defaultIndex;
|
|
1618
|
-
const render = (initial = false) => {
|
|
1619
|
-
if (!initial) {
|
|
1620
|
-
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1621
|
-
stdout.write("\x1B[J");
|
|
1622
|
-
}
|
|
1623
|
-
console.log(` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}`);
|
|
1624
|
-
for (let i = 0; i < options.length; i++) {
|
|
1625
|
-
if (i === selected) {
|
|
1626
|
-
console.log(` ${chalk8.hex("#D4A017")(">")} ${chalk8.white.bold(options[i].label)}`);
|
|
1627
|
-
} else {
|
|
1628
|
-
console.log(` ${chalk8.gray(options[i].label)}`);
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
};
|
|
1632
|
-
render(true);
|
|
1633
|
-
readline.emitKeypressEvents(stdin);
|
|
1634
|
-
const wasTTY = stdin.isTTY || false;
|
|
1635
|
-
if (wasTTY) stdin.setRawMode(true);
|
|
1636
|
-
const onKeypress = (_str, key) => {
|
|
1637
|
-
if (!key) return;
|
|
1638
|
-
if (key.name === "up" && selected > 0) {
|
|
1639
|
-
selected--;
|
|
1640
|
-
render();
|
|
1641
|
-
} else if (key.name === "down" && selected < options.length - 1) {
|
|
1642
|
-
selected++;
|
|
1643
|
-
render();
|
|
1644
|
-
} else if (key.name === "return") {
|
|
1645
|
-
if (wasTTY) stdin.setRawMode(false);
|
|
1646
|
-
stdin.removeListener("keypress", onKeypress);
|
|
1647
|
-
stdin.pause();
|
|
1648
|
-
stdout.write(`\x1B[${options.length + 1}A`);
|
|
1649
|
-
stdout.write("\x1B[J");
|
|
1650
|
-
console.log(` ${chalk8.green("\u2713")} ${chalk8.white.bold(question)} ${chalk8.hex("#D4A017")(options[selected].label)}
|
|
1651
|
-
`);
|
|
1652
|
-
resolve(options[selected].value);
|
|
1653
|
-
} else if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
1654
|
-
if (wasTTY) stdin.setRawMode(false);
|
|
1655
|
-
stdin.removeListener("keypress", onKeypress);
|
|
1656
|
-
process.exit(0);
|
|
1657
|
-
}
|
|
1658
|
-
};
|
|
1659
|
-
stdin.resume();
|
|
1660
|
-
stdin.on("keypress", onKeypress);
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
var init_select = __esm({
|
|
1664
|
-
"src/cli/select.ts"() {
|
|
1665
|
-
"use strict";
|
|
1666
|
-
}
|
|
1667
|
-
});
|
|
1668
|
-
|
|
1669
2190
|
// src/cli/setup.ts
|
|
1670
2191
|
var setup_exports = {};
|
|
1671
2192
|
__export(setup_exports, {
|
|
1672
2193
|
runSetup: () => runSetup
|
|
1673
2194
|
});
|
|
1674
2195
|
import readline2 from "readline/promises";
|
|
1675
|
-
import { stdin as
|
|
1676
|
-
import
|
|
2196
|
+
import { stdin as stdin3, stdout as stdout3 } from "process";
|
|
2197
|
+
import path7 from "path";
|
|
1677
2198
|
import { URL as URL4 } from "url";
|
|
1678
|
-
import
|
|
2199
|
+
import chalk7 from "chalk";
|
|
1679
2200
|
function drawHeader(title) {
|
|
1680
2201
|
const w = maxWidth();
|
|
1681
2202
|
if (w < 50) {
|
|
1682
2203
|
console.log(`
|
|
1683
|
-
${
|
|
2204
|
+
${bullet("\u25CF")} ${ui.text.bold(title)}`);
|
|
1684
2205
|
return;
|
|
1685
2206
|
}
|
|
1686
2207
|
console.log(boxTop(w));
|
|
1687
|
-
console.log(boxLine(w,
|
|
2208
|
+
console.log(boxLine(w, ui.text.bold(" " + title)));
|
|
1688
2209
|
console.log(boxBot(w));
|
|
1689
2210
|
console.log("");
|
|
1690
2211
|
}
|
|
1691
2212
|
async function runSetup(legacyMode = false) {
|
|
1692
2213
|
showBanner();
|
|
1693
|
-
console.log(
|
|
1694
|
-
console.log(
|
|
1695
|
-
|
|
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 });
|
|
1696
2218
|
const ask = async (question, defaultVal) => {
|
|
1697
|
-
const suffix = defaultVal ?
|
|
1698
|
-
const prompt = ` ${
|
|
2219
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2220
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1699
2221
|
const answer = await rl.question(prompt);
|
|
1700
2222
|
return answer.trim() || defaultVal || "";
|
|
1701
2223
|
};
|
|
@@ -1707,42 +2229,42 @@ async function runSetup(legacyMode = false) {
|
|
|
1707
2229
|
new URL4(input);
|
|
1708
2230
|
siteUrl = input;
|
|
1709
2231
|
} catch {
|
|
1710
|
-
console.log(` ${
|
|
2232
|
+
console.log(` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
|
|
1711
2233
|
`);
|
|
1712
2234
|
}
|
|
1713
2235
|
}
|
|
1714
|
-
console.log(` ${
|
|
2236
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk7.underline(siteUrl)}
|
|
1715
2237
|
`);
|
|
1716
2238
|
drawHeader("Step 2 : Platform");
|
|
1717
2239
|
const detected = detectPlatform(siteUrl);
|
|
1718
2240
|
let platformName;
|
|
1719
2241
|
if (legacyMode) {
|
|
1720
|
-
console.log(` ${
|
|
2242
|
+
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
1721
2243
|
const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
|
|
1722
2244
|
platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
|
|
1723
|
-
console.log(` ${
|
|
2245
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
1724
2246
|
`);
|
|
1725
2247
|
} else {
|
|
1726
2248
|
rl.close();
|
|
1727
2249
|
const platforms = [
|
|
1728
|
-
{ label: `Framer${detected.name === "framer" ?
|
|
1729
|
-
{ label: `Webflow${detected.name === "webflow" ?
|
|
1730
|
-
{ 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" }
|
|
1731
2253
|
];
|
|
1732
2254
|
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
1733
2255
|
platformName = await select("Select platform", platforms, Math.max(0, defaultIdx));
|
|
1734
2256
|
}
|
|
1735
|
-
const rl2 = legacyMode ? rl : readline2.createInterface({ input:
|
|
2257
|
+
const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
|
|
1736
2258
|
const ask2 = async (question, defaultVal) => {
|
|
1737
|
-
const suffix = defaultVal ?
|
|
1738
|
-
const prompt = ` ${
|
|
2259
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2260
|
+
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
1739
2261
|
const answer = await rl2.question(prompt);
|
|
1740
2262
|
return answer.trim() || defaultVal || "";
|
|
1741
2263
|
};
|
|
1742
2264
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
1743
2265
|
drawHeader("Step 3 : Output Directory");
|
|
1744
2266
|
const outDir = await ask2("Output directory", "./" + derivedName);
|
|
1745
|
-
console.log(` ${
|
|
2267
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
1746
2268
|
`);
|
|
1747
2269
|
drawHeader("Step 4 : Options");
|
|
1748
2270
|
let prettyPrint;
|
|
@@ -1751,15 +2273,15 @@ async function runSetup(legacyMode = false) {
|
|
|
1751
2273
|
if (legacyMode) {
|
|
1752
2274
|
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
1753
2275
|
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
1754
|
-
console.log(` ${
|
|
2276
|
+
console.log(` ${ui.success("\u2713")} Pretty-print: ${prettyPrint ? ui.success("yes") : ui.error("no")}
|
|
1755
2277
|
`);
|
|
1756
2278
|
const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
|
|
1757
2279
|
includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
|
|
1758
|
-
console.log(` ${
|
|
2280
|
+
console.log(` ${ui.success("\u2713")} Sub-pages: ${includeSubpages ? ui.success("yes") : ui.error("no")}
|
|
1759
2281
|
`);
|
|
1760
2282
|
const concurrencyAnswer = await ask2("Download concurrency", "12");
|
|
1761
2283
|
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
1762
|
-
console.log(` ${
|
|
2284
|
+
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
1763
2285
|
`);
|
|
1764
2286
|
} else {
|
|
1765
2287
|
rl2.close();
|
|
@@ -1782,19 +2304,18 @@ async function runSetup(legacyMode = false) {
|
|
|
1782
2304
|
}
|
|
1783
2305
|
const w = maxWidth();
|
|
1784
2306
|
const isSmall = w < 50;
|
|
1785
|
-
const G2 = chalk9.hex("#C4953A");
|
|
1786
|
-
const B2 = chalk9.hex("#8B6914");
|
|
1787
2307
|
console.log("");
|
|
1788
2308
|
if (!isSmall) {
|
|
1789
2309
|
console.log(boxTop(w));
|
|
1790
|
-
console.log(boxLine(w,
|
|
2310
|
+
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
2311
|
+
console.log(boxSep(w));
|
|
1791
2312
|
} else {
|
|
1792
|
-
console.log(
|
|
2313
|
+
console.log(ui.text.bold(" Summary:"));
|
|
1793
2314
|
}
|
|
1794
2315
|
for (const [label, value] of [
|
|
1795
2316
|
["URL", siteUrl],
|
|
1796
2317
|
["Platform", platformName],
|
|
1797
|
-
["Output",
|
|
2318
|
+
["Output", path7.resolve(outDir)],
|
|
1798
2319
|
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
1799
2320
|
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
1800
2321
|
["Concurrency", String(concurrency)]
|
|
@@ -1819,14 +2340,14 @@ async function runSetup(legacyMode = false) {
|
|
|
1819
2340
|
}
|
|
1820
2341
|
if (!startExport) {
|
|
1821
2342
|
console.log(`
|
|
1822
|
-
${
|
|
2343
|
+
${ui.warning("Export cancelled.")}
|
|
1823
2344
|
`);
|
|
1824
2345
|
return;
|
|
1825
2346
|
}
|
|
1826
2347
|
console.log("");
|
|
1827
2348
|
const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1828
2349
|
CFG2.concurrency = concurrency;
|
|
1829
|
-
const exporter = new FramerExporter(siteUrl,
|
|
2350
|
+
const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
|
|
1830
2351
|
exporter.prettyPrint = prettyPrint;
|
|
1831
2352
|
await exporter.run(includeSubpages);
|
|
1832
2353
|
}
|
|
@@ -1838,24 +2359,26 @@ var init_setup = __esm({
|
|
|
1838
2359
|
init_platforms();
|
|
1839
2360
|
init_select();
|
|
1840
2361
|
init_box();
|
|
2362
|
+
init_theme();
|
|
1841
2363
|
}
|
|
1842
2364
|
});
|
|
1843
2365
|
|
|
1844
2366
|
// src/cli/index.ts
|
|
1845
2367
|
init_package();
|
|
1846
|
-
import
|
|
2368
|
+
import path8 from "path";
|
|
1847
2369
|
import { URL as URL5 } from "url";
|
|
1848
2370
|
|
|
1849
2371
|
// src/cli/help.ts
|
|
1850
2372
|
init_banner();
|
|
1851
|
-
|
|
2373
|
+
init_theme();
|
|
1852
2374
|
function showHelp() {
|
|
1853
2375
|
showBanner();
|
|
1854
|
-
console.log(
|
|
1855
|
-
|
|
1856
|
-
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]")}`);
|
|
1857
2380
|
console.log("");
|
|
1858
|
-
console.log(
|
|
2381
|
+
console.log(ui.text.bold(" OPTIONS\n"));
|
|
1859
2382
|
const opts = [
|
|
1860
2383
|
["--setup", "Launch the interactive setup assistant"],
|
|
1861
2384
|
["--platform <p>", "Force platform: framer, webflow, wix"],
|
|
@@ -1864,27 +2387,27 @@ function showHelp() {
|
|
|
1864
2387
|
["--help, -h", "Show this help message"]
|
|
1865
2388
|
];
|
|
1866
2389
|
for (const [flag, desc] of opts) {
|
|
1867
|
-
console.log(` ${
|
|
2390
|
+
console.log(` ${ui.success(flag.padEnd(18))} ${ui.text(desc)}`);
|
|
1868
2391
|
}
|
|
1869
2392
|
console.log("");
|
|
1870
|
-
console.log(
|
|
2393
|
+
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
1871
2394
|
const platforms = [
|
|
1872
2395
|
["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
|
|
1873
2396
|
["Webflow", "Auto-detected via .webflow.io URLs"],
|
|
1874
2397
|
["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
|
|
1875
2398
|
];
|
|
1876
2399
|
for (const [name, desc] of platforms) {
|
|
1877
|
-
console.log(` ${
|
|
2400
|
+
console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
|
|
1878
2401
|
}
|
|
1879
2402
|
console.log("");
|
|
1880
|
-
console.log(
|
|
1881
|
-
console.log(` ${
|
|
1882
|
-
console.log(` ${
|
|
1883
|
-
console.log(` ${
|
|
1884
|
-
console.log(` ${
|
|
1885
|
-
console.log(` ${
|
|
1886
|
-
console.log(` ${
|
|
1887
|
-
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")}`);
|
|
1888
2411
|
console.log("");
|
|
1889
2412
|
}
|
|
1890
2413
|
|
|
@@ -1923,6 +2446,7 @@ async function checkForUpdates(currentVersion) {
|
|
|
1923
2446
|
}
|
|
1924
2447
|
|
|
1925
2448
|
// src/cli/index.ts
|
|
2449
|
+
init_theme();
|
|
1926
2450
|
var VERSION = package_default.version;
|
|
1927
2451
|
function extractFlag(args, flag) {
|
|
1928
2452
|
const idx = args.indexOf(flag);
|
|
@@ -1944,30 +2468,26 @@ async function main() {
|
|
|
1944
2468
|
process.exit(0);
|
|
1945
2469
|
}
|
|
1946
2470
|
if (args.includes("--about")) {
|
|
1947
|
-
const
|
|
2471
|
+
const chalk8 = (await import("chalk")).default;
|
|
1948
2472
|
showBanner();
|
|
1949
|
-
console.log(` ${
|
|
1950
|
-
console.log(` ${
|
|
2473
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
2474
|
+
console.log(` ${ui.text(package_default.description)}
|
|
1951
2475
|
`);
|
|
1952
|
-
console.log(` ${
|
|
1953
|
-
console.log(` ${
|
|
1954
|
-
console.log(` ${
|
|
1955
|
-
console.log(` ${
|
|
1956
|
-
console.log(` ${
|
|
1957
|
-
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}`)}`);
|
|
1958
2482
|
console.log("");
|
|
1959
2483
|
process.exit(0);
|
|
1960
2484
|
}
|
|
1961
2485
|
checkForUpdates(VERSION).then((latest) => {
|
|
1962
2486
|
if (!latest) return;
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
);
|
|
1968
|
-
console.log(` ${chalk10.default.hex("#D4A017")(" Run:")} ${chalk10.default.hex("#B8860B")("npm i -g framer-export@latest")}`);
|
|
1969
|
-
console.log("");
|
|
1970
|
-
});
|
|
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("");
|
|
1971
2491
|
});
|
|
1972
2492
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1973
2493
|
showHelp();
|
|
@@ -1992,9 +2512,8 @@ async function main() {
|
|
|
1992
2512
|
try {
|
|
1993
2513
|
new URL5(url);
|
|
1994
2514
|
} catch {
|
|
1995
|
-
|
|
1996
|
-
console.log(` ${
|
|
1997
|
-
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")}
|
|
1998
2517
|
`);
|
|
1999
2518
|
process.exit(1);
|
|
2000
2519
|
}
|
|
@@ -2004,12 +2523,12 @@ async function main() {
|
|
|
2004
2523
|
const defaultDir = deriveOutputName2(url, detected);
|
|
2005
2524
|
const out = args[1] || `./${defaultDir}`;
|
|
2006
2525
|
try {
|
|
2007
|
-
await new FramerExporter2(url,
|
|
2526
|
+
await new FramerExporter2(url, path8.resolve(out), platformOverride || void 0).run(includeSubpages);
|
|
2008
2527
|
} catch (e) {
|
|
2009
|
-
const
|
|
2528
|
+
const chalk8 = (await import("chalk")).default;
|
|
2010
2529
|
console.log(`
|
|
2011
|
-
${
|
|
2012
|
-
console.log(
|
|
2530
|
+
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`);
|
|
2531
|
+
console.log(chalk8.gray(e.stack || ""));
|
|
2013
2532
|
process.exit(1);
|
|
2014
2533
|
}
|
|
2015
2534
|
}
|