frameshot-mcp 0.7.0 → 0.9.7

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.
Files changed (67) hide show
  1. package/README.md +83 -69
  2. package/action.yml +114 -16
  3. package/dist/chunk-MEBQ7ZWA.js +1774 -0
  4. package/dist/chunk-VUYZHZBH.js +157 -0
  5. package/dist/cli.js +131 -133
  6. package/dist/index.js +519 -572
  7. package/dist/renderer.d.ts +17 -7
  8. package/dist/renderer.js +4 -6
  9. package/dist/stubs/gatsby.js +20 -0
  10. package/dist/stubs/next-font.js +9 -0
  11. package/dist/stubs/next-headers.js +20 -0
  12. package/dist/stubs/next-image.js +34 -0
  13. package/dist/stubs/next-link.js +25 -0
  14. package/dist/stubs/next-navigation.js +17 -0
  15. package/dist/stubs/next-router.js +19 -0
  16. package/dist/stubs/nuxt-app.js +37 -0
  17. package/dist/stubs/nuxt-imports.js +13 -0
  18. package/dist/stubs/qwik-city.js +33 -0
  19. package/dist/stubs/react-router.js +67 -0
  20. package/dist/stubs/server-only.js +2 -0
  21. package/dist/stubs/solid-router.js +27 -0
  22. package/dist/stubs/solid-start.js +18 -0
  23. package/dist/stubs/stubs/gatsby.js +20 -0
  24. package/dist/stubs/stubs/next-font.js +9 -0
  25. package/dist/stubs/stubs/next-headers.js +20 -0
  26. package/dist/stubs/stubs/next-image.js +34 -0
  27. package/dist/stubs/stubs/next-link.js +25 -0
  28. package/dist/stubs/stubs/next-navigation.js +17 -0
  29. package/dist/stubs/stubs/next-router.js +19 -0
  30. package/dist/stubs/stubs/nuxt-app.js +37 -0
  31. package/dist/stubs/stubs/nuxt-imports.js +13 -0
  32. package/dist/stubs/stubs/qwik-city.js +33 -0
  33. package/dist/stubs/stubs/react-router.js +67 -0
  34. package/dist/stubs/stubs/server-only.js +2 -0
  35. package/dist/stubs/stubs/solid-router.js +27 -0
  36. package/dist/stubs/stubs/solid-start.js +18 -0
  37. package/dist/stubs/stubs/sveltekit-environment.js +5 -0
  38. package/dist/stubs/stubs/sveltekit-navigation.js +11 -0
  39. package/dist/stubs/stubs/sveltekit-stores.js +15 -0
  40. package/dist/stubs/stubs/vike.js +11 -0
  41. package/dist/stubs/sveltekit-environment.js +5 -0
  42. package/dist/stubs/sveltekit-navigation.js +11 -0
  43. package/dist/stubs/sveltekit-stores.js +15 -0
  44. package/dist/stubs/vike.js +11 -0
  45. package/package.json +10 -4
  46. package/scripts/render-changed.mjs +140 -18
  47. package/dist/chunk-3LVWVDET.js +0 -849
  48. package/dist/chunk-47YJG5HR.js +0 -690
  49. package/dist/chunk-67JZQ6OI.js +0 -819
  50. package/dist/chunk-AZCGKIMU.js +0 -850
  51. package/dist/chunk-B3CLIGWU.js +0 -786
  52. package/dist/chunk-C6QSY4WR.js +0 -811
  53. package/dist/chunk-DX54PJKO.js +0 -603
  54. package/dist/chunk-EMCJGIMY.js +0 -984
  55. package/dist/chunk-FQNWGR62.js +0 -849
  56. package/dist/chunk-FTYTZW6D.js +0 -203
  57. package/dist/chunk-JGVKYXY2.js +0 -857
  58. package/dist/chunk-JYPEA4P2.js +0 -846
  59. package/dist/chunk-KHK35HDD.js +0 -855
  60. package/dist/chunk-Q7A3DLED.js +0 -848
  61. package/dist/chunk-SIA6XEHM.js +0 -811
  62. package/dist/chunk-ST35YDI6.js +0 -834
  63. package/dist/chunk-T5OBJK35.js +0 -855
  64. package/dist/chunk-U3GHS7KO.js +0 -837
  65. package/dist/chunk-WS2ASCD6.js +0 -683
  66. package/dist/chunk-WZMHVSUA.js +0 -847
  67. package/dist/chunk-ZZST6K7Y.js +0 -987
@@ -0,0 +1,157 @@
1
+ import {
2
+ AuditUseCase,
3
+ BrowserPool,
4
+ CatalogUseCase,
5
+ DiffUseCase,
6
+ HtmlBuilder,
7
+ ImageComparator,
8
+ RenderUseCase,
9
+ ScreenshotUseCase,
10
+ SnapshotStore,
11
+ SnapshotUseCase,
12
+ ViteBundler
13
+ } from "./chunk-MEBQ7ZWA.js";
14
+
15
+ // src/use-cases/watch.ts
16
+ import { watch } from "chokidar";
17
+ var WatchUseCase = class {
18
+ constructor(renderUseCase) {
19
+ this.renderUseCase = renderUseCase;
20
+ }
21
+ renderUseCase;
22
+ sessions = /* @__PURE__ */ new Map();
23
+ latestRenders = /* @__PURE__ */ new Map();
24
+ inFlight = /* @__PURE__ */ new Set();
25
+ // key: `${sessionId}:${filePath}`
26
+ nextId = 1;
27
+ start(patterns, props, callback) {
28
+ const id = `watch-${this.nextId++}`;
29
+ const watcher = watch(patterns, {
30
+ ignoreInitial: true,
31
+ // only watch future changes, not existing files on startup
32
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
33
+ });
34
+ watcher.on(
35
+ "change",
36
+ (path) => this.handleChange(id, path, props, callback)
37
+ );
38
+ const session = {
39
+ id,
40
+ patterns,
41
+ stop: () => {
42
+ watcher.close();
43
+ this.sessions.delete(id);
44
+ this.latestRenders.delete(id);
45
+ }
46
+ };
47
+ this.sessions.set(id, session);
48
+ return session;
49
+ }
50
+ stop(sessionId) {
51
+ const session = this.sessions.get(sessionId);
52
+ if (!session) return false;
53
+ session.stop();
54
+ return true;
55
+ }
56
+ stopAll() {
57
+ const sessions = Array.from(this.sessions.values());
58
+ for (const session of sessions) {
59
+ session.stop();
60
+ }
61
+ this.latestRenders.clear();
62
+ }
63
+ activeSessions() {
64
+ return Array.from(this.sessions.values());
65
+ }
66
+ getLatestRender(sessionId) {
67
+ return this.latestRenders.get(sessionId);
68
+ }
69
+ async handleChange(sessionId, filePath, props, callback) {
70
+ const key = `${sessionId}:${filePath}`;
71
+ if (this.inFlight.has(key)) return;
72
+ this.inFlight.add(key);
73
+ const start = performance.now();
74
+ try {
75
+ const { results, mode } = await this.renderUseCase.renderFile(filePath, {
76
+ props,
77
+ autoFit: true,
78
+ fullPage: true,
79
+ engines: ["chromium"]
80
+ });
81
+ const result = results[0];
82
+ const event = {
83
+ sessionId,
84
+ filePath,
85
+ image: result.image,
86
+ width: result.width,
87
+ height: result.height,
88
+ elapsedMs: Math.round(performance.now() - start),
89
+ mode,
90
+ consoleErrors: result.consoleErrors
91
+ };
92
+ this.latestRenders.set(sessionId, event);
93
+ await callback(event);
94
+ } catch (err) {
95
+ const event = {
96
+ sessionId,
97
+ filePath,
98
+ image: "",
99
+ width: 0,
100
+ height: 0,
101
+ elapsedMs: Math.round(performance.now() - start),
102
+ mode: "cdn",
103
+ consoleErrors: [],
104
+ error: err instanceof Error ? err.message : String(err)
105
+ };
106
+ this.latestRenders.set(sessionId, event);
107
+ await callback(event);
108
+ } finally {
109
+ this.inFlight.delete(key);
110
+ }
111
+ }
112
+ };
113
+
114
+ // src/container.ts
115
+ function createContainer() {
116
+ const pool = new BrowserPool();
117
+ const htmlBuilder = new HtmlBuilder();
118
+ const imageComparator = new ImageComparator();
119
+ const snapshotStore = new SnapshotStore();
120
+ const viteBundler = new ViteBundler();
121
+ const renderUseCase = new RenderUseCase(
122
+ pool,
123
+ htmlBuilder,
124
+ imageComparator,
125
+ viteBundler
126
+ );
127
+ const screenshotUseCase = new ScreenshotUseCase(pool);
128
+ const diffUseCase = new DiffUseCase(renderUseCase, imageComparator);
129
+ const auditUseCase = new AuditUseCase(pool, htmlBuilder);
130
+ const snapshotUseCase = new SnapshotUseCase(
131
+ snapshotStore,
132
+ renderUseCase,
133
+ diffUseCase
134
+ );
135
+ const catalogUseCase = new CatalogUseCase(renderUseCase);
136
+ const watchUseCase = new WatchUseCase(renderUseCase);
137
+ return {
138
+ pool,
139
+ viteBundler,
140
+ renderUseCase,
141
+ screenshotUseCase,
142
+ diffUseCase,
143
+ auditUseCase,
144
+ snapshotUseCase,
145
+ catalogUseCase,
146
+ watchUseCase,
147
+ async shutdown() {
148
+ watchUseCase.stopAll();
149
+ await viteBundler.shutdown();
150
+ await pool.shutdown();
151
+ }
152
+ };
153
+ }
154
+
155
+ export {
156
+ createContainer
157
+ };
package/dist/cli.js CHANGED
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- BrowserPool,
4
- CatalogUseCase,
5
- DiffUseCase,
6
- EXT_TO_FRAMEWORK,
7
- HtmlBuilder,
8
- ImageComparator,
9
- RenderUseCase,
10
- ViteBundler
11
- } from "./chunk-Q7A3DLED.js";
3
+ createContainer
4
+ } from "./chunk-VUYZHZBH.js";
5
+ import {
6
+ EXT_TO_FRAMEWORK
7
+ } from "./chunk-MEBQ7ZWA.js";
12
8
 
13
9
  // src/cli.ts
14
- import { execFileSync, execSync } from "child_process";
15
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
10
+ import { mkdirSync } from "fs";
11
+ import { resolve as resolve2 } from "path";
12
+
13
+ // src/cli/commands.ts
14
+ import { execSync } from "child_process";
15
+ import { readFileSync, writeFileSync } from "fs";
16
16
  import { basename, extname, relative, resolve } from "path";
17
+
18
+ // src/cli/display.ts
17
19
  var c = {
18
20
  reset: "\x1B[0m",
19
21
  bold: "\x1B[1m",
@@ -66,6 +68,15 @@ function spinner(text) {
66
68
  function separator() {
67
69
  log(` ${c.gray}${"\u2500".repeat(48)}${c.reset}`);
68
70
  }
71
+ function renderBar(pct) {
72
+ const width = 30;
73
+ const filled = Math.round(pct / 100 * width);
74
+ const empty = width - filled;
75
+ return `${c.yellow}${"\u2588".repeat(filled)}${c.gray}${"\u2591".repeat(empty)}${c.reset} ${c.dim}${pct.toFixed(1)}%${c.reset}`;
76
+ }
77
+
78
+ // src/cli/image.ts
79
+ import { execFileSync } from "child_process";
69
80
  function supportsItermImage() {
70
81
  const term = process.env.TERM_PROGRAM ?? "";
71
82
  return term === "iTerm.app" || term === "WezTerm" || !!process.env.TERM_PROGRAM_VERSION?.includes("iTerm") || term === "vscode";
@@ -121,117 +132,10 @@ function displayImage(base64, filePath) {
121
132
  } catch {
122
133
  }
123
134
  }
124
- function parseArgs(args) {
125
- const command = args[0] ?? "help";
126
- const positional = args[1] ?? "";
127
- const flags = {
128
- out: ".frameshot",
129
- recursive: false
130
- };
131
- for (let i = 2; i < args.length; i++) {
132
- const arg = args[i];
133
- switch (arg) {
134
- case "--props":
135
- try {
136
- flags.props = JSON.parse(args[++i]);
137
- } catch {
138
- error("Invalid --props JSON");
139
- }
140
- break;
141
- case "--out":
142
- case "-o":
143
- flags.out = args[++i];
144
- break;
145
- case "--recursive":
146
- case "-r":
147
- flags.recursive = true;
148
- break;
149
- case "--width":
150
- flags.width = Number.parseInt(args[++i], 10);
151
- break;
152
- case "--height":
153
- flags.height = Number.parseInt(args[++i], 10);
154
- break;
155
- }
156
- }
157
- return { command, positional, flags };
158
- }
159
- var HELP = `
160
- ${brand()} ${c.dim}v0.7.0${c.reset}
161
- ${c.dim}Zero-config component screenshots \u2014 Vite + Playwright${c.reset}
162
-
163
- ${c.white}${c.bold}Commands${c.reset}
164
- ${c.blue}render${c.reset} ${c.dim}<file>${c.reset} Render a component to PNG
165
- ${c.blue}catalog${c.reset} ${c.dim}<dir>${c.reset} Render all components in a directory
166
- ${c.blue}diff${c.reset} ${c.dim}<file>${c.reset} Visual diff against git HEAD
167
135
 
168
- ${c.white}${c.bold}Options${c.reset}
169
- ${c.purple}--props${c.reset} ${c.dim}'{...}'${c.reset} Component props (JSON)
170
- ${c.purple}--out${c.reset}, ${c.purple}-o${c.reset} ${c.dim}<dir>${c.reset} Output directory ${c.gray}[.frameshot]${c.reset}
171
- ${c.purple}--recursive${c.reset}, ${c.purple}-r${c.reset} Scan subdirectories
172
- ${c.purple}--width${c.reset} ${c.dim}<px>${c.reset} Viewport width ${c.gray}[auto]${c.reset}
173
- ${c.purple}--height${c.reset} ${c.dim}<px>${c.reset} Viewport height ${c.gray}[auto]${c.reset}
174
-
175
- ${c.white}${c.bold}Examples${c.reset}
176
- ${c.gray}$${c.reset} frameshot render src/Button.tsx
177
- ${c.gray}$${c.reset} frameshot render src/Card.tsx ${c.purple}--props${c.reset} '{"title":"Hi"}'
178
- ${c.gray}$${c.reset} frameshot catalog src/components/ ${c.purple}-r${c.reset}
179
- ${c.gray}$${c.reset} frameshot diff src/Header.tsx
180
-
181
- ${c.dim}Supports: React \xB7 Vue \xB7 Svelte \xB7 Tailwind \xB7 CSS Modules${c.reset}
182
- ${c.dim}Display: iTerm2 \xB7 Kitty \xB7 Sixel \xB7 fallback to open${c.reset}
183
-
184
- `;
185
- async function main() {
186
- const { command, positional, flags } = parseArgs(process.argv.slice(2));
187
- if (command === "help" || command === "--help" || command === "-h") {
188
- process.stdout.write(HELP);
189
- return;
190
- }
191
- if (!positional) {
192
- error(
193
- `Missing argument. Run ${c.dim}frameshot --help${c.reset} for usage.`
194
- );
195
- }
196
- log(`
197
- ${brand()} ${c.dim}v0.7.0${c.reset}
198
- `);
199
- const pool = new BrowserPool();
200
- const htmlBuilder = new HtmlBuilder();
201
- const imageComparator = new ImageComparator();
202
- const viteBundler = new ViteBundler();
203
- const renderUseCase = new RenderUseCase(
204
- pool,
205
- htmlBuilder,
206
- imageComparator,
207
- viteBundler
208
- );
209
- const catalogUseCase = new CatalogUseCase(renderUseCase);
210
- const diffUseCase = new DiffUseCase(renderUseCase, imageComparator);
211
- const outDir = resolve(flags.out);
212
- mkdirSync(outDir, { recursive: true });
213
- try {
214
- await pool.warmup(["chromium"]);
215
- switch (command) {
216
- case "render":
217
- await cmdRender(renderUseCase, positional, flags, outDir);
218
- break;
219
- case "catalog":
220
- await cmdCatalog(catalogUseCase, positional, flags, outDir);
221
- break;
222
- case "diff":
223
- await cmdDiff(diffUseCase, positional, flags, outDir);
224
- break;
225
- default:
226
- error(
227
- `Unknown command '${command}'. Run ${c.dim}frameshot --help${c.reset} for usage.`
228
- );
229
- }
230
- } finally {
231
- await viteBundler.shutdown();
232
- await pool.shutdown();
233
- }
234
- log("");
136
+ // src/cli/commands.ts
137
+ function getViewport(flags) {
138
+ return { width: flags.width ?? 1280, height: flags.height ?? 800 };
235
139
  }
236
140
  async function cmdRender(useCase, filePath, flags, outDir) {
237
141
  const absPath = resolve(filePath);
@@ -253,10 +157,7 @@ async function cmdRender(useCase, filePath, flags, outDir) {
253
157
  const start = performance.now();
254
158
  const { results, mode } = await useCase.renderFile(absPath, {
255
159
  props: flags.props,
256
- viewport: {
257
- width: flags.width ?? 1280,
258
- height: flags.height ?? 800
259
- },
160
+ viewport: getViewport(flags),
260
161
  autoFit,
261
162
  fullPage: true,
262
163
  engines: ["chromium"]
@@ -301,7 +202,7 @@ async function cmdCatalog(useCase, dirPath, flags, outDir) {
301
202
  const s = spinner("Scanning & rendering components...");
302
203
  const results = await useCase.renderCatalog(absDir, {
303
204
  recursive: flags.recursive,
304
- viewport: { width: flags.width ?? 1280, height: flags.height ?? 800 },
205
+ viewport: getViewport(flags),
305
206
  autoFit: flags.width === void 0 || flags.height === void 0
306
207
  });
307
208
  if (results.length === 0) {
@@ -349,7 +250,7 @@ async function cmdDiff(useCase, filePath, flags, outDir) {
349
250
  baseCode = execSync(`git show HEAD:${gitRelPath}`, { encoding: "utf-8" });
350
251
  } catch {
351
252
  s.stop(`${c.red}Failed${c.reset}`);
352
- error(
253
+ throw new Error(
353
254
  `Could not get HEAD version of ${relPath}. Is this file tracked by git?`
354
255
  );
355
256
  }
@@ -357,7 +258,7 @@ async function cmdDiff(useCase, filePath, flags, outDir) {
357
258
  const ext = extname(filePath).toLowerCase();
358
259
  const framework = EXT_TO_FRAMEWORK[ext] ?? "react";
359
260
  const result = await useCase.diffComponent(baseCode, currentCode, framework, {
360
- viewport: { width: flags.width ?? 1280, height: flags.height ?? 800 },
261
+ viewport: getViewport(flags),
361
262
  autoFit: flags.width === void 0 || flags.height === void 0
362
263
  });
363
264
  const elapsed = Math.round(performance.now() - start);
@@ -396,11 +297,108 @@ async function cmdDiff(useCase, filePath, flags, outDir) {
396
297
  );
397
298
  }
398
299
  }
399
- function renderBar(pct) {
400
- const width = 30;
401
- const filled = Math.round(pct / 100 * width);
402
- const empty = width - filled;
403
- return `${c.yellow}${"\u2588".repeat(filled)}${c.gray}${"\u2591".repeat(empty)}${c.reset} ${c.dim}${pct.toFixed(1)}%${c.reset}`;
300
+
301
+ // src/cli.ts
302
+ function parseArgs(args) {
303
+ const command = args[0] ?? "help";
304
+ const positional = args[1] ?? "";
305
+ const flags = {
306
+ out: ".frameshot",
307
+ recursive: false
308
+ };
309
+ for (let i = 2; i < args.length; i++) {
310
+ const arg = args[i];
311
+ switch (arg) {
312
+ case "--props":
313
+ try {
314
+ flags.props = JSON.parse(args[++i]);
315
+ } catch {
316
+ error("Invalid --props JSON");
317
+ }
318
+ break;
319
+ case "--out":
320
+ case "-o":
321
+ flags.out = args[++i];
322
+ break;
323
+ case "--recursive":
324
+ case "-r":
325
+ flags.recursive = true;
326
+ break;
327
+ case "--width":
328
+ flags.width = Number.parseInt(args[++i], 10);
329
+ break;
330
+ case "--height":
331
+ flags.height = Number.parseInt(args[++i], 10);
332
+ break;
333
+ }
334
+ }
335
+ return { command, positional, flags };
336
+ }
337
+ var HELP = `
338
+ ${brand()} ${c.dim}v0.7.0${c.reset}
339
+ ${c.dim}Zero-config component screenshots \u2014 Vite + Playwright${c.reset}
340
+
341
+ ${c.white}${c.bold}Commands${c.reset}
342
+ ${c.blue}render${c.reset} ${c.dim}<file>${c.reset} Render a component to PNG
343
+ ${c.blue}catalog${c.reset} ${c.dim}<dir>${c.reset} Render all components in a directory
344
+ ${c.blue}diff${c.reset} ${c.dim}<file>${c.reset} Visual diff against git HEAD
345
+
346
+ ${c.white}${c.bold}Options${c.reset}
347
+ ${c.purple}--props${c.reset} ${c.dim}'{...}'${c.reset} Component props (JSON)
348
+ ${c.purple}--out${c.reset}, ${c.purple}-o${c.reset} ${c.dim}<dir>${c.reset} Output directory ${c.gray}[.frameshot]${c.reset}
349
+ ${c.purple}--recursive${c.reset}, ${c.purple}-r${c.reset} Scan subdirectories
350
+ ${c.purple}--width${c.reset} ${c.dim}<px>${c.reset} Viewport width ${c.gray}[auto]${c.reset}
351
+ ${c.purple}--height${c.reset} ${c.dim}<px>${c.reset} Viewport height ${c.gray}[auto]${c.reset}
352
+
353
+ ${c.white}${c.bold}Examples${c.reset}
354
+ ${c.gray}$${c.reset} frameshot render src/Button.tsx
355
+ ${c.gray}$${c.reset} frameshot render src/Card.tsx ${c.purple}--props${c.reset} '{"title":"Hi"}'
356
+ ${c.gray}$${c.reset} frameshot catalog src/components/ ${c.purple}-r${c.reset}
357
+ ${c.gray}$${c.reset} frameshot diff src/Header.tsx
358
+
359
+ ${c.dim}Supports: React \xB7 Vue \xB7 Svelte \xB7 Tailwind \xB7 CSS Modules${c.reset}
360
+ ${c.dim}Display: iTerm2 \xB7 Kitty \xB7 Sixel \xB7 fallback to open${c.reset}
361
+
362
+ `;
363
+ async function main() {
364
+ const { command, positional, flags } = parseArgs(process.argv.slice(2));
365
+ if (command === "help" || command === "--help" || command === "-h") {
366
+ process.stdout.write(HELP);
367
+ return;
368
+ }
369
+ if (!positional) {
370
+ error(
371
+ `Missing argument. Run ${c.dim}frameshot --help${c.reset} for usage.`
372
+ );
373
+ }
374
+ log(`
375
+ ${brand()} ${c.dim}v0.7.0${c.reset}
376
+ `);
377
+ const container = createContainer();
378
+ const { pool, renderUseCase, catalogUseCase, diffUseCase } = container;
379
+ const outDir = resolve2(flags.out);
380
+ mkdirSync(outDir, { recursive: true });
381
+ try {
382
+ await pool.warmup(["chromium"]);
383
+ switch (command) {
384
+ case "render":
385
+ await cmdRender(renderUseCase, positional, flags, outDir);
386
+ break;
387
+ case "catalog":
388
+ await cmdCatalog(catalogUseCase, positional, flags, outDir);
389
+ break;
390
+ case "diff":
391
+ await cmdDiff(diffUseCase, positional, flags, outDir);
392
+ break;
393
+ default:
394
+ error(
395
+ `Unknown command '${command}'. Run ${c.dim}frameshot --help${c.reset} for usage.`
396
+ );
397
+ }
398
+ } finally {
399
+ await container.shutdown();
400
+ }
401
+ log("");
404
402
  }
405
403
  main().catch((e) => {
406
404
  error(e.message ?? String(e));