framer-export 4.3.10 → 4.4.2

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 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.10",
18
+ version: "4.4.2",
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",
@@ -204,6 +204,663 @@ var init_banner = __esm({
204
204
  }
205
205
  });
206
206
 
207
+ // src/cli/cooking.ts
208
+ import chalk2 from "chalk";
209
+ async function showLoadingIntro(version) {
210
+ if (!process.stdout.isTTY || process.env.CI) return;
211
+ await new Promise((resolve) => {
212
+ let frame = 0;
213
+ const draw = () => {
214
+ const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
215
+ const title = renderShinyText(`Framer Export v${version}`, frame, {
216
+ baseColor: "#969696",
217
+ shineColor: "#ffffff",
218
+ shineWidth: 18
219
+ });
220
+ const dots = ".".repeat(frame % 4).padEnd(3, " ");
221
+ process.stdout.write(
222
+ `\r\x1B[2K ${ui.primary(spinner)} ${title} ${ui.muted(`Loading${dots}`)}`
223
+ );
224
+ frame++;
225
+ };
226
+ process.stdout.write("\x1B[?25l");
227
+ draw();
228
+ const interval = setInterval(draw, FRAME_INTERVAL);
229
+ setTimeout(() => {
230
+ clearInterval(interval);
231
+ process.stdout.write("\r\x1B[2K\x1B[?25h");
232
+ resolve();
233
+ }, INTRO_DURATION);
234
+ });
235
+ }
236
+ function renderShinyText(text, frame, options = {}) {
237
+ const baseColor = options.baseColor || "#808080";
238
+ const shineColor = options.shineColor || "#ffffff";
239
+ const shineWidth = options.shineWidth || SHINE_WIDTH;
240
+ const travel = text.length + shineWidth * 2;
241
+ const pos = frame * SHINE_SPEED % travel - shineWidth;
242
+ const base = hexToRgb(baseColor);
243
+ const shine = hexToRgb(shineColor);
244
+ let result = "";
245
+ for (let i = 0; i < text.length; i++) {
246
+ const dist = Math.abs(i - pos);
247
+ const intensity = smoothstep(0, 1, 1 - Math.min(1, dist / shineWidth));
248
+ const color = mixRgb(base, shine, intensity);
249
+ const paint = intensity > 0.82 ? chalk2.rgb(color.r, color.g, color.b).bold : chalk2.rgb(color.r, color.g, color.b);
250
+ result += paint(text[i]);
251
+ }
252
+ return result;
253
+ }
254
+ function smoothstep(edge0, edge1, value) {
255
+ const t = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
256
+ return t * t * (3 - 2 * t);
257
+ }
258
+ function hexToRgb(hex) {
259
+ const clean = hex.replace("#", "");
260
+ const normalized = clean.length === 3 ? clean.split("").map((char) => char + char).join("") : clean.padEnd(6, "0").slice(0, 6);
261
+ return {
262
+ r: Number.parseInt(normalized.slice(0, 2), 16),
263
+ g: Number.parseInt(normalized.slice(2, 4), 16),
264
+ b: Number.parseInt(normalized.slice(4, 6), 16)
265
+ };
266
+ }
267
+ function mixRgb(from, to, amount) {
268
+ return {
269
+ r: Math.round(from.r + (to.r - from.r) * amount),
270
+ g: Math.round(from.g + (to.g - from.g) * amount),
271
+ b: Math.round(from.b + (to.b - from.b) * amount)
272
+ };
273
+ }
274
+ var SHINE_WIDTH, FRAME_INTERVAL, INTRO_DURATION, SHINE_SPEED, SPINNER_FRAMES, CookingSpinner;
275
+ var init_cooking = __esm({
276
+ "src/cli/cooking.ts"() {
277
+ "use strict";
278
+ init_theme();
279
+ SHINE_WIDTH = 14;
280
+ FRAME_INTERVAL = 70;
281
+ INTRO_DURATION = 1400;
282
+ SHINE_SPEED = 0.55;
283
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
284
+ CookingSpinner = class {
285
+ interval = null;
286
+ frame = 0;
287
+ phase = "";
288
+ active = false;
289
+ start(phase = "") {
290
+ this.phase = phase;
291
+ this.frame = 0;
292
+ this.active = true;
293
+ this.draw();
294
+ this.interval = setInterval(() => {
295
+ this.frame++;
296
+ this.draw();
297
+ }, FRAME_INTERVAL);
298
+ }
299
+ update(phase) {
300
+ this.phase = phase;
301
+ }
302
+ log(message) {
303
+ if (this.active) {
304
+ process.stdout.write("\r\x1B[2K");
305
+ }
306
+ process.stdout.write(message + "\n");
307
+ if (this.active) {
308
+ this.draw();
309
+ }
310
+ }
311
+ stop() {
312
+ this.active = false;
313
+ if (this.interval) {
314
+ clearInterval(this.interval);
315
+ this.interval = null;
316
+ }
317
+ process.stdout.write("\r\x1B[2K");
318
+ }
319
+ draw() {
320
+ if (!this.active) return;
321
+ const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
322
+ const shimmer = renderShinyText("Exporting", this.frame);
323
+ const frameStr = ui.primary(spinner);
324
+ const phaseStr = this.phase ? ` ${ui.muted(this.limitLen(this.phase, 52))}` : "";
325
+ process.stdout.write(`\r\x1B[2K ${frameStr} ${shimmer}${phaseStr}`);
326
+ }
327
+ limitLen(s, max) {
328
+ if (s.length <= max) return s;
329
+ return s.slice(0, max - 1) + "\u2026";
330
+ }
331
+ };
332
+ }
333
+ });
334
+
335
+ // src/cli/box.ts
336
+ import chalk3 from "chalk";
337
+ function maxWidth() {
338
+ return Math.min(process.stdout.columns || 80, 76);
339
+ }
340
+ function padRight(text, w) {
341
+ const visible = stripAnsi(text).length;
342
+ if (visible >= w) return text;
343
+ return text + " ".repeat(w - visible);
344
+ }
345
+ function boxTop(w) {
346
+ const inner = w - 4;
347
+ return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
348
+ }
349
+ function panelTop(w) {
350
+ const inner = w - 4;
351
+ return ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
352
+ }
353
+ function boxBot(w) {
354
+ const inner = w - 4;
355
+ return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
356
+ }
357
+ function panelBot(w) {
358
+ const inner = w - 4;
359
+ return ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
360
+ }
361
+ function boxLine(w, text) {
362
+ const inner = w - 4;
363
+ const padded = padRight(text, inner);
364
+ return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
365
+ }
366
+ function panelLine(w, text) {
367
+ const inner = w - 4;
368
+ const padded = padRight(text, inner);
369
+ return ui.border("\u2502 ") + padded + ui.border(" \u2502");
370
+ }
371
+ function boxSep(w) {
372
+ const inner = w - 4;
373
+ return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
374
+ }
375
+ function panelSep(w) {
376
+ const inner = w - 4;
377
+ return ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
378
+ }
379
+ function boxRow(w, label, value) {
380
+ const inner = w - 4;
381
+ const labelPlain = stripAnsi(chalk3.bold(label));
382
+ const visible = labelPlain.length + 1 + value.length;
383
+ if (visible > inner) {
384
+ const avail = inner - labelPlain.length - 2;
385
+ const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
386
+ return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
387
+ }
388
+ const right = inner - labelPlain.length - 1 - value.length;
389
+ return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
390
+ }
391
+ var init_box = __esm({
392
+ "src/cli/box.ts"() {
393
+ "use strict";
394
+ init_theme();
395
+ }
396
+ });
397
+
398
+ // src/cli/select.ts
399
+ import readline from "readline";
400
+ import { stdin, stdout } from "process";
401
+ async function select(question, options, defaultIndex = 0, config = {}) {
402
+ const isTTY = stdin.isTTY && stdout.isTTY;
403
+ if (!isTTY) {
404
+ return fallbackPrompt(question, options, defaultIndex, config);
405
+ }
406
+ return arrowSelect(question, options, defaultIndex, config);
407
+ }
408
+ async function promptInput(question, defaultValue = "", config = {}) {
409
+ if (!stdin.isTTY || !stdout.isTTY) {
410
+ return fallbackInput(question, defaultValue);
411
+ }
412
+ return fullscreenInput(question, defaultValue, config);
413
+ }
414
+ async function arrowSelect(question, options, defaultIndex, config) {
415
+ const actions = config.actions ?? [];
416
+ const headerLines = config.headerLines ?? [];
417
+ const width = Math.max(44, Math.min(maxWidth(), 72));
418
+ const inner = width - 4;
419
+ const actionLineOffset = 2 + headerLines.length;
420
+ const hasActions = actions.length > 0;
421
+ const optionStartOffset = 3 + headerLines.length + (hasActions ? 1 : 0);
422
+ const lineCount = options.length + 4 + headerLines.length + (hasActions ? 1 : 0);
423
+ const rows = process.stdout.rows || 24;
424
+ const columns = process.stdout.columns || 80;
425
+ const panelTopRow = Math.max(2, Math.floor((rows - lineCount) / 2) + 1);
426
+ const panelLeftCol = Math.max(1, Math.floor((columns - width) / 2) + 1);
427
+ const footerRow = Math.min(rows, panelTopRow + lineCount + 1);
428
+ return new Promise((resolve) => {
429
+ const firstEnabled = options.findIndex((option) => !option.disabled);
430
+ let selected = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
431
+ let selectedAction = null;
432
+ if (selected < 0) selected = 0;
433
+ const move = (direction) => {
434
+ let next = selected + direction;
435
+ while (next >= 0 && next < options.length) {
436
+ if (!options[next].disabled) {
437
+ selected = next;
438
+ return;
439
+ }
440
+ next += direction;
441
+ }
442
+ };
443
+ const render = (initial = false) => {
444
+ if (!initial) {
445
+ stdout.write("\x1B[2J");
446
+ }
447
+ const lines = [];
448
+ lines.push(panelTop(width));
449
+ lines.push(
450
+ panelLine(width, centerText(`${ui.primary("\u25CF")} ${ui.text.bold(question)}`, inner))
451
+ );
452
+ for (const header of headerLines) {
453
+ lines.push(panelLine(width, centerText(ui.muted(header), inner)));
454
+ }
455
+ if (hasActions) {
456
+ lines.push(
457
+ panelLine(width, centerText(renderActions(actions, selectedAction, inner), inner))
458
+ );
459
+ }
460
+ lines.push(panelSep(width));
461
+ for (let i = 0; i < options.length; i++) {
462
+ lines.push(
463
+ panelLine(
464
+ width,
465
+ centerText(
466
+ renderOption(options[i], selectedAction === null && i === selected, inner),
467
+ inner
468
+ )
469
+ )
470
+ );
471
+ }
472
+ lines.push(panelBot(width));
473
+ lines.forEach((line, index) => writeAt(panelTopRow + index, panelLeftCol, line));
474
+ writeAt(
475
+ footerRow,
476
+ panelLeftCol,
477
+ centerText(
478
+ ui.muted(config.footer || "\u2191\u2193 move \xB7 enter select \xB7 mouse hover/click \xB7 esc close"),
479
+ width
480
+ )
481
+ );
482
+ };
483
+ const choose = (value = options[selected].value) => {
484
+ cleanup();
485
+ console.log(
486
+ ` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(labelForValue(value, options, actions))}
487
+ `
488
+ );
489
+ resolve(value);
490
+ };
491
+ const onMouseData = (chunk) => {
492
+ const mouse = parseMouseEvent(chunk);
493
+ if (!mouse) return;
494
+ if (mouse.kind === "wheel-up") {
495
+ move(-1);
496
+ render();
497
+ return;
498
+ }
499
+ if (mouse.kind === "wheel-down") {
500
+ move(1);
501
+ render();
502
+ return;
503
+ }
504
+ if (hasActions && mouse.y === panelTopRow + actionLineOffset) {
505
+ const actionIdx = actionIndexAtX(actions, mouse.x, panelLeftCol, inner);
506
+ if (actionIdx === null || actions[actionIdx].disabled) return;
507
+ if (selectedAction !== actionIdx) {
508
+ selectedAction = actionIdx;
509
+ render();
510
+ }
511
+ if (mouse.kind === "click") choose(actions[actionIdx].value);
512
+ return;
513
+ }
514
+ const idx = mouse.y - panelTopRow - optionStartOffset;
515
+ if (idx < 0 || idx >= options.length || options[idx].disabled) return;
516
+ if (selected !== idx) {
517
+ selected = idx;
518
+ selectedAction = null;
519
+ render();
520
+ }
521
+ if (mouse.kind === "click") {
522
+ choose();
523
+ }
524
+ };
525
+ const cleanup = () => {
526
+ leaveInteractiveScreen();
527
+ stdin.setRawMode(false);
528
+ stdin.removeListener("keypress", onKeypress);
529
+ stdin.removeListener("data", onMouseData);
530
+ stdin.pause();
531
+ };
532
+ readline.emitKeypressEvents(stdin);
533
+ stdin.setRawMode(true);
534
+ enterInteractiveScreen(true);
535
+ render(true);
536
+ const onKeypress = (_str, key) => {
537
+ if (!key) return;
538
+ if (key.name === "up" && selected > 0) {
539
+ move(-1);
540
+ selectedAction = null;
541
+ render();
542
+ } else if (key.name === "down" && selected < options.length - 1) {
543
+ move(1);
544
+ selectedAction = null;
545
+ render();
546
+ } else if (key.name === "tab" && hasActions) {
547
+ selectedAction = selectedAction === null ? 0 : null;
548
+ render();
549
+ } else if (key.name === "return") {
550
+ choose(selectedAction === null ? options[selected].value : actions[selectedAction].value);
551
+ } else if (key.ctrl && key.name === "c" || key.name === "escape") {
552
+ cleanup();
553
+ process.exit(0);
554
+ }
555
+ };
556
+ stdin.resume();
557
+ stdin.on("data", onMouseData);
558
+ stdin.on("keypress", onKeypress);
559
+ });
560
+ }
561
+ async function fallbackPrompt(question, options, defaultIndex, config) {
562
+ if (!stdin.isTTY) {
563
+ const firstEnabled = options.findIndex((option) => !option.disabled);
564
+ const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
565
+ const def = String(enabledDefault + 1);
566
+ printFallbackOptions(question, options, enabledDefault, config);
567
+ while (true) {
568
+ const trimmed = (await readPipedLine()).trim();
569
+ if (trimmed.toLowerCase() === "a") {
570
+ const action = config.actions?.find((item) => !item.disabled);
571
+ if (action) return action.value;
572
+ }
573
+ if (!trimmed) {
574
+ const label = stripAnsi(options[enabledDefault].label);
575
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
576
+ `);
577
+ return options[enabledDefault].value;
578
+ }
579
+ const idx = parseInt(trimmed, 10);
580
+ if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
581
+ const label = stripAnsi(options[idx - 1].label);
582
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
583
+ `);
584
+ return options[idx - 1].value;
585
+ }
586
+ console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length} or ${def}`)}
587
+ `);
588
+ }
589
+ }
590
+ return new Promise((resolve) => {
591
+ const rl = readline.createInterface({ input: stdin, output: stdout });
592
+ const firstEnabled = options.findIndex((option) => !option.disabled);
593
+ const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
594
+ printFallbackOptions(question, options, enabledDefault, config);
595
+ const def = String(enabledDefault + 1);
596
+ const ask = () => {
597
+ rl.question(
598
+ ` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
599
+ (answer) => {
600
+ const trimmed = answer.trim();
601
+ if (trimmed.toLowerCase() === "a") {
602
+ const action = config.actions?.find((item) => !item.disabled);
603
+ if (action) {
604
+ rl.close();
605
+ resolve(action.value);
606
+ return;
607
+ }
608
+ }
609
+ if (!trimmed) {
610
+ rl.close();
611
+ const label = stripAnsi(options[enabledDefault].label);
612
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
613
+ `);
614
+ resolve(options[enabledDefault].value);
615
+ return;
616
+ }
617
+ const idx = parseInt(trimmed, 10);
618
+ if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
619
+ rl.close();
620
+ const label = stripAnsi(options[idx - 1].label);
621
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
622
+ `);
623
+ resolve(options[idx - 1].value);
624
+ } else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
625
+ console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
626
+ `);
627
+ ask();
628
+ } else {
629
+ console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
630
+ `);
631
+ ask();
632
+ }
633
+ }
634
+ );
635
+ };
636
+ ask();
637
+ });
638
+ }
639
+ function fullscreenInput(question, defaultValue, config) {
640
+ const headerLines = config.headerLines ?? [];
641
+ const width = Math.max(44, Math.min(maxWidth(), 72));
642
+ const inner = width - 4;
643
+ const lineCount = 6 + headerLines.length;
644
+ const rows = process.stdout.rows || 24;
645
+ const columns = process.stdout.columns || 80;
646
+ const panelTopRow = Math.max(2, Math.floor((rows - lineCount) / 2) + 1);
647
+ const panelLeftCol = Math.max(1, Math.floor((columns - width) / 2) + 1);
648
+ const footerRow = Math.min(rows, panelTopRow + lineCount + 1);
649
+ return new Promise((resolve) => {
650
+ let value = defaultValue;
651
+ const render = () => {
652
+ stdout.write("\x1B[2J");
653
+ const shown = value || "";
654
+ const clipped = truncatePlain(shown, Math.max(12, inner - 10));
655
+ const input = `${ui.primary(">")} ${ui.text(clipped)}${ui.primary("\u258C")}`;
656
+ const lines = [];
657
+ lines.push(panelTop(width));
658
+ lines.push(
659
+ panelLine(width, centerText(`${ui.primary("\u25CF")} ${ui.text.bold(question)}`, inner))
660
+ );
661
+ for (const header of headerLines) {
662
+ lines.push(panelLine(width, centerText(ui.muted(header), inner)));
663
+ }
664
+ lines.push(panelSep(width));
665
+ lines.push(panelLine(width, centerText(input, inner)));
666
+ lines.push(panelBot(width));
667
+ lines.forEach((line, index) => writeAt(panelTopRow + index, panelLeftCol, line));
668
+ writeAt(
669
+ footerRow,
670
+ panelLeftCol,
671
+ centerText(ui.muted(config.footer || "type value \xB7 enter confirm \xB7 esc close"), width)
672
+ );
673
+ };
674
+ const cleanup = () => {
675
+ leaveInteractiveScreen();
676
+ stdin.setRawMode(false);
677
+ stdin.removeListener("keypress", onKeypress);
678
+ stdin.pause();
679
+ };
680
+ const submit = () => {
681
+ const output2 = cleanInputValue(value.trim() || defaultValue);
682
+ cleanup();
683
+ resolve(output2);
684
+ };
685
+ const onKeypress = (str, key) => {
686
+ if (key.ctrl && key.name === "c" || key.name === "escape") {
687
+ cleanup();
688
+ process.exit(0);
689
+ }
690
+ if (key.name === "return") {
691
+ submit();
692
+ return;
693
+ }
694
+ if (key.name === "backspace") {
695
+ value = value.slice(0, -1);
696
+ render();
697
+ return;
698
+ }
699
+ if (key.name === "delete") {
700
+ value = "";
701
+ render();
702
+ return;
703
+ }
704
+ if (str && !key.ctrl && !key.meta && str >= " " && !isTerminalSequence(str, key)) {
705
+ value += cleanInputValue(str.replace(/[\r\n]/g, ""));
706
+ render();
707
+ }
708
+ };
709
+ readline.emitKeypressEvents(stdin);
710
+ stdin.setRawMode(true);
711
+ enterInteractiveScreen(false);
712
+ render();
713
+ stdin.resume();
714
+ stdin.on("keypress", onKeypress);
715
+ });
716
+ }
717
+ async function fallbackInput(question, defaultValue) {
718
+ if (!stdin.isTTY) {
719
+ const suffix = defaultValue ? ui.muted(` (${defaultValue})`) : "";
720
+ console.log(` ${ui.primary(">")} ${ui.text.bold(question)}${suffix}: `);
721
+ const answer = await readPipedLine();
722
+ return cleanInputValue(answer.trim() || defaultValue);
723
+ }
724
+ return new Promise((resolve) => {
725
+ const rl = readline.createInterface({ input: stdin, output: stdout });
726
+ const suffix = defaultValue ? ui.muted(` (${defaultValue})`) : "";
727
+ rl.question(` ${ui.primary(">")} ${ui.text.bold(question)}${suffix}: `, (answer) => {
728
+ rl.close();
729
+ resolve(cleanInputValue(answer.trim() || defaultValue));
730
+ });
731
+ });
732
+ }
733
+ function printFallbackOptions(question, options, enabledDefault, config) {
734
+ console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}
735
+ `);
736
+ for (const header of config.headerLines ?? []) {
737
+ console.log(` ${ui.muted(header)}`);
738
+ }
739
+ for (const action of config.actions ?? []) {
740
+ console.log(
741
+ ` ${ui.muted("[A]")} ${action.disabled ? ui.muted(action.label) : ui.text(action.label)}`
742
+ );
743
+ }
744
+ if ((config.headerLines?.length || 0) > 0 || (config.actions?.length || 0) > 0) {
745
+ console.log("");
746
+ }
747
+ for (let i = 0; i < options.length; i++) {
748
+ const marker = i === enabledDefault ? ui.success(" \u25C6") : " ";
749
+ const label = options[i].disabled ? ui.muted(stripAnsi(options[i].label)) : ui.text(options[i].label);
750
+ console.log(` ${ui.muted(`[${i + 1}]`)}${marker} ${label}`);
751
+ }
752
+ console.log("");
753
+ }
754
+ function readPipedLine() {
755
+ if (!pipedLinesPromise) {
756
+ pipedLinesPromise = new Promise((resolve) => {
757
+ let data = "";
758
+ stdin.setEncoding("utf8");
759
+ stdin.on("data", (chunk) => {
760
+ data += chunk;
761
+ });
762
+ stdin.on("end", () => {
763
+ resolve(data.split(/\r?\n/));
764
+ });
765
+ stdin.on("error", () => {
766
+ resolve([]);
767
+ });
768
+ });
769
+ }
770
+ return pipedLinesPromise.then((lines) => lines[pipedLineIndex++] ?? "");
771
+ }
772
+ function renderActions(actions, active, width) {
773
+ return actions.map((action, index) => {
774
+ const label = truncatePlain(
775
+ stripAnsi(action.label),
776
+ Math.max(8, Math.floor(width / actions.length) - 8)
777
+ );
778
+ if (action.disabled) return `${ui.border("[")} ${ui.muted(label)} ${ui.border("]")}`;
779
+ if (active === index)
780
+ return `${ui.primary("\u203A")} ${ui.primary("[")} ${ui.text.bold(label)} ${ui.primary("]")} ${ui.primary("\u2039")}`;
781
+ return `${ui.border("[")} ${ui.primary(label)} ${ui.border("]")}`;
782
+ }).join(` ${ui.muted("\xB7")} `);
783
+ }
784
+ function actionIndexAtX(actions, mouseX, panelLeftCol, inner) {
785
+ if (actions.length === 0) return null;
786
+ const contentStart = panelLeftCol + 2;
787
+ const relative = mouseX - contentStart;
788
+ if (relative < 0 || relative > inner) return null;
789
+ return Math.min(actions.length - 1, Math.floor(relative / Math.max(1, inner) * actions.length));
790
+ }
791
+ function labelForValue(value, options, actions) {
792
+ return stripAnsi(
793
+ options.find((option) => option.value === value)?.label || actions.find((action) => action.value === value)?.label || value
794
+ );
795
+ }
796
+ function renderOption(option, selected, width) {
797
+ const plain = truncatePlain(stripAnsi(option.label), Math.max(10, width - 12));
798
+ if (option.disabled) {
799
+ return `${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")}`;
800
+ }
801
+ if (selected) {
802
+ return `${ui.primary("\u203A")} ${ui.primary("[")} ${ui.text.bold(plain)} ${ui.primary("]")} ${ui.primary("\u2039")}`;
803
+ }
804
+ return `${ui.muted(" ")} ${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")} ${ui.muted(" ")}`;
805
+ }
806
+ function enterInteractiveScreen(enableMouse) {
807
+ stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?25l");
808
+ if (enableMouse) {
809
+ stdout.write("\x1B[?1006h\x1B[?1000h\x1B[?1002h\x1B[?1003h");
810
+ }
811
+ }
812
+ function leaveInteractiveScreen() {
813
+ stdout.write(
814
+ "\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?25h\x1B[?1049l"
815
+ );
816
+ }
817
+ function parseMouseEvent(chunk) {
818
+ const text = chunk.toString("utf-8");
819
+ const match = text.match(/\x1B\[<(\d+);(\d+);(\d+)([mM])/);
820
+ if (!match) return parseLegacyMouseEvent(text);
821
+ const code = Number(match[1]);
822
+ const x = Number(match[2]);
823
+ const y = Number(match[3]);
824
+ const state = match[4];
825
+ if (code === 64) return { kind: "wheel-up", x, y };
826
+ if (code === 65) return { kind: "wheel-down", x, y };
827
+ if (state === "m") return { kind: "click", x, y };
828
+ if ((code & 32) === 32 || code === 35) return { kind: "hover", x, y };
829
+ if ((code & 3) === 0) return { kind: "hover", x, y };
830
+ return null;
831
+ }
832
+ function parseLegacyMouseEvent(text) {
833
+ const match = text.match(/\x1B\[M([\s\S])([\s\S])([\s\S])/);
834
+ if (!match) return null;
835
+ const code = match[1].charCodeAt(0) - 32;
836
+ const x = match[2].charCodeAt(0) - 32;
837
+ const y = match[3].charCodeAt(0) - 32;
838
+ if (code === 64) return { kind: "wheel-up", x, y };
839
+ if (code === 65) return { kind: "wheel-down", x, y };
840
+ if ((code & 3) === 3) return { kind: "click", x, y };
841
+ if ((code & 32) === 32) return { kind: "hover", x, y };
842
+ return { kind: "hover", x, y };
843
+ }
844
+ function writeAt(row, col, text) {
845
+ stdout.write(`\x1B[${row};${col}H${text}`);
846
+ }
847
+ function isTerminalSequence(str, key) {
848
+ return str.includes("\x1B") || !!key.sequence?.includes("\x1B") || /^(?:\d+;){2}\d+[mM]$/.test(str);
849
+ }
850
+ function cleanInputValue(value) {
851
+ return value.replace(/\x1B\[<\d+;\d+;\d+[mM]/g, "").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").replace(/(?:\d+;){2}\d+[mM]/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
852
+ }
853
+ var pipedLinesPromise, pipedLineIndex;
854
+ var init_select = __esm({
855
+ "src/cli/select.ts"() {
856
+ "use strict";
857
+ init_box();
858
+ init_theme();
859
+ pipedLinesPromise = null;
860
+ pipedLineIndex = 0;
861
+ }
862
+ });
863
+
207
864
  // src/assets/asset-map.ts
208
865
  import path from "path";
209
866
  import crypto from "crypto";
@@ -340,7 +997,7 @@ var init_download = __esm({
340
997
  });
341
998
 
342
999
  // src/logger/index.ts
343
- import chalk2 from "chalk";
1000
+ import chalk4 from "chalk";
344
1001
  function setCooking(spinner) {
345
1002
  _cooking = spinner;
346
1003
  }
@@ -360,52 +1017,52 @@ var init_logger = __esm({
360
1017
  _cooking = null;
361
1018
  T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
362
1019
  LOG_PALETTE = [
363
- (s) => chalk2.hex(THEME.primary)(s),
364
- (s) => chalk2.hex(THEME.primarySoft)(s),
365
- (s) => chalk2.hex(THEME.secondary)(s),
366
- (s) => chalk2.hex(THEME.accent)(s),
367
- (s) => chalk2.hex(THEME.info)(s),
368
- (s) => chalk2.hex(THEME.text)(s)
1020
+ (s) => chalk4.hex(THEME.primary)(s),
1021
+ (s) => chalk4.hex(THEME.primarySoft)(s),
1022
+ (s) => chalk4.hex(THEME.secondary)(s),
1023
+ (s) => chalk4.hex(THEME.accent)(s),
1024
+ (s) => chalk4.hex(THEME.info)(s),
1025
+ (s) => chalk4.hex(THEME.text)(s)
369
1026
  ];
370
1027
  _li = 0;
371
1028
  log = (m) => {
372
1029
  _li++;
373
1030
  const c = LOG_PALETTE[_li % LOG_PALETTE.length];
374
1031
  output(
375
- `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
1032
+ `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
376
1033
  );
377
1034
  };
378
1035
  INFO_PALETTE = [
379
- (s) => chalk2.hex(THEME.info)(s),
380
- (s) => chalk2.hex(THEME.secondary)(s),
381
- (s) => chalk2.hex(THEME.primarySoft)(s)
1036
+ (s) => chalk4.hex(THEME.info)(s),
1037
+ (s) => chalk4.hex(THEME.secondary)(s),
1038
+ (s) => chalk4.hex(THEME.primarySoft)(s)
382
1039
  ];
383
1040
  _ii = 0;
384
1041
  info = (m) => {
385
1042
  _ii++;
386
1043
  const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
387
1044
  output(
388
- `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
1045
+ `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
389
1046
  );
390
1047
  };
391
1048
  warn = (m) => {
392
1049
  const colors = [
393
- (s) => chalk2.hex(THEME.warning)(s),
394
- (s) => chalk2.hex(THEME.primary)(s)
1050
+ (s) => chalk4.hex(THEME.warning)(s),
1051
+ (s) => chalk4.hex(THEME.primary)(s)
395
1052
  ];
396
1053
  const c = colors[Math.floor(Math.random() * colors.length)];
397
- const line = `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
1054
+ const line = `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
398
1055
  if (_cooking) _cooking.log(line);
399
1056
  else console.warn(line);
400
1057
  };
401
1058
  success = (m) => {
402
1059
  const colors = [
403
- (s) => chalk2.hex(THEME.success)(s),
404
- (s) => chalk2.hex(THEME.info)(s)
1060
+ (s) => chalk4.hex(THEME.success)(s),
1061
+ (s) => chalk4.hex(THEME.info)(s)
405
1062
  ];
406
1063
  const c = colors[Math.floor(Math.random() * colors.length)];
407
1064
  output(
408
- `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
1065
+ `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
409
1066
  );
410
1067
  };
411
1068
  }
@@ -946,12 +1603,13 @@ var init_template = __esm({
946
1603
  "src/server/template.ts"() {
947
1604
  "use strict";
948
1605
  SERVE_SCRIPT = `#!/usr/bin/env node
949
- const http = require('http');
950
- const fs = require('fs');
951
- const path = require('path');
1606
+ import http from 'node:http';
1607
+ import fs from 'node:fs';
1608
+ import path from 'node:path';
1609
+ import { fileURLToPath } from 'node:url';
952
1610
 
953
1611
  const PORT = process.env.PORT || 3000;
954
- const ROOT = __dirname;
1612
+ const ROOT = path.dirname(fileURLToPath(import.meta.url));
955
1613
 
956
1614
  const MIME = {
957
1615
  '.html': 'text/html; charset=utf-8',
@@ -1022,6 +1680,7 @@ const ANSI = {
1022
1680
  border: '\\x1b[38;2;72;72;72m',
1023
1681
  success: '\\x1b[38;2;127;216;143m',
1024
1682
  info: '\\x1b[38;2;86;182;194m',
1683
+ error: '\\x1b[38;2;224;108;117m',
1025
1684
  };
1026
1685
 
1027
1686
  function color(name, value) {
@@ -1035,23 +1694,13 @@ function frameLine(label, value) {
1035
1694
  }
1036
1695
 
1037
1696
  function drawServerUi() {
1038
- const logo = [
1039
- '\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ',
1040
- '\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557',
1041
- '\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D',
1042
- '\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557',
1043
- '\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D',
1044
- '\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 ',
1045
- ];
1046
-
1047
- console.log('');
1048
- for (const line of logo) console.log(' ' + color('primary', line));
1049
1697
  console.log('');
1050
1698
  console.log(' ' + color('border', '\u256D\u2500' + '\u2500'.repeat(56) + '\u2500\u256E'));
1051
- console.log(' ' + color('border', '\u2502 ') + color('text', ' Framer Export local server') + ' '.repeat(28) + color('border', ' \u2502'));
1699
+ console.log(' ' + color('border', '\u2502 ') + color('text', ' Framer Export preview server') + ' '.repeat(26) + color('border', ' \u2502'));
1052
1700
  console.log(' ' + color('border', '\u251C\u2500' + '\u2500'.repeat(56) + '\u2500\u2524'));
1053
1701
  frameLine('URL', 'http://localhost:' + PORT);
1054
1702
  frameLine('Root', ROOT);
1703
+ frameLine('Run', 'node serve.js');
1055
1704
  frameLine('Mode', 'static mirror + SPA fallback');
1056
1705
  console.log(' ' + color('border', '\u2570\u2500' + '\u2500'.repeat(56) + '\u2500\u256F'));
1057
1706
  console.log('');
@@ -1060,9 +1709,12 @@ function drawServerUi() {
1060
1709
 
1061
1710
  server.listen(PORT, () => {
1062
1711
  const frames = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
1712
+ const steps = ['Preparing server', 'Checking exported files', 'Binding local port', 'Serving static mirror'];
1063
1713
  let i = 0;
1064
1714
  const timer = setInterval(() => {
1065
- process.stdout.write('\\r\\x1B[2K ' + color('primary', frames[i % frames.length]) + ' ' + color('muted', 'starting Framer Export server'));
1715
+ const step = steps[Math.min(steps.length - 1, Math.floor(i / 6))];
1716
+ const dots = color('error', '.'.repeat(i % 4).padEnd(3, ' '));
1717
+ process.stdout.write('\\r\\x1B[2K ' + color('primary', frames[i % frames.length]) + ' ' + color('text', step) + dots);
1066
1718
  i++;
1067
1719
  }, 80);
1068
1720
 
@@ -1070,7 +1722,7 @@ server.listen(PORT, () => {
1070
1722
  clearInterval(timer);
1071
1723
  process.stdout.write('\\r\\x1B[2K');
1072
1724
  drawServerUi();
1073
- }, 720);
1725
+ }, 1280);
1074
1726
  });
1075
1727
  `;
1076
1728
  }
@@ -1236,8 +1888,13 @@ async function buildOutput(exporter) {
1236
1888
  log("Writing index.html (" + (html.length / 1024).toFixed(1) + " KB)...");
1237
1889
  await fs2.writeFile(path3.join(exporter.outDir, "index.html"), html);
1238
1890
  success("index.html written");
1239
- await fs2.writeFile(path3.join(exporter.outDir, "serve.cjs"), SERVE_SCRIPT);
1240
- log("serve.cjs written");
1891
+ await fs2.writeFile(path3.join(exporter.outDir, "serve.js"), SERVE_SCRIPT);
1892
+ log("serve.js written");
1893
+ await fs2.writeFile(
1894
+ path3.join(exporter.outDir, "package.json"),
1895
+ JSON.stringify({ type: "module", scripts: { serve: "node serve.js" } }, null, 2) + "\n"
1896
+ );
1897
+ log("package.json written for serve.js");
1241
1898
  success("Output build complete");
1242
1899
  }
1243
1900
  async function rewriteDownloadedFiles(exporter) {
@@ -1318,52 +1975,6 @@ var init_output = __esm({
1318
1975
  }
1319
1976
  });
1320
1977
 
1321
- // src/cli/box.ts
1322
- import chalk3 from "chalk";
1323
- function maxWidth() {
1324
- return Math.min(process.stdout.columns || 80, 76);
1325
- }
1326
- function padRight(text, w) {
1327
- const visible = stripAnsi(text).length;
1328
- if (visible >= w) return text;
1329
- return text + " ".repeat(w - visible);
1330
- }
1331
- function boxTop(w) {
1332
- const inner = w - 4;
1333
- return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
1334
- }
1335
- function boxBot(w) {
1336
- const inner = w - 4;
1337
- return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
1338
- }
1339
- function boxLine(w, text) {
1340
- const inner = w - 4;
1341
- const padded = padRight(text, inner);
1342
- return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
1343
- }
1344
- function boxSep(w) {
1345
- const inner = w - 4;
1346
- return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
1347
- }
1348
- function boxRow(w, label, value) {
1349
- const inner = w - 4;
1350
- const labelPlain = stripAnsi(chalk3.bold(label));
1351
- const visible = labelPlain.length + 1 + value.length;
1352
- if (visible > inner) {
1353
- const avail = inner - labelPlain.length - 2;
1354
- const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
1355
- return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
1356
- }
1357
- const right = inner - labelPlain.length - 1 - value.length;
1358
- return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
1359
- }
1360
- var init_box = __esm({
1361
- "src/cli/box.ts"() {
1362
- "use strict";
1363
- init_theme();
1364
- }
1365
- });
1366
-
1367
1978
  // src/exporter/summary.ts
1368
1979
  import fs3 from "fs/promises";
1369
1980
  import path4 from "path";
@@ -1409,277 +2020,50 @@ async function printSummary(exporter) {
1409
2020
  ];
1410
2021
  console.log("");
1411
2022
  if (!isSmall) {
1412
- console.log(boxTop(w));
1413
- console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
1414
- console.log(boxSep(w));
1415
- } else {
1416
- console.log(ui.text.bold(" Export Summary:"));
1417
- }
1418
- for (const [label, cnt, type, color] of entries) {
1419
- if (cnt === 0) continue;
1420
- const inner = w - 4;
1421
- const l = label.padEnd(16);
1422
- const c = String(cnt).padStart(3);
1423
- const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
1424
- const visible = 16 + 3 + 2 + type.length;
1425
- const pad = Math.max(0, inner - visible);
1426
- if (isSmall) {
1427
- console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
1428
- } else {
1429
- console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
1430
- }
1431
- }
1432
- if (!isSmall) {
1433
- console.log(boxBot(w));
1434
- }
1435
- console.log("");
1436
- const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
1437
- if (!isSmall) {
1438
- console.log(boxTop(w));
1439
- console.log(boxLine(w, ui.text.bold(" To serve locally")));
1440
- console.log(boxSep(w));
1441
- const inner = w - 6;
1442
- const cmdLen = cdCmd.length;
1443
- const pad = Math.max(0, inner - cmdLen);
1444
- console.log(
1445
- " " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502")
1446
- );
1447
- console.log(boxBot(w));
1448
- } else {
1449
- console.log(ui.text.bold(" To serve locally:"));
1450
- console.log(` ${G(cdCmd)}`);
1451
- }
1452
- console.log("");
1453
- console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
1454
- console.log("");
1455
- }
1456
- var init_summary = __esm({
1457
- "src/exporter/summary.ts"() {
1458
- "use strict";
1459
- init_box();
1460
- init_theme();
1461
- }
1462
- });
1463
-
1464
- // src/cli/select.ts
1465
- import readline from "readline";
1466
- import { stdin, stdout } from "process";
1467
- async function select(question, options, defaultIndex = 0) {
1468
- const isTTY = stdin.isTTY && stdout.isTTY;
1469
- if (!isTTY) {
1470
- return fallbackPrompt(question, options, defaultIndex);
1471
- }
1472
- return arrowSelect(question, options, defaultIndex);
1473
- }
1474
- async function arrowSelect(question, options, defaultIndex) {
1475
- const panelTopRow = await queryCursorRow();
1476
- const width = Math.min(maxWidth(), 72);
1477
- const inner = width - 4;
1478
- const optionStartOffset = 3;
1479
- const lineCount = options.length + 4;
1480
- return new Promise((resolve) => {
1481
- const firstEnabled = options.findIndex((option) => !option.disabled);
1482
- let selected = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
1483
- if (selected < 0) selected = 0;
1484
- const move = (direction) => {
1485
- let next = selected + direction;
1486
- while (next >= 0 && next < options.length) {
1487
- if (!options[next].disabled) {
1488
- selected = next;
1489
- return;
1490
- }
1491
- next += direction;
1492
- }
1493
- };
1494
- const render = (initial = false) => {
1495
- if (!initial) {
1496
- stdout.write(`\x1B[${lineCount}A`);
1497
- stdout.write("\x1B[J");
1498
- }
1499
- console.log(boxTop(width));
1500
- console.log(
1501
- boxLine(width, centerText(`${ui.primary("\u25CF")} ${ui.text.bold(question)}`, inner))
1502
- );
1503
- console.log(boxSep(width));
1504
- for (let i = 0; i < options.length; i++) {
1505
- console.log(
1506
- boxLine(width, centerText(renderOption(options[i], i === selected, inner), inner))
1507
- );
1508
- }
1509
- console.log(boxBot(width));
1510
- };
1511
- const choose = () => {
1512
- cleanup();
1513
- stdout.write(`\x1B[${lineCount}A`);
1514
- stdout.write("\x1B[J");
1515
- console.log(
1516
- ` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(stripAnsi(options[selected].label))}
1517
- `
1518
- );
1519
- resolve(options[selected].value);
1520
- };
1521
- const onMouseData = (chunk) => {
1522
- const mouse = parseMouseEvent(chunk);
1523
- if (!mouse) return;
1524
- if (mouse.kind === "wheel-up") {
1525
- move(-1);
1526
- render();
1527
- return;
1528
- }
1529
- if (mouse.kind === "wheel-down") {
1530
- move(1);
1531
- render();
1532
- return;
1533
- }
1534
- if (panelTopRow === null) return;
1535
- const idx = mouse.y - panelTopRow - optionStartOffset;
1536
- if (idx < 0 || idx >= options.length || options[idx].disabled) return;
1537
- if (selected !== idx) {
1538
- selected = idx;
1539
- render();
1540
- }
1541
- if (mouse.kind === "click") {
1542
- choose();
1543
- }
1544
- };
1545
- const cleanup = () => {
1546
- disableMouse();
1547
- stdin.setRawMode(false);
1548
- stdin.removeListener("keypress", onKeypress);
1549
- stdin.removeListener("data", onMouseData);
1550
- stdin.pause();
1551
- };
1552
- render(true);
1553
- readline.emitKeypressEvents(stdin);
1554
- stdin.setRawMode(true);
1555
- enableInteractiveInput();
1556
- const onKeypress = (_str, key) => {
1557
- if (!key) return;
1558
- if (key.name === "up" && selected > 0) {
1559
- move(-1);
1560
- render();
1561
- } else if (key.name === "down" && selected < options.length - 1) {
1562
- move(1);
1563
- render();
1564
- } else if (key.name === "return") {
1565
- choose();
1566
- } else if (key.ctrl && key.name === "c" || key.name === "escape") {
1567
- cleanup();
1568
- process.exit(0);
1569
- }
1570
- };
1571
- stdin.resume();
1572
- stdin.on("data", onMouseData);
1573
- stdin.on("keypress", onKeypress);
1574
- });
1575
- }
1576
- async function fallbackPrompt(question, options, defaultIndex) {
1577
- return new Promise((resolve) => {
1578
- const rl = readline.createInterface({ input: stdin, output: stdout });
1579
- const firstEnabled = options.findIndex((option) => !option.disabled);
1580
- const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
1581
- console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}
1582
- `);
1583
- for (let i = 0; i < options.length; i++) {
1584
- const marker = i === enabledDefault ? ui.success(" \u25C6") : " ";
1585
- const label = options[i].disabled ? ui.muted(stripAnsi(options[i].label)) : ui.text(options[i].label);
1586
- console.log(` ${ui.muted(`[${i + 1}]`)}${marker} ${label}`);
1587
- }
1588
- console.log("");
1589
- const def = String(enabledDefault + 1);
1590
- const ask = () => {
1591
- rl.question(
1592
- ` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
1593
- (answer) => {
1594
- const trimmed = answer.trim();
1595
- if (!trimmed) {
1596
- rl.close();
1597
- const label = stripAnsi(options[enabledDefault].label);
1598
- console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1599
- `);
1600
- resolve(options[enabledDefault].value);
1601
- return;
1602
- }
1603
- const idx = parseInt(trimmed, 10);
1604
- if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
1605
- rl.close();
1606
- const label = stripAnsi(options[idx - 1].label);
1607
- console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1608
- `);
1609
- resolve(options[idx - 1].value);
1610
- } else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
1611
- console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
1612
- `);
1613
- ask();
1614
- } else {
1615
- console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
1616
- `);
1617
- ask();
1618
- }
1619
- }
1620
- );
1621
- };
1622
- ask();
1623
- });
1624
- }
1625
- function renderOption(option, selected, width) {
1626
- const plain = truncatePlain(stripAnsi(option.label), Math.max(10, width - 12));
1627
- if (option.disabled) {
1628
- return `${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")}`;
2023
+ console.log(boxTop(w));
2024
+ console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
2025
+ console.log(boxSep(w));
2026
+ } else {
2027
+ console.log(ui.text.bold(" Export Summary:"));
1629
2028
  }
1630
- if (selected) {
1631
- return `${ui.primary("\u203A")} ${ui.primary("[")} ${ui.text.bold(plain)} ${ui.primary("]")} ${ui.primary("\u2039")}`;
2029
+ for (const [label, cnt, type, color] of entries) {
2030
+ if (cnt === 0) continue;
2031
+ const inner = w - 4;
2032
+ const l = label.padEnd(16);
2033
+ const c = String(cnt).padStart(3);
2034
+ const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
2035
+ const visible = 16 + 3 + 2 + type.length;
2036
+ const pad = Math.max(0, inner - visible);
2037
+ if (isSmall) {
2038
+ console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
2039
+ } else {
2040
+ console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
2041
+ }
1632
2042
  }
1633
- return `${ui.muted(" ")} ${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")} ${ui.muted(" ")}`;
1634
- }
1635
- function enableInteractiveInput() {
1636
- stdout.write("\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1003h\x1B[?1006h");
1637
- }
1638
- function disableMouse() {
1639
- stdout.write("\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?25h");
1640
- }
1641
- function parseMouseEvent(chunk) {
1642
- const text = chunk.toString("utf-8");
1643
- const match = text.match(/\x1B\[<(\d+);(\d+);(\d+)([mM])/);
1644
- if (!match) return null;
1645
- const code = Number(match[1]);
1646
- const x = Number(match[2]);
1647
- const y = Number(match[3]);
1648
- const state = match[4];
1649
- if (code === 64) return { kind: "wheel-up", x, y };
1650
- if (code === 65) return { kind: "wheel-down", x, y };
1651
- if (state === "m") return { kind: "click", x, y };
1652
- if ((code & 32) === 32 || code === 35) return { kind: "hover", x, y };
1653
- if ((code & 3) === 0) return { kind: "hover", x, y };
1654
- return null;
1655
- }
1656
- function queryCursorRow() {
1657
- if (!stdin.isTTY || !stdout.isTTY) return Promise.resolve(null);
1658
- return new Promise((resolve) => {
1659
- const wasRaw = stdin.isRaw;
1660
- let done = false;
1661
- const finish = (row) => {
1662
- if (done) return;
1663
- done = true;
1664
- clearTimeout(timer);
1665
- stdin.removeListener("data", onData);
1666
- if (!wasRaw) stdin.setRawMode(false);
1667
- resolve(row);
1668
- };
1669
- const onData = (chunk) => {
1670
- const match = chunk.toString("utf-8").match(/\x1B\[(\d+);\d+R/);
1671
- if (!match) return;
1672
- finish(Number(match[1]));
1673
- };
1674
- const timer = setTimeout(() => finish(null), 80);
1675
- stdin.setRawMode(true);
1676
- stdin.resume();
1677
- stdin.on("data", onData);
1678
- stdout.write("\x1B[6n");
1679
- });
2043
+ if (!isSmall) {
2044
+ console.log(boxBot(w));
2045
+ }
2046
+ console.log("");
2047
+ const cdCmd = "cd " + path4.basename(exporter.outDir);
2048
+ const runCmd = "node serve.js";
2049
+ if (!isSmall) {
2050
+ console.log(boxTop(w));
2051
+ console.log(boxLine(w, ui.text.bold(" To serve locally")));
2052
+ console.log(boxSep(w));
2053
+ console.log(boxRow(w, "Open", cdCmd));
2054
+ console.log(boxRow(w, "Run", runCmd));
2055
+ console.log(boxBot(w));
2056
+ } else {
2057
+ console.log(ui.text.bold(" To serve locally:"));
2058
+ console.log(` ${G(cdCmd)}`);
2059
+ console.log(` ${G(runCmd)}`);
2060
+ }
2061
+ console.log("");
2062
+ console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
2063
+ console.log("");
1680
2064
  }
1681
- var init_select = __esm({
1682
- "src/cli/select.ts"() {
2065
+ var init_summary = __esm({
2066
+ "src/exporter/summary.ts"() {
1683
2067
  "use strict";
1684
2068
  init_box();
1685
2069
  init_theme();
@@ -1693,17 +2077,10 @@ import { stdin as stdin2, stdout as stdout2 } from "process";
1693
2077
  import { spawn } from "child_process";
1694
2078
  async function runAiPromptAssistant(exporter) {
1695
2079
  if (!stdin2.isTTY || !stdout2.isTTY) return;
1696
- printConvertPanel(exporter);
1697
- const action = await select(
1698
- "Framer Export AI Convert",
1699
- [
1700
- { label: `${buttonLabel("Convert")} open AI prompt modal`, value: "convert" },
1701
- { label: `${buttonLabel("Skip")} finish export`, value: "skip" }
1702
- ],
1703
- 0
1704
- );
1705
- if (action === "skip") return;
1706
- printAssistantModal("AI conversion prompt", [
2080
+ const serveCommand = buildServeCommand(exporter);
2081
+ const shouldConvert = await runExportCompletePrompt(exporter, serveCommand);
2082
+ if (!shouldConvert) return;
2083
+ printAssistantModal(`${ui.text.bold("AI conversion prompt")} ${ui.error("BETA")}`, [
1707
2084
  "Choose a target stack, AI tool, and conversion situation.",
1708
2085
  "Mouse clicks are supported in the terminal when available.",
1709
2086
  "A detailed prompt file will be generated inside the export folder."
@@ -1732,16 +2109,8 @@ async function runAiPromptAssistant(exporter) {
1732
2109
  const promptPath = path5.join(aiDir, `${aiTool.id}-${target.id}-${goal.id}-prompt.md`);
1733
2110
  await fs4.mkdir(aiDir, { recursive: true });
1734
2111
  await fs4.writeFile(promptPath, prompt, "utf-8");
1735
- printPromptResult(promptPath, target, aiTool, goal);
1736
- const promptAction = await select(
1737
- "Prompt actions",
1738
- [
1739
- { label: `${buttonLabel("Copy prompt")} clipboard`, value: "copy" },
1740
- { label: `${buttonLabel("Done")} keep file only`, value: "done" }
1741
- ],
1742
- 0
1743
- );
1744
- if (promptAction === "copy") {
2112
+ const copyPrompt = await runPromptReadyPrompt(promptPath, target, aiTool, goal);
2113
+ if (copyPrompt) {
1745
2114
  try {
1746
2115
  await copyToClipboard(prompt);
1747
2116
  console.log(` ${ui.success("\u2713")} ${ui.text.bold("Prompt copied to clipboard")}
@@ -1799,27 +2168,97 @@ function centerText2(text, width) {
1799
2168
  const left = Math.floor((width - visible) / 2);
1800
2169
  return " ".repeat(left) + text + " ".repeat(width - visible - left);
1801
2170
  }
1802
- function printConvertPanel(exporter) {
1803
- const w = maxWidth();
1804
- const inner = w - 4;
1805
- const rows = [
1806
- centerText2(`${ui.text.bold("AI Convert")} ${ui.muted("BETA")}`, inner),
1807
- centerText2(ui.muted("Generate a conversion prompt after the export."), inner),
1808
- centerText2(`${buttonLabel("Convert")} ${ui.muted("or")} ${buttonLabel("Skip")}`, inner),
1809
- centerText2(ui.muted(path5.basename(exporter.outDir)), inner)
1810
- ];
1811
- console.log("");
1812
- console.log(boxTop(w));
1813
- for (const row of rows) console.log(boxLine(w, row));
1814
- console.log(boxBot(w));
1815
- console.log("");
2171
+ function buildServeCommand(exporter) {
2172
+ return `cd ${path5.basename(exporter.outDir)} && node serve.js`;
2173
+ }
2174
+ async function runExportCompletePrompt(exporter, serveCommand) {
2175
+ let copyServeCommand = false;
2176
+ while (true) {
2177
+ const action = await select(
2178
+ "Export complete",
2179
+ [
2180
+ {
2181
+ label: `${checkboxLabel(copyServeCommand)} Copy serve command when finishing`,
2182
+ value: "toggle-copy"
2183
+ },
2184
+ {
2185
+ label: `${buttonLabel("Convert to AI code")} ${ui.error("BETA")}`,
2186
+ value: "convert"
2187
+ },
2188
+ { label: buttonLabel("Finish"), value: "finish" }
2189
+ ],
2190
+ 2,
2191
+ {
2192
+ headerLines: [
2193
+ `Output: ${path5.basename(exporter.outDir)}`,
2194
+ `Run: ${serveCommand}`,
2195
+ "Use Convert to open the AI conversion assistant."
2196
+ ],
2197
+ footer: "enter select \xB7 checkbox toggles copy \xB7 mouse hover/click"
2198
+ }
2199
+ );
2200
+ if (action === "toggle-copy") {
2201
+ copyServeCommand = !copyServeCommand;
2202
+ continue;
2203
+ }
2204
+ if (action === "finish") {
2205
+ if (copyServeCommand) {
2206
+ try {
2207
+ await copyToClipboard(serveCommand);
2208
+ console.log(` ${ui.success("\u2713")} ${ui.text.bold("Serve command copied")}
2209
+ `);
2210
+ } catch (error) {
2211
+ console.log(
2212
+ ` ${ui.warning("!")} ${ui.warning("Clipboard copy unavailable:")} ${ui.muted(error.message)}
2213
+ `
2214
+ );
2215
+ }
2216
+ }
2217
+ return false;
2218
+ }
2219
+ return true;
2220
+ }
2221
+ }
2222
+ async function runPromptReadyPrompt(promptPath, target, aiTool, goal) {
2223
+ const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
2224
+ let copyPrompt = false;
2225
+ while (true) {
2226
+ const action = await select(
2227
+ `AI Prompt Ready ${ui.error("BETA")}`,
2228
+ [
2229
+ {
2230
+ label: `${checkboxLabel(copyPrompt)} Copy prompt when finishing`,
2231
+ value: "toggle-copy"
2232
+ },
2233
+ { label: buttonLabel("Finish"), value: "finish" }
2234
+ ],
2235
+ 1,
2236
+ {
2237
+ headerLines: [
2238
+ `Tool: ${aiTool.displayName}`,
2239
+ `Stack: ${target.label}`,
2240
+ `Mode: ${goal.label}`,
2241
+ `File: ${relPath}`
2242
+ ],
2243
+ footer: "enter finish \xB7 checkbox toggles copy \xB7 mouse hover/click"
2244
+ }
2245
+ );
2246
+ if (action === "toggle-copy") {
2247
+ copyPrompt = !copyPrompt;
2248
+ continue;
2249
+ }
2250
+ return copyPrompt;
2251
+ }
2252
+ }
2253
+ function checkboxLabel(checked) {
2254
+ return checked ? ui.success("\u2611") : ui.muted("\u2610");
1816
2255
  }
1817
2256
  function printAssistantModal(title, lines) {
1818
2257
  const w = maxWidth();
1819
2258
  const inner = w - 4;
1820
2259
  console.log("");
1821
2260
  console.log(boxTop(w));
1822
- console.log(boxLine(w, centerText2(`${ui.primary("\u25C6")} ${ui.text.bold(title)}`, inner)));
2261
+ console.log(boxLine(w, centerText2(`${ui.primary("\u25C6")} ${title}`, inner)));
1823
2262
  console.log(boxSep(w));
1824
2263
  for (const line of lines) {
1825
2264
  console.log(boxLine(w, centerText2(ui.muted(line), inner)));
@@ -2002,44 +2441,6 @@ function pipeToCommand(command, args, input) {
2002
2441
  child.stdin?.end(input);
2003
2442
  });
2004
2443
  }
2005
- function printPromptResult(promptPath, target, aiTool, goal) {
2006
- const w = maxWidth();
2007
- const isSmall = w < 50;
2008
- const inner = w - 4;
2009
- const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
2010
- console.log("");
2011
- if (!isSmall) {
2012
- console.log(boxTop(w));
2013
- console.log(
2014
- boxLine(
2015
- w,
2016
- centerText2(
2017
- `${ui.success("\u2713")} ${ui.text.bold("AI Prompt Ready")} ${ui.muted("BETA")}`,
2018
- inner
2019
- )
2020
- )
2021
- );
2022
- console.log(boxSep(w));
2023
- console.log(
2024
- boxLine(w, centerText2(`${ui.muted("Tool")} ${ui.primary(aiTool.displayName)}`, inner))
2025
- );
2026
- console.log(boxLine(w, centerText2(`${ui.muted("Stack")} ${ui.primary(target.label)}`, inner)));
2027
- console.log(boxLine(w, centerText2(`${ui.muted("Mode")} ${ui.primary(goal.label)}`, inner)));
2028
- console.log(boxLine(w, centerText2(`${ui.muted("File")} ${ui.primary(relPath)}`, inner)));
2029
- console.log(boxSep(w));
2030
- console.log(
2031
- boxLine(w, centerText2(`${buttonLabel("Copy prompt")} ${buttonLabel("Done")}`, inner))
2032
- );
2033
- console.log(boxBot(w));
2034
- } else {
2035
- console.log(ui.text.bold(" AI Prompt Ready - BETA"));
2036
- console.log(` Tool: ${ui.primary(aiTool.displayName)}`);
2037
- console.log(` Stack: ${ui.primary(target.label)}`);
2038
- console.log(` Situation: ${ui.primary(goal.label)}`);
2039
- console.log(` File: ${ui.primary(relPath)}`);
2040
- }
2041
- console.log("");
2042
- }
2043
2444
  var IMPORTANT_DIRS, AI_TOOLS, TARGETS, GOALS;
2044
2445
  var init_prompt_assistant = __esm({
2045
2446
  "src/ai/prompt-assistant.ts"() {
@@ -2166,82 +2567,6 @@ var init_prompt_assistant = __esm({
2166
2567
  }
2167
2568
  });
2168
2569
 
2169
- // src/cli/cooking.ts
2170
- import chalk4 from "chalk";
2171
- var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, CookingSpinner;
2172
- var init_cooking = __esm({
2173
- "src/cli/cooking.ts"() {
2174
- "use strict";
2175
- init_theme();
2176
- SHINE_WIDTH = 10;
2177
- FRAME_INTERVAL = 80;
2178
- SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2179
- CookingSpinner = class {
2180
- interval = null;
2181
- frame = 0;
2182
- phase = "";
2183
- active = false;
2184
- start(phase = "") {
2185
- this.phase = phase;
2186
- this.frame = 0;
2187
- this.active = true;
2188
- this.draw();
2189
- this.interval = setInterval(() => {
2190
- this.frame++;
2191
- this.draw();
2192
- }, FRAME_INTERVAL);
2193
- }
2194
- update(phase) {
2195
- this.phase = phase;
2196
- }
2197
- log(message) {
2198
- if (this.active) {
2199
- process.stdout.write("\r\x1B[2K");
2200
- }
2201
- process.stdout.write(message + "\n");
2202
- if (this.active) {
2203
- this.draw();
2204
- }
2205
- }
2206
- stop() {
2207
- this.active = false;
2208
- if (this.interval) {
2209
- clearInterval(this.interval);
2210
- this.interval = null;
2211
- }
2212
- process.stdout.write("\r\x1B[2K");
2213
- }
2214
- draw() {
2215
- if (!this.active) return;
2216
- const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
2217
- const shimmer = this.renderShimmer("Exporting");
2218
- const frameStr = ui.primary(spinner);
2219
- const phaseStr = this.phase ? ` ${ui.muted(this.limitLen(this.phase, 52))}` : "";
2220
- process.stdout.write(`\r\x1B[2K ${frameStr} ${shimmer}${phaseStr}`);
2221
- }
2222
- renderShimmer(text) {
2223
- let result = "";
2224
- const pos = this.frame % (text.length + SHINE_WIDTH * 2) - SHINE_WIDTH;
2225
- for (let i = 0; i < text.length; i++) {
2226
- const dist = Math.abs(i - pos);
2227
- if (dist < SHINE_WIDTH) {
2228
- const t = 1 - dist / SHINE_WIDTH;
2229
- const g = Math.floor(160 + t * 95);
2230
- result += chalk4.rgb(250, Math.min(255, g), Math.min(255, Math.floor(g * 0.75)))(text[i]);
2231
- } else {
2232
- result += ui.muted(text[i]);
2233
- }
2234
- }
2235
- return result;
2236
- }
2237
- limitLen(s, max) {
2238
- if (s.length <= max) return s;
2239
- return s.slice(0, max - 1) + "\u2026";
2240
- }
2241
- };
2242
- }
2243
- });
2244
-
2245
2570
  // src/exporter/index.ts
2246
2571
  var exporter_exports = {};
2247
2572
  __export(exporter_exports, {
@@ -2266,11 +2591,20 @@ function deriveOutputName(url, platformName) {
2266
2591
  } else {
2267
2592
  siteName = hostname.replace(/\./g, "-");
2268
2593
  }
2269
- return `${platformName}-${siteName}`;
2594
+ const cleanName = siteName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "site";
2595
+ return `${platformName}-${cleanName}-${randomOutputSuffix()}`;
2270
2596
  } catch {
2271
- return "framer-export-output";
2597
+ return `framer-export-output-${randomOutputSuffix()}`;
2272
2598
  }
2273
2599
  }
2600
+ function randomOutputSuffix() {
2601
+ const adjectives = ["clean", "bright", "swift", "sharp", "fresh", "solid", "tidy", "prime"];
2602
+ const nouns = ["mirror", "export", "site", "build", "copy", "page", "stack", "bundle"];
2603
+ const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
2604
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
2605
+ const id = Math.random().toString(36).slice(2, 6);
2606
+ return `${adjective}-${noun}-${id}`;
2607
+ }
2274
2608
  var FramerExporter;
2275
2609
  var init_exporter = __esm({
2276
2610
  "src/exporter/index.ts"() {
@@ -2463,71 +2797,117 @@ async function runSetup(legacyMode = false) {
2463
2797
  ` ${ui.muted("Export Framer, Webflow, and Wix sites into a clean local mirror.")}
2464
2798
  `
2465
2799
  );
2466
- const rl = readline2.createInterface({ input: stdin3, output: stdout3 });
2467
- const ask = async (question, defaultVal) => {
2800
+ const rl = legacyMode ? readline2.createInterface({ input: stdin3, output: stdout3 }) : null;
2801
+ const ask = async (question, defaultVal, headerLines = []) => {
2802
+ if (!legacyMode) {
2803
+ return promptInput(question, defaultVal || "", { headerLines });
2804
+ }
2468
2805
  const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
2469
2806
  const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
2470
2807
  const answer = await rl.question(prompt);
2471
2808
  return answer.trim() || defaultVal || "";
2472
2809
  };
2473
- drawHeader("Step 1 : Site URL");
2810
+ if (legacyMode) drawHeader("Step 1 : Site URL");
2474
2811
  let siteUrl = "";
2812
+ let urlError = "";
2475
2813
  while (!siteUrl) {
2476
- const input = await ask("Enter the site URL");
2814
+ const input = await ask(
2815
+ "Enter the site URL",
2816
+ "",
2817
+ ["Step 1 : Site URL", urlError].filter(Boolean)
2818
+ );
2477
2819
  try {
2478
2820
  new URL4(input);
2479
2821
  siteUrl = input;
2822
+ urlError = "";
2480
2823
  } catch {
2481
- console.log(
2482
- ` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
2483
- `
2484
- );
2824
+ urlError = "Invalid URL. Enter a valid URL (https://...)";
2825
+ if (legacyMode) {
2826
+ console.log(` ${ui.error("\u2717")} ${ui.error(urlError)}
2827
+ `);
2828
+ }
2485
2829
  }
2486
2830
  }
2487
- console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk6.underline(siteUrl)}
2831
+ if (legacyMode) {
2832
+ console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk6.underline(siteUrl)}
2488
2833
  `);
2489
- drawHeader("Step 2 : Platform");
2490
- const detected = detectPlatform(siteUrl);
2491
- let platformName;
2834
+ }
2835
+ let platformName = null;
2492
2836
  if (legacyMode) {
2837
+ drawHeader("Step 2 : Platform");
2838
+ const detected = detectPlatform(siteUrl);
2493
2839
  console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
2494
2840
  const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
2495
2841
  platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
2496
2842
  console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
2497
2843
  `);
2498
2844
  } else {
2499
- rl.close();
2500
- const platforms = [
2501
- {
2502
- label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
2503
- value: "framer"
2504
- },
2505
- {
2506
- label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
2507
- value: "webflow"
2508
- },
2509
- { label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
2510
- ];
2511
- const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
2512
- platformName = await select(
2513
- "Select platform",
2514
- platforms,
2515
- Math.max(0, defaultIdx)
2516
- );
2845
+ while (!platformName) {
2846
+ const detected = detectPlatform(siteUrl);
2847
+ const platforms = [
2848
+ {
2849
+ label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
2850
+ value: "framer"
2851
+ },
2852
+ {
2853
+ label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
2854
+ value: "webflow"
2855
+ },
2856
+ { label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
2857
+ ];
2858
+ const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
2859
+ const platformChoice = await select("Select platform", platforms, Math.max(0, defaultIdx), {
2860
+ headerLines: [`URL: ${siteUrl}`],
2861
+ actions: [{ label: "Modify URL", value: "modify-url" }],
2862
+ footer: "tab focus button \xB7 mouse hover/click \xB7 enter select"
2863
+ });
2864
+ if (platformChoice === "modify-url") {
2865
+ siteUrl = "";
2866
+ urlError = "";
2867
+ while (!siteUrl) {
2868
+ const input = await ask(
2869
+ "Modify site URL",
2870
+ "",
2871
+ ["Step 1 : Site URL", urlError].filter(Boolean)
2872
+ );
2873
+ try {
2874
+ new URL4(input);
2875
+ siteUrl = input;
2876
+ urlError = "";
2877
+ } catch {
2878
+ urlError = "Invalid URL. Enter a valid URL (https://...)";
2879
+ }
2880
+ }
2881
+ continue;
2882
+ }
2883
+ platformName = platformChoice;
2884
+ }
2517
2885
  }
2518
- const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
2519
- const ask2 = async (question, defaultVal) => {
2886
+ if (!platformName) {
2887
+ throw new Error("Platform selection failed");
2888
+ }
2889
+ const rl2 = legacyMode ? rl : null;
2890
+ const ask2 = async (question, defaultVal, headerLines = []) => {
2891
+ if (!legacyMode) {
2892
+ return promptInput(question, defaultVal || "", { headerLines });
2893
+ }
2520
2894
  const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
2521
2895
  const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
2522
2896
  const answer = await rl2.question(prompt);
2523
2897
  return answer.trim() || defaultVal || "";
2524
2898
  };
2525
2899
  const derivedName = deriveOutputName(siteUrl, platformName);
2526
- drawHeader("Step 3 : Output Directory");
2527
- const outDir = await ask2("Output directory", "./" + derivedName);
2528
- console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
2900
+ if (legacyMode) drawHeader("Step 3 : Output Directory");
2901
+ const outDir = await ask2("Output directory", "./" + derivedName, [
2902
+ "Step 3 : Output Directory",
2903
+ `URL: ${siteUrl}`,
2904
+ `Platform: ${platformName}`
2905
+ ]);
2906
+ if (legacyMode) {
2907
+ console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
2529
2908
  `);
2530
- drawHeader("Step 4 : Options");
2909
+ }
2910
+ if (legacyMode) drawHeader("Step 4 : Options");
2531
2911
  let prettyPrint;
2532
2912
  let concurrency;
2533
2913
  let includeSubpages;
@@ -2549,7 +2929,6 @@ async function runSetup(legacyMode = false) {
2549
2929
  console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
2550
2930
  `);
2551
2931
  } else {
2552
- rl2.close();
2553
2932
  const prettyVal = await select("Pretty-print JS files?", [
2554
2933
  { label: "Yes", value: "yes" },
2555
2934
  { label: "No", value: "no" }
@@ -2640,6 +3019,7 @@ var init_setup = __esm({
2640
3019
  init_package();
2641
3020
  import path8 from "path";
2642
3021
  import { URL as URL5 } from "url";
3022
+ import { spawnSync } from "child_process";
2643
3023
 
2644
3024
  // src/cli/help.ts
2645
3025
  init_banner();
@@ -2702,6 +3082,7 @@ function showHelp() {
2702
3082
 
2703
3083
  // src/cli/index.ts
2704
3084
  init_banner();
3085
+ init_cooking();
2705
3086
 
2706
3087
  // src/cli/update-check.ts
2707
3088
  import https from "https";
@@ -2718,7 +3099,7 @@ async function checkForUpdates(currentVersion) {
2718
3099
  const json = JSON.parse(data);
2719
3100
  const latest = json.version;
2720
3101
  if (!latest) return resolve(null);
2721
- if (latest !== currentVersion) return resolve(latest);
3102
+ if (isNewerVersion(latest, currentVersion)) return resolve(latest);
2722
3103
  resolve(null);
2723
3104
  } catch {
2724
3105
  resolve(null);
@@ -2733,8 +3114,23 @@ async function checkForUpdates(currentVersion) {
2733
3114
  });
2734
3115
  });
2735
3116
  }
3117
+ function isNewerVersion(latest, current) {
3118
+ const latestParts = parseVersion(latest);
3119
+ const currentParts = parseVersion(current);
3120
+ for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
3121
+ const latestPart = latestParts[i] ?? 0;
3122
+ const currentPart = currentParts[i] ?? 0;
3123
+ if (latestPart > currentPart) return true;
3124
+ if (latestPart < currentPart) return false;
3125
+ }
3126
+ return false;
3127
+ }
3128
+ function parseVersion(version) {
3129
+ return version.replace(/^v/, "").split(/[.-]/).map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
3130
+ }
2736
3131
 
2737
3132
  // src/cli/index.ts
3133
+ init_select();
2738
3134
  init_theme();
2739
3135
  var VERSION = package_default.version;
2740
3136
  function extractFlag(args, flag) {
@@ -2750,6 +3146,56 @@ function hasFlag(args, flag) {
2750
3146
  args.splice(idx, 1);
2751
3147
  return true;
2752
3148
  }
3149
+ async function showUpdateNotice() {
3150
+ const latest = await checkForUpdates(VERSION);
3151
+ if (!latest) return;
3152
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3153
+ console.log("");
3154
+ console.log(
3155
+ ` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`
3156
+ );
3157
+ console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
3158
+ console.log("");
3159
+ return;
3160
+ }
3161
+ const action = await select(
3162
+ "Update available",
3163
+ [
3164
+ { label: "Continue without updating", value: "continue" },
3165
+ { label: "Update now", value: "update" }
3166
+ ],
3167
+ 0,
3168
+ {
3169
+ headerLines: [
3170
+ `Current version: ${VERSION}`,
3171
+ `Latest version: ${latest}`,
3172
+ "You can continue now and update later."
3173
+ ],
3174
+ footer: "enter continue \xB7 mouse hover/click"
3175
+ }
3176
+ );
3177
+ if (action === "update") {
3178
+ console.log(
3179
+ ` ${ui.primary("Updating:")} ${ui.primarySoft("npm i -g framer-export@latest")}
3180
+ `
3181
+ );
3182
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
3183
+ const result = spawnSync(npmCommand, ["i", "-g", "framer-export@latest"], {
3184
+ stdio: "inherit"
3185
+ });
3186
+ if (result.status === 0) {
3187
+ console.log(`
3188
+ ${ui.success("\u2713")} Updated. Re-run your command to use the new version.
3189
+ `);
3190
+ process.exit(0);
3191
+ }
3192
+ console.log(
3193
+ `
3194
+ ${ui.error("\u2717")} Update failed. Run manually: ${ui.primarySoft("npm i -g framer-export@latest")}
3195
+ `
3196
+ );
3197
+ }
3198
+ }
2753
3199
  async function main() {
2754
3200
  const args = process.argv.slice(2);
2755
3201
  if (args.includes("--version") || args.includes("-v")) {
@@ -2777,19 +3223,12 @@ async function main() {
2777
3223
  console.log("");
2778
3224
  process.exit(0);
2779
3225
  }
2780
- checkForUpdates(VERSION).then((latest) => {
2781
- if (!latest) return;
2782
- console.log("");
2783
- console.log(
2784
- ` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`
2785
- );
2786
- console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
2787
- console.log("");
2788
- });
2789
3226
  if (args.includes("--help") || args.includes("-h")) {
2790
3227
  showHelp();
2791
3228
  process.exit(0);
2792
3229
  }
3230
+ await showLoadingIntro(VERSION);
3231
+ await showUpdateNotice();
2793
3232
  if (args.includes("--setup")) {
2794
3233
  hasFlag(args, "--setup");
2795
3234
  const legacyMode = hasFlag(args, "--legacy-mode");