framer-export 4.3.10 → 4.4.1

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.1",
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
  }
@@ -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";
@@ -1392,294 +2003,69 @@ async function printSummary(exporter) {
1392
2003
  const G2 = ui.primarySoft;
1393
2004
  const C2 = ui.secondary;
1394
2005
  const Y = ui.accent;
1395
- const O = ui.warning;
1396
- const Br = ui.info;
1397
- const Gr = ui.muted;
1398
- const Gn = ui.success;
1399
- const entries = [
1400
- ["styles/", styles, "CSS", G],
1401
- ["scripts/vendor/", vendor, "JS vendor", G2],
1402
- ["scripts/modules/", scripts, "JS modules", C2],
1403
- ["assets/images/", imgs, "images", Y],
1404
- ["assets/videos/", videos, "videos", O],
1405
- ["assets/fonts/", fonts, "fonts", Br],
1406
- ["assets/misc/", misc, "misc", Gr],
1407
- ["data/", data, "data", Gr],
1408
- ["subpages/", subpages, "pages", Gn]
1409
- ];
1410
- console.log("");
1411
- 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}`);
2006
+ const O = ui.warning;
2007
+ const Br = ui.info;
2008
+ const Gr = ui.muted;
2009
+ const Gn = ui.success;
2010
+ const entries = [
2011
+ ["styles/", styles, "CSS", G],
2012
+ ["scripts/vendor/", vendor, "JS vendor", G2],
2013
+ ["scripts/modules/", scripts, "JS modules", C2],
2014
+ ["assets/images/", imgs, "images", Y],
2015
+ ["assets/videos/", videos, "videos", O],
2016
+ ["assets/fonts/", fonts, "fonts", Br],
2017
+ ["assets/misc/", misc, "misc", Gr],
2018
+ ["data/", data, "data", Gr],
2019
+ ["subpages/", subpages, "pages", Gn]
2020
+ ];
2021
+ console.log("");
2022
+ if (!isSmall) {
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:"));
2028
+ }
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"));
1587
2041
  }
1588
- console.log("");
1589
- const def = String(enabledDefault + 1);
1590
- const ask = () => {
1591
- rl.question(
1592
- ` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
1593
- (answer) => {
1594
- const trimmed = answer.trim();
1595
- if (!trimmed) {
1596
- rl.close();
1597
- const label = stripAnsi(options[enabledDefault].label);
1598
- console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1599
- `);
1600
- resolve(options[enabledDefault].value);
1601
- return;
1602
- }
1603
- const idx = parseInt(trimmed, 10);
1604
- if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
1605
- rl.close();
1606
- const label = stripAnsi(options[idx - 1].label);
1607
- console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1608
- `);
1609
- resolve(options[idx - 1].value);
1610
- } else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
1611
- console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
1612
- `);
1613
- ask();
1614
- } else {
1615
- console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
1616
- `);
1617
- ask();
1618
- }
1619
- }
1620
- );
1621
- };
1622
- ask();
1623
- });
1624
- }
1625
- function renderOption(option, selected, width) {
1626
- const plain = truncatePlain(stripAnsi(option.label), Math.max(10, width - 12));
1627
- if (option.disabled) {
1628
- return `${ui.border("[")} ${ui.muted(plain)} ${ui.border("]")}`;
1629
2042
  }
1630
- if (selected) {
1631
- return `${ui.primary("\u203A")} ${ui.primary("[")} ${ui.text.bold(plain)} ${ui.primary("]")} ${ui.primary("\u2039")}`;
2043
+ if (!isSmall) {
2044
+ console.log(boxBot(w));
1632
2045
  }
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
- });
2046
+ console.log("");
2047
+ const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
2048
+ if (!isSmall) {
2049
+ console.log(boxTop(w));
2050
+ console.log(boxLine(w, ui.text.bold(" To serve locally")));
2051
+ console.log(boxSep(w));
2052
+ const inner = w - 6;
2053
+ const cmdLen = cdCmd.length;
2054
+ const pad = Math.max(0, inner - cmdLen);
2055
+ console.log(
2056
+ " " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502")
2057
+ );
2058
+ console.log(boxBot(w));
2059
+ } else {
2060
+ console.log(ui.text.bold(" To serve locally:"));
2061
+ console.log(` ${G(cdCmd)}`);
2062
+ }
2063
+ console.log("");
2064
+ console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
2065
+ console.log("");
1680
2066
  }
1681
- var init_select = __esm({
1682
- "src/cli/select.ts"() {
2067
+ var init_summary = __esm({
2068
+ "src/exporter/summary.ts"() {
1683
2069
  "use strict";
1684
2070
  init_box();
1685
2071
  init_theme();
@@ -2166,82 +2552,6 @@ var init_prompt_assistant = __esm({
2166
2552
  }
2167
2553
  });
2168
2554
 
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
2555
  // src/exporter/index.ts
2246
2556
  var exporter_exports = {};
2247
2557
  __export(exporter_exports, {
@@ -2266,11 +2576,20 @@ function deriveOutputName(url, platformName) {
2266
2576
  } else {
2267
2577
  siteName = hostname.replace(/\./g, "-");
2268
2578
  }
2269
- return `${platformName}-${siteName}`;
2579
+ const cleanName = siteName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "site";
2580
+ return `${platformName}-${cleanName}-${randomOutputSuffix()}`;
2270
2581
  } catch {
2271
- return "framer-export-output";
2582
+ return `framer-export-output-${randomOutputSuffix()}`;
2272
2583
  }
2273
2584
  }
2585
+ function randomOutputSuffix() {
2586
+ const adjectives = ["clean", "bright", "swift", "sharp", "fresh", "solid", "tidy", "prime"];
2587
+ const nouns = ["mirror", "export", "site", "build", "copy", "page", "stack", "bundle"];
2588
+ const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
2589
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
2590
+ const id = Math.random().toString(36).slice(2, 6);
2591
+ return `${adjective}-${noun}-${id}`;
2592
+ }
2274
2593
  var FramerExporter;
2275
2594
  var init_exporter = __esm({
2276
2595
  "src/exporter/index.ts"() {
@@ -2463,71 +2782,117 @@ async function runSetup(legacyMode = false) {
2463
2782
  ` ${ui.muted("Export Framer, Webflow, and Wix sites into a clean local mirror.")}
2464
2783
  `
2465
2784
  );
2466
- const rl = readline2.createInterface({ input: stdin3, output: stdout3 });
2467
- const ask = async (question, defaultVal) => {
2785
+ const rl = legacyMode ? readline2.createInterface({ input: stdin3, output: stdout3 }) : null;
2786
+ const ask = async (question, defaultVal, headerLines = []) => {
2787
+ if (!legacyMode) {
2788
+ return promptInput(question, defaultVal || "", { headerLines });
2789
+ }
2468
2790
  const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
2469
2791
  const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
2470
2792
  const answer = await rl.question(prompt);
2471
2793
  return answer.trim() || defaultVal || "";
2472
2794
  };
2473
- drawHeader("Step 1 : Site URL");
2795
+ if (legacyMode) drawHeader("Step 1 : Site URL");
2474
2796
  let siteUrl = "";
2797
+ let urlError = "";
2475
2798
  while (!siteUrl) {
2476
- const input = await ask("Enter the site URL");
2799
+ const input = await ask(
2800
+ "Enter the site URL",
2801
+ "",
2802
+ ["Step 1 : Site URL", urlError].filter(Boolean)
2803
+ );
2477
2804
  try {
2478
2805
  new URL4(input);
2479
2806
  siteUrl = input;
2807
+ urlError = "";
2480
2808
  } catch {
2481
- console.log(
2482
- ` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
2483
- `
2484
- );
2809
+ urlError = "Invalid URL. Enter a valid URL (https://...)";
2810
+ if (legacyMode) {
2811
+ console.log(` ${ui.error("\u2717")} ${ui.error(urlError)}
2812
+ `);
2813
+ }
2485
2814
  }
2486
2815
  }
2487
- console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk6.underline(siteUrl)}
2816
+ if (legacyMode) {
2817
+ console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk6.underline(siteUrl)}
2488
2818
  `);
2489
- drawHeader("Step 2 : Platform");
2490
- const detected = detectPlatform(siteUrl);
2491
- let platformName;
2819
+ }
2820
+ let platformName = null;
2492
2821
  if (legacyMode) {
2822
+ drawHeader("Step 2 : Platform");
2823
+ const detected = detectPlatform(siteUrl);
2493
2824
  console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
2494
2825
  const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
2495
2826
  platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
2496
2827
  console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
2497
2828
  `);
2498
2829
  } 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
- );
2830
+ while (!platformName) {
2831
+ const detected = detectPlatform(siteUrl);
2832
+ const platforms = [
2833
+ {
2834
+ label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
2835
+ value: "framer"
2836
+ },
2837
+ {
2838
+ label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
2839
+ value: "webflow"
2840
+ },
2841
+ { label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
2842
+ ];
2843
+ const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
2844
+ const platformChoice = await select("Select platform", platforms, Math.max(0, defaultIdx), {
2845
+ headerLines: [`URL: ${siteUrl}`],
2846
+ actions: [{ label: "Modify URL", value: "modify-url" }],
2847
+ footer: "tab focus button \xB7 mouse hover/click \xB7 enter select"
2848
+ });
2849
+ if (platformChoice === "modify-url") {
2850
+ siteUrl = "";
2851
+ urlError = "";
2852
+ while (!siteUrl) {
2853
+ const input = await ask(
2854
+ "Modify site URL",
2855
+ "",
2856
+ ["Step 1 : Site URL", urlError].filter(Boolean)
2857
+ );
2858
+ try {
2859
+ new URL4(input);
2860
+ siteUrl = input;
2861
+ urlError = "";
2862
+ } catch {
2863
+ urlError = "Invalid URL. Enter a valid URL (https://...)";
2864
+ }
2865
+ }
2866
+ continue;
2867
+ }
2868
+ platformName = platformChoice;
2869
+ }
2517
2870
  }
2518
- const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
2519
- const ask2 = async (question, defaultVal) => {
2871
+ if (!platformName) {
2872
+ throw new Error("Platform selection failed");
2873
+ }
2874
+ const rl2 = legacyMode ? rl : null;
2875
+ const ask2 = async (question, defaultVal, headerLines = []) => {
2876
+ if (!legacyMode) {
2877
+ return promptInput(question, defaultVal || "", { headerLines });
2878
+ }
2520
2879
  const suffix = defaultVal ? chalk6.gray(` (${defaultVal})`) : "";
2521
2880
  const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
2522
2881
  const answer = await rl2.question(prompt);
2523
2882
  return answer.trim() || defaultVal || "";
2524
2883
  };
2525
2884
  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)}
2885
+ if (legacyMode) drawHeader("Step 3 : Output Directory");
2886
+ const outDir = await ask2("Output directory", "./" + derivedName, [
2887
+ "Step 3 : Output Directory",
2888
+ `URL: ${siteUrl}`,
2889
+ `Platform: ${platformName}`
2890
+ ]);
2891
+ if (legacyMode) {
2892
+ console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
2529
2893
  `);
2530
- drawHeader("Step 4 : Options");
2894
+ }
2895
+ if (legacyMode) drawHeader("Step 4 : Options");
2531
2896
  let prettyPrint;
2532
2897
  let concurrency;
2533
2898
  let includeSubpages;
@@ -2549,7 +2914,6 @@ async function runSetup(legacyMode = false) {
2549
2914
  console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
2550
2915
  `);
2551
2916
  } else {
2552
- rl2.close();
2553
2917
  const prettyVal = await select("Pretty-print JS files?", [
2554
2918
  { label: "Yes", value: "yes" },
2555
2919
  { label: "No", value: "no" }
@@ -2640,6 +3004,7 @@ var init_setup = __esm({
2640
3004
  init_package();
2641
3005
  import path8 from "path";
2642
3006
  import { URL as URL5 } from "url";
3007
+ import { spawnSync } from "child_process";
2643
3008
 
2644
3009
  // src/cli/help.ts
2645
3010
  init_banner();
@@ -2702,6 +3067,7 @@ function showHelp() {
2702
3067
 
2703
3068
  // src/cli/index.ts
2704
3069
  init_banner();
3070
+ init_cooking();
2705
3071
 
2706
3072
  // src/cli/update-check.ts
2707
3073
  import https from "https";
@@ -2718,7 +3084,7 @@ async function checkForUpdates(currentVersion) {
2718
3084
  const json = JSON.parse(data);
2719
3085
  const latest = json.version;
2720
3086
  if (!latest) return resolve(null);
2721
- if (latest !== currentVersion) return resolve(latest);
3087
+ if (isNewerVersion(latest, currentVersion)) return resolve(latest);
2722
3088
  resolve(null);
2723
3089
  } catch {
2724
3090
  resolve(null);
@@ -2733,8 +3099,23 @@ async function checkForUpdates(currentVersion) {
2733
3099
  });
2734
3100
  });
2735
3101
  }
3102
+ function isNewerVersion(latest, current) {
3103
+ const latestParts = parseVersion(latest);
3104
+ const currentParts = parseVersion(current);
3105
+ for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
3106
+ const latestPart = latestParts[i] ?? 0;
3107
+ const currentPart = currentParts[i] ?? 0;
3108
+ if (latestPart > currentPart) return true;
3109
+ if (latestPart < currentPart) return false;
3110
+ }
3111
+ return false;
3112
+ }
3113
+ function parseVersion(version) {
3114
+ return version.replace(/^v/, "").split(/[.-]/).map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
3115
+ }
2736
3116
 
2737
3117
  // src/cli/index.ts
3118
+ init_select();
2738
3119
  init_theme();
2739
3120
  var VERSION = package_default.version;
2740
3121
  function extractFlag(args, flag) {
@@ -2750,6 +3131,56 @@ function hasFlag(args, flag) {
2750
3131
  args.splice(idx, 1);
2751
3132
  return true;
2752
3133
  }
3134
+ async function showUpdateNotice() {
3135
+ const latest = await checkForUpdates(VERSION);
3136
+ if (!latest) return;
3137
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3138
+ console.log("");
3139
+ console.log(
3140
+ ` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`
3141
+ );
3142
+ console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
3143
+ console.log("");
3144
+ return;
3145
+ }
3146
+ const action = await select(
3147
+ "Update available",
3148
+ [
3149
+ { label: "Continue without updating", value: "continue" },
3150
+ { label: "Update now", value: "update" }
3151
+ ],
3152
+ 0,
3153
+ {
3154
+ headerLines: [
3155
+ `Current version: ${VERSION}`,
3156
+ `Latest version: ${latest}`,
3157
+ "You can continue now and update later."
3158
+ ],
3159
+ footer: "enter continue \xB7 mouse hover/click"
3160
+ }
3161
+ );
3162
+ if (action === "update") {
3163
+ console.log(
3164
+ ` ${ui.primary("Updating:")} ${ui.primarySoft("npm i -g framer-export@latest")}
3165
+ `
3166
+ );
3167
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
3168
+ const result = spawnSync(npmCommand, ["i", "-g", "framer-export@latest"], {
3169
+ stdio: "inherit"
3170
+ });
3171
+ if (result.status === 0) {
3172
+ console.log(`
3173
+ ${ui.success("\u2713")} Updated. Re-run your command to use the new version.
3174
+ `);
3175
+ process.exit(0);
3176
+ }
3177
+ console.log(
3178
+ `
3179
+ ${ui.error("\u2717")} Update failed. Run manually: ${ui.primarySoft("npm i -g framer-export@latest")}
3180
+ `
3181
+ );
3182
+ }
3183
+ }
2753
3184
  async function main() {
2754
3185
  const args = process.argv.slice(2);
2755
3186
  if (args.includes("--version") || args.includes("-v")) {
@@ -2777,19 +3208,12 @@ async function main() {
2777
3208
  console.log("");
2778
3209
  process.exit(0);
2779
3210
  }
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
3211
  if (args.includes("--help") || args.includes("-h")) {
2790
3212
  showHelp();
2791
3213
  process.exit(0);
2792
3214
  }
3215
+ await showLoadingIntro(VERSION);
3216
+ await showUpdateNotice();
2793
3217
  if (args.includes("--setup")) {
2794
3218
  hasFlag(args, "--setup");
2795
3219
  const legacyMode = hasFlag(args, "--legacy-mode");