framer-export 4.4.1 → 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.4.1",
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",
@@ -1603,12 +1603,13 @@ var init_template = __esm({
1603
1603
  "src/server/template.ts"() {
1604
1604
  "use strict";
1605
1605
  SERVE_SCRIPT = `#!/usr/bin/env node
1606
- const http = require('http');
1607
- const fs = require('fs');
1608
- 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';
1609
1610
 
1610
1611
  const PORT = process.env.PORT || 3000;
1611
- const ROOT = __dirname;
1612
+ const ROOT = path.dirname(fileURLToPath(import.meta.url));
1612
1613
 
1613
1614
  const MIME = {
1614
1615
  '.html': 'text/html; charset=utf-8',
@@ -1679,6 +1680,7 @@ const ANSI = {
1679
1680
  border: '\\x1b[38;2;72;72;72m',
1680
1681
  success: '\\x1b[38;2;127;216;143m',
1681
1682
  info: '\\x1b[38;2;86;182;194m',
1683
+ error: '\\x1b[38;2;224;108;117m',
1682
1684
  };
1683
1685
 
1684
1686
  function color(name, value) {
@@ -1692,23 +1694,13 @@ function frameLine(label, value) {
1692
1694
  }
1693
1695
 
1694
1696
  function drawServerUi() {
1695
- const logo = [
1696
- '\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 ',
1697
- '\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',
1698
- '\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',
1699
- '\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',
1700
- '\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',
1701
- '\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 ',
1702
- ];
1703
-
1704
- console.log('');
1705
- for (const line of logo) console.log(' ' + color('primary', line));
1706
1697
  console.log('');
1707
1698
  console.log(' ' + color('border', '\u256D\u2500' + '\u2500'.repeat(56) + '\u2500\u256E'));
1708
- 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'));
1709
1700
  console.log(' ' + color('border', '\u251C\u2500' + '\u2500'.repeat(56) + '\u2500\u2524'));
1710
1701
  frameLine('URL', 'http://localhost:' + PORT);
1711
1702
  frameLine('Root', ROOT);
1703
+ frameLine('Run', 'node serve.js');
1712
1704
  frameLine('Mode', 'static mirror + SPA fallback');
1713
1705
  console.log(' ' + color('border', '\u2570\u2500' + '\u2500'.repeat(56) + '\u2500\u256F'));
1714
1706
  console.log('');
@@ -1717,9 +1709,12 @@ function drawServerUi() {
1717
1709
 
1718
1710
  server.listen(PORT, () => {
1719
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'];
1720
1713
  let i = 0;
1721
1714
  const timer = setInterval(() => {
1722
- 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);
1723
1718
  i++;
1724
1719
  }, 80);
1725
1720
 
@@ -1727,7 +1722,7 @@ server.listen(PORT, () => {
1727
1722
  clearInterval(timer);
1728
1723
  process.stdout.write('\\r\\x1B[2K');
1729
1724
  drawServerUi();
1730
- }, 720);
1725
+ }, 1280);
1731
1726
  });
1732
1727
  `;
1733
1728
  }
@@ -1893,8 +1888,13 @@ async function buildOutput(exporter) {
1893
1888
  log("Writing index.html (" + (html.length / 1024).toFixed(1) + " KB)...");
1894
1889
  await fs2.writeFile(path3.join(exporter.outDir, "index.html"), html);
1895
1890
  success("index.html written");
1896
- await fs2.writeFile(path3.join(exporter.outDir, "serve.cjs"), SERVE_SCRIPT);
1897
- 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");
1898
1898
  success("Output build complete");
1899
1899
  }
1900
1900
  async function rewriteDownloadedFiles(exporter) {
@@ -2044,21 +2044,19 @@ async function printSummary(exporter) {
2044
2044
  console.log(boxBot(w));
2045
2045
  }
2046
2046
  console.log("");
2047
- const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
2047
+ const cdCmd = "cd " + path4.basename(exporter.outDir);
2048
+ const runCmd = "node serve.js";
2048
2049
  if (!isSmall) {
2049
2050
  console.log(boxTop(w));
2050
2051
  console.log(boxLine(w, ui.text.bold(" To serve locally")));
2051
2052
  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
- );
2053
+ console.log(boxRow(w, "Open", cdCmd));
2054
+ console.log(boxRow(w, "Run", runCmd));
2058
2055
  console.log(boxBot(w));
2059
2056
  } else {
2060
2057
  console.log(ui.text.bold(" To serve locally:"));
2061
2058
  console.log(` ${G(cdCmd)}`);
2059
+ console.log(` ${G(runCmd)}`);
2062
2060
  }
2063
2061
  console.log("");
2064
2062
  console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
@@ -2079,17 +2077,10 @@ import { stdin as stdin2, stdout as stdout2 } from "process";
2079
2077
  import { spawn } from "child_process";
2080
2078
  async function runAiPromptAssistant(exporter) {
2081
2079
  if (!stdin2.isTTY || !stdout2.isTTY) return;
2082
- printConvertPanel(exporter);
2083
- const action = await select(
2084
- "Framer Export AI Convert",
2085
- [
2086
- { label: `${buttonLabel("Convert")} open AI prompt modal`, value: "convert" },
2087
- { label: `${buttonLabel("Skip")} finish export`, value: "skip" }
2088
- ],
2089
- 0
2090
- );
2091
- if (action === "skip") return;
2092
- 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")}`, [
2093
2084
  "Choose a target stack, AI tool, and conversion situation.",
2094
2085
  "Mouse clicks are supported in the terminal when available.",
2095
2086
  "A detailed prompt file will be generated inside the export folder."
@@ -2118,16 +2109,8 @@ async function runAiPromptAssistant(exporter) {
2118
2109
  const promptPath = path5.join(aiDir, `${aiTool.id}-${target.id}-${goal.id}-prompt.md`);
2119
2110
  await fs4.mkdir(aiDir, { recursive: true });
2120
2111
  await fs4.writeFile(promptPath, prompt, "utf-8");
2121
- printPromptResult(promptPath, target, aiTool, goal);
2122
- const promptAction = await select(
2123
- "Prompt actions",
2124
- [
2125
- { label: `${buttonLabel("Copy prompt")} clipboard`, value: "copy" },
2126
- { label: `${buttonLabel("Done")} keep file only`, value: "done" }
2127
- ],
2128
- 0
2129
- );
2130
- if (promptAction === "copy") {
2112
+ const copyPrompt = await runPromptReadyPrompt(promptPath, target, aiTool, goal);
2113
+ if (copyPrompt) {
2131
2114
  try {
2132
2115
  await copyToClipboard(prompt);
2133
2116
  console.log(` ${ui.success("\u2713")} ${ui.text.bold("Prompt copied to clipboard")}
@@ -2185,27 +2168,97 @@ function centerText2(text, width) {
2185
2168
  const left = Math.floor((width - visible) / 2);
2186
2169
  return " ".repeat(left) + text + " ".repeat(width - visible - left);
2187
2170
  }
2188
- function printConvertPanel(exporter) {
2189
- const w = maxWidth();
2190
- const inner = w - 4;
2191
- const rows = [
2192
- centerText2(`${ui.text.bold("AI Convert")} ${ui.muted("BETA")}`, inner),
2193
- centerText2(ui.muted("Generate a conversion prompt after the export."), inner),
2194
- centerText2(`${buttonLabel("Convert")} ${ui.muted("or")} ${buttonLabel("Skip")}`, inner),
2195
- centerText2(ui.muted(path5.basename(exporter.outDir)), inner)
2196
- ];
2197
- console.log("");
2198
- console.log(boxTop(w));
2199
- for (const row of rows) console.log(boxLine(w, row));
2200
- console.log(boxBot(w));
2201
- 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");
2202
2255
  }
2203
2256
  function printAssistantModal(title, lines) {
2204
2257
  const w = maxWidth();
2205
2258
  const inner = w - 4;
2206
2259
  console.log("");
2207
2260
  console.log(boxTop(w));
2208
- console.log(boxLine(w, centerText2(`${ui.primary("\u25C6")} ${ui.text.bold(title)}`, inner)));
2261
+ console.log(boxLine(w, centerText2(`${ui.primary("\u25C6")} ${title}`, inner)));
2209
2262
  console.log(boxSep(w));
2210
2263
  for (const line of lines) {
2211
2264
  console.log(boxLine(w, centerText2(ui.muted(line), inner)));
@@ -2388,44 +2441,6 @@ function pipeToCommand(command, args, input) {
2388
2441
  child.stdin?.end(input);
2389
2442
  });
2390
2443
  }
2391
- function printPromptResult(promptPath, target, aiTool, goal) {
2392
- const w = maxWidth();
2393
- const isSmall = w < 50;
2394
- const inner = w - 4;
2395
- const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
2396
- console.log("");
2397
- if (!isSmall) {
2398
- console.log(boxTop(w));
2399
- console.log(
2400
- boxLine(
2401
- w,
2402
- centerText2(
2403
- `${ui.success("\u2713")} ${ui.text.bold("AI Prompt Ready")} ${ui.muted("BETA")}`,
2404
- inner
2405
- )
2406
- )
2407
- );
2408
- console.log(boxSep(w));
2409
- console.log(
2410
- boxLine(w, centerText2(`${ui.muted("Tool")} ${ui.primary(aiTool.displayName)}`, inner))
2411
- );
2412
- console.log(boxLine(w, centerText2(`${ui.muted("Stack")} ${ui.primary(target.label)}`, inner)));
2413
- console.log(boxLine(w, centerText2(`${ui.muted("Mode")} ${ui.primary(goal.label)}`, inner)));
2414
- console.log(boxLine(w, centerText2(`${ui.muted("File")} ${ui.primary(relPath)}`, inner)));
2415
- console.log(boxSep(w));
2416
- console.log(
2417
- boxLine(w, centerText2(`${buttonLabel("Copy prompt")} ${buttonLabel("Done")}`, inner))
2418
- );
2419
- console.log(boxBot(w));
2420
- } else {
2421
- console.log(ui.text.bold(" AI Prompt Ready - BETA"));
2422
- console.log(` Tool: ${ui.primary(aiTool.displayName)}`);
2423
- console.log(` Stack: ${ui.primary(target.label)}`);
2424
- console.log(` Situation: ${ui.primary(goal.label)}`);
2425
- console.log(` File: ${ui.primary(relPath)}`);
2426
- }
2427
- console.log("");
2428
- }
2429
2444
  var IMPORTANT_DIRS, AI_TOOLS, TARGETS, GOALS;
2430
2445
  var init_prompt_assistant = __esm({
2431
2446
  "src/ai/prompt-assistant.ts"() {