frameshot-mcp 0.8.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 (73) hide show
  1. package/README.md +68 -122
  2. package/action.yml +14 -5
  3. package/dist/{chunk-PYWXJZTZ.js → chunk-MEBQ7ZWA.js} +760 -109
  4. package/dist/{chunk-MA3FOIQY.js → chunk-VUYZHZBH.js} +1 -1
  5. package/dist/cli.js +128 -115
  6. package/dist/index.js +377 -528
  7. package/dist/renderer.d.ts +7 -3
  8. package/dist/renderer.js +1 -1
  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 +8 -3
  46. package/scripts/render-changed.mjs +67 -12
  47. package/dist/chunk-3CDSNOX5.js +0 -869
  48. package/dist/chunk-3LVWVDET.js +0 -849
  49. package/dist/chunk-47YJG5HR.js +0 -690
  50. package/dist/chunk-67JZQ6OI.js +0 -819
  51. package/dist/chunk-AUACBLHM.js +0 -191
  52. package/dist/chunk-AZCGKIMU.js +0 -850
  53. package/dist/chunk-B3CLIGWU.js +0 -786
  54. package/dist/chunk-C6QSY4WR.js +0 -811
  55. package/dist/chunk-DX54PJKO.js +0 -603
  56. package/dist/chunk-EMCJGIMY.js +0 -984
  57. package/dist/chunk-FQNWGR62.js +0 -849
  58. package/dist/chunk-FTYTZW6D.js +0 -203
  59. package/dist/chunk-GVOEFYEX.js +0 -139
  60. package/dist/chunk-JGVKYXY2.js +0 -857
  61. package/dist/chunk-JYPEA4P2.js +0 -846
  62. package/dist/chunk-KHK35HDD.js +0 -855
  63. package/dist/chunk-L2CADTS7.js +0 -191
  64. package/dist/chunk-O7NWWFIU.js +0 -871
  65. package/dist/chunk-Q7A3DLED.js +0 -848
  66. package/dist/chunk-Q7NQA4ZM.js +0 -1095
  67. package/dist/chunk-SIA6XEHM.js +0 -811
  68. package/dist/chunk-ST35YDI6.js +0 -834
  69. package/dist/chunk-T5OBJK35.js +0 -855
  70. package/dist/chunk-U3GHS7KO.js +0 -837
  71. package/dist/chunk-WS2ASCD6.js +0 -683
  72. package/dist/chunk-WZMHVSUA.js +0 -847
  73. package/dist/chunk-ZZST6K7Y.js +0 -987
@@ -4,6 +4,31 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/domain/types.ts
8
+ var DEFAULT_RENDER_OPTIONS = {
9
+ viewport: { width: 1280, height: 800 },
10
+ engines: ["chromium"],
11
+ fullPage: true,
12
+ darkMode: false,
13
+ css: "",
14
+ tailwindVersion: "3",
15
+ waitFor: 0,
16
+ autoFit: false
17
+ };
18
+ var DEVICE_PRESETS = {
19
+ mobile: { width: 375, height: 667 },
20
+ tablet: { width: 768, height: 1024 },
21
+ desktop: { width: 1280, height: 800 }
22
+ };
23
+ var EXT_TO_FRAMEWORK = {
24
+ ".jsx": "react",
25
+ ".tsx": "react",
26
+ ".vue": "vue",
27
+ ".svelte": "svelte",
28
+ ".html": "html",
29
+ ".htm": "html"
30
+ };
31
+
7
32
  // src/infrastructure/browser-pool.ts
8
33
  import { chromium, firefox, webkit } from "playwright";
9
34
  var BrowserPool = class {
@@ -78,14 +103,18 @@ var BrowserPool = class {
78
103
  const launcher = { chromium, firefox, webkit }[engine];
79
104
  let browser;
80
105
  try {
81
- browser = await launcher.launch({
82
- headless: true,
83
- ...engine === "chromium" ? { channel: "chrome" } : {}
84
- });
85
- } catch (_e) {
86
- throw new Error(
87
- `${engine} is not installed. Run: npx playwright install ${engine}`
88
- );
106
+ browser = await launcher.launch({ headless: true });
107
+ } catch {
108
+ try {
109
+ browser = await launcher.launch({
110
+ headless: true,
111
+ ...engine === "chromium" ? { channel: "chrome" } : {}
112
+ });
113
+ } catch (_e) {
114
+ throw new Error(
115
+ `${engine} is not installed. Run: npx playwright install ${engine}`
116
+ );
117
+ }
89
118
  }
90
119
  const slot = { browser, idlePages: [] };
91
120
  this.pool.set(engine, slot);
@@ -247,7 +276,9 @@ var ProjectDetector = class {
247
276
  const viteConfigPath = this.findViteConfig(root);
248
277
  const framework = this.detectFramework(root);
249
278
  const hasVite = this.checkViteAvailable(root);
250
- return { root, viteConfigPath, framework, hasVite };
279
+ const isNextJs = this.checkIsNextJs(root);
280
+ const pathAliases = this.readTsconfigAliases(root);
281
+ return { root, viteConfigPath, framework, hasVite, isNextJs, pathAliases };
251
282
  }
252
283
  findProjectRoot(startPath) {
253
284
  let dir = dirname(resolve(startPath));
@@ -277,6 +308,8 @@ var ProjectDetector = class {
277
308
  ...pkg.dependencies,
278
309
  ...pkg.devDependencies
279
310
  };
311
+ if (allDeps["solid-js"]) return "solid";
312
+ if (allDeps.preact) return "preact";
280
313
  if (allDeps.react || allDeps["react-dom"]) return "react";
281
314
  if (allDeps.vue) return "vue";
282
315
  if (allDeps.svelte) return "svelte";
@@ -288,19 +321,601 @@ var ProjectDetector = class {
288
321
  const vitePkgPath = join(root, "node_modules", "vite", "package.json");
289
322
  return existsSync(vitePkgPath);
290
323
  }
324
+ checkIsNextJs(root) {
325
+ const pkgPath = join(root, "package.json");
326
+ if (!existsSync(pkgPath)) return false;
327
+ try {
328
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
329
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
330
+ return !!allDeps.next;
331
+ } catch {
332
+ return false;
333
+ }
334
+ }
335
+ readTsconfigAliases(root) {
336
+ const tscPath = join(root, "tsconfig.json");
337
+ if (!existsSync(tscPath)) return {};
338
+ try {
339
+ const content = readFileSync(tscPath, "utf-8");
340
+ const stripped = content.replace(/^\s*\/\/[^\n]*/gm, "");
341
+ const tsc = JSON.parse(stripped);
342
+ const paths = tsc.compilerOptions?.paths ?? {};
343
+ const baseUrl = tsc.compilerOptions?.baseUrl ?? ".";
344
+ const result = {};
345
+ for (const [alias, targets] of Object.entries(paths)) {
346
+ const key = alias.replace(/\/\*$/, "");
347
+ const target = (targets[0] ?? "").replace(/\/\*$/, "");
348
+ result[key] = join(root, baseUrl, target);
349
+ }
350
+ return result;
351
+ } catch {
352
+ return {};
353
+ }
354
+ }
291
355
  };
292
356
 
293
357
  // src/infrastructure/vite-bundler.ts
294
- import { existsSync as existsSync2 } from "fs";
358
+ import { existsSync as existsSync4 } from "fs";
295
359
  import { createRequire } from "module";
296
- import { join as join2, resolve as resolve2 } from "path";
360
+ import { dirname as dirname2, join as join18, resolve as resolve2 } from "path";
361
+ import { fileURLToPath } from "url";
362
+
363
+ // src/infrastructure/adapters/astro.ts
364
+ import { join as join3 } from "path";
365
+
366
+ // src/infrastructure/adapters/helpers.ts
367
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
368
+ import { join as join2 } from "path";
369
+ function hasDep(pkgPath, names) {
370
+ if (!existsSync2(pkgPath)) return false;
371
+ try {
372
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
373
+ const all = { ...pkg.dependencies, ...pkg.devDependencies };
374
+ return names.some((n) => !!all[n]);
375
+ } catch {
376
+ return false;
377
+ }
378
+ }
379
+ function hasInstalled(projectRoot, name) {
380
+ return existsSync2(join2(projectRoot, "node_modules", ...name.split("/")));
381
+ }
382
+ async function loadPlugin(pkg, fnName) {
383
+ try {
384
+ const mod = await import(pkg);
385
+ const candidate = fnName ? mod[fnName] : mod.default ?? mod;
386
+ if (typeof candidate === "function") {
387
+ const result = candidate();
388
+ return Array.isArray(result) ? result : [result];
389
+ }
390
+ } catch {
391
+ process.stderr.write(`[frameshot] ${pkg} not available
392
+ `);
393
+ }
394
+ return [];
395
+ }
396
+ function loadReactPlugin() {
397
+ return loadPlugin("@vitejs/plugin-react");
398
+ }
399
+ function rscDirectivePlugin() {
400
+ return {
401
+ name: "frameshot-rsc-directives",
402
+ transform(code) {
403
+ return code.replace(/^['"]use (client|server)['"];?\s*/m, "");
404
+ }
405
+ };
406
+ }
407
+
408
+ // src/infrastructure/adapters/astro.ts
409
+ var AstroAdapter = class {
410
+ id = "astro";
411
+ detect(projectRoot) {
412
+ return hasDep(join3(projectRoot, "package.json"), ["astro"]);
413
+ }
414
+ getAliases() {
415
+ return {};
416
+ }
417
+ getOptimizeDeps() {
418
+ return [];
419
+ }
420
+ async getPlugins(projectRoot) {
421
+ const plugins = [];
422
+ if (hasInstalled(projectRoot, "react"))
423
+ plugins.push(...await loadReactPlugin());
424
+ if (hasInstalled(projectRoot, "vue"))
425
+ plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
426
+ if (hasInstalled(projectRoot, "svelte"))
427
+ plugins.push(
428
+ ...await loadPlugin("@sveltejs/vite-plugin-svelte", "svelte")
429
+ );
430
+ if (hasInstalled(projectRoot, "solid-js"))
431
+ plugins.push(...await loadPlugin("vite-plugin-solid"));
432
+ return plugins;
433
+ }
434
+ useFrameshotVite() {
435
+ return true;
436
+ }
437
+ skipProjectConfig() {
438
+ return true;
439
+ }
440
+ };
441
+
442
+ // src/infrastructure/adapters/gatsby.ts
443
+ import { join as join4 } from "path";
444
+ var GatsbyAdapter = class {
445
+ id = "gatsby";
446
+ detect(projectRoot) {
447
+ return hasDep(join4(projectRoot, "package.json"), ["gatsby"]);
448
+ }
449
+ getAliases(_root, stubsDir) {
450
+ return { gatsby: join4(stubsDir, "gatsby.js") };
451
+ }
452
+ getOptimizeDeps(_root) {
453
+ return ["react", "react-dom/client"];
454
+ }
455
+ async getPlugins(_root) {
456
+ return loadReactPlugin();
457
+ }
458
+ useFrameshotVite() {
459
+ return true;
460
+ }
461
+ skipProjectConfig() {
462
+ return true;
463
+ }
464
+ };
465
+
466
+ // src/infrastructure/adapters/generic.ts
467
+ var GenericAdapter = class {
468
+ id = "generic";
469
+ detect() {
470
+ return true;
471
+ }
472
+ getAliases() {
473
+ return {};
474
+ }
475
+ getOptimizeDeps(projectRoot) {
476
+ const deps = [];
477
+ if (hasInstalled(projectRoot, "react"))
478
+ deps.push("react", "react-dom/client");
479
+ if (hasInstalled(projectRoot, "vue")) deps.push("vue");
480
+ if (hasInstalled(projectRoot, "solid-js"))
481
+ deps.push("solid-js", "solid-js/web");
482
+ if (hasInstalled(projectRoot, "preact")) deps.push("preact");
483
+ return deps;
484
+ }
485
+ async getPlugins(projectRoot) {
486
+ const plugins = [];
487
+ if (hasInstalled(projectRoot, "react"))
488
+ plugins.push(...await loadReactPlugin());
489
+ if (hasInstalled(projectRoot, "vue"))
490
+ plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
491
+ if (hasInstalled(projectRoot, "svelte"))
492
+ plugins.push(
493
+ ...await loadPlugin("@sveltejs/vite-plugin-svelte", "svelte")
494
+ );
495
+ if (hasInstalled(projectRoot, "solid-js"))
496
+ plugins.push(...await loadPlugin("vite-plugin-solid"));
497
+ if (hasInstalled(projectRoot, "preact"))
498
+ plugins.push(...await loadPlugin("@preact/preset-vite"));
499
+ return plugins;
500
+ }
501
+ useFrameshotVite() {
502
+ return true;
503
+ }
504
+ skipProjectConfig() {
505
+ return true;
506
+ }
507
+ };
508
+
509
+ // src/infrastructure/adapters/lit.ts
510
+ import { join as join5 } from "path";
511
+ var LitAdapter = class {
512
+ id = "lit";
513
+ detect(projectRoot) {
514
+ return hasDep(join5(projectRoot, "package.json"), ["lit", "lit-element"]);
515
+ }
516
+ getAliases() {
517
+ return {};
518
+ }
519
+ getOptimizeDeps() {
520
+ return ["lit"];
521
+ }
522
+ async getPlugins(_root) {
523
+ return [];
524
+ }
525
+ useFrameshotVite() {
526
+ return true;
527
+ }
528
+ skipProjectConfig() {
529
+ return true;
530
+ }
531
+ };
532
+
533
+ // src/infrastructure/adapters/next.ts
534
+ import { join as join6 } from "path";
535
+ var NextJsAdapter = class {
536
+ id = "next";
537
+ detect(projectRoot) {
538
+ return hasDep(join6(projectRoot, "package.json"), ["next"]);
539
+ }
540
+ getAliases(_root, stubsDir) {
541
+ return {
542
+ "next/navigation": join6(stubsDir, "next-navigation.js"),
543
+ "next/router": join6(stubsDir, "next-router.js"),
544
+ "next/image": join6(stubsDir, "next-image.js"),
545
+ "next/link": join6(stubsDir, "next-link.js"),
546
+ "next/font/google": join6(stubsDir, "next-font.js"),
547
+ "next/font/local": join6(stubsDir, "next-font.js"),
548
+ "next/headers": join6(stubsDir, "next-headers.js"),
549
+ "server-only": join6(stubsDir, "server-only.js"),
550
+ "client-only": join6(stubsDir, "server-only.js")
551
+ };
552
+ }
553
+ getOptimizeDeps(projectRoot) {
554
+ const base = ["react", "react-dom/client"];
555
+ const optional = [
556
+ "@mui/material",
557
+ "@mui/icons-material",
558
+ "@emotion/react",
559
+ "@emotion/styled",
560
+ "@chakra-ui/react",
561
+ "styled-components"
562
+ ];
563
+ return [
564
+ ...base,
565
+ ...optional.filter((p) => hasInstalled(projectRoot, p.split("/")[0]))
566
+ ];
567
+ }
568
+ async getPlugins(_root) {
569
+ return [...await loadReactPlugin(), rscDirectivePlugin()];
570
+ }
571
+ useFrameshotVite() {
572
+ return true;
573
+ }
574
+ skipProjectConfig() {
575
+ return true;
576
+ }
577
+ };
578
+
579
+ // src/infrastructure/adapters/nuxt.ts
580
+ import { join as join7 } from "path";
581
+ var NuxtAdapter = class {
582
+ id = "nuxt";
583
+ detect(projectRoot) {
584
+ return hasDep(join7(projectRoot, "package.json"), ["nuxt"]);
585
+ }
586
+ getAliases(_root, stubsDir) {
587
+ return {
588
+ "#app": join7(stubsDir, "nuxt-app.js"),
589
+ "#imports": join7(stubsDir, "nuxt-imports.js"),
590
+ "#head": join7(stubsDir, "nuxt-app.js")
591
+ };
592
+ }
593
+ getOptimizeDeps(_root) {
594
+ return ["vue"];
595
+ }
596
+ async getPlugins(_root) {
597
+ return loadPlugin("@vitejs/plugin-vue");
598
+ }
599
+ useFrameshotVite() {
600
+ return true;
601
+ }
602
+ skipProjectConfig() {
603
+ return true;
604
+ }
605
+ };
606
+
607
+ // src/infrastructure/adapters/preact.ts
608
+ import { join as join8 } from "path";
609
+ var PreactAdapter = class {
610
+ id = "preact";
611
+ detect(projectRoot) {
612
+ return hasDep(join8(projectRoot, "package.json"), ["preact"]);
613
+ }
614
+ getAliases() {
615
+ return {};
616
+ }
617
+ getOptimizeDeps() {
618
+ return ["preact", "preact/hooks"];
619
+ }
620
+ async getPlugins(_root) {
621
+ return loadPlugin("@preact/preset-vite");
622
+ }
623
+ useFrameshotVite() {
624
+ return true;
625
+ }
626
+ skipProjectConfig() {
627
+ return true;
628
+ }
629
+ };
630
+
631
+ // src/infrastructure/adapters/qwik.ts
632
+ import { join as join9 } from "path";
633
+ var QwikAdapter = class {
634
+ id = "qwik";
635
+ detect(projectRoot) {
636
+ return hasDep(join9(projectRoot, "package.json"), ["@builder.io/qwik"]);
637
+ }
638
+ getAliases(_root, stubsDir) {
639
+ return { "@builder.io/qwik-city": join9(stubsDir, "qwik-city.js") };
640
+ }
641
+ getOptimizeDeps(_root) {
642
+ return ["@builder.io/qwik"];
643
+ }
644
+ async getPlugins(_root) {
645
+ return loadPlugin("@builder.io/qwik/optimizer", "qwikVite");
646
+ }
647
+ useFrameshotVite() {
648
+ return false;
649
+ }
650
+ skipProjectConfig() {
651
+ return true;
652
+ }
653
+ };
654
+
655
+ // src/infrastructure/adapters/react-router.ts
656
+ import { join as join10 } from "path";
657
+ var ReactRouterAdapter = class {
658
+ id = "react-router";
659
+ detect(projectRoot) {
660
+ return hasDep(join10(projectRoot, "package.json"), [
661
+ "@react-router/dev",
662
+ "@remix-run/react"
663
+ ]);
664
+ }
665
+ getAliases(_root, stubsDir) {
666
+ return {
667
+ "react-router": join10(stubsDir, "react-router.js"),
668
+ "react-router-dom": join10(stubsDir, "react-router.js"),
669
+ "@remix-run/react": join10(stubsDir, "react-router.js")
670
+ };
671
+ }
672
+ getOptimizeDeps(_root) {
673
+ return ["react", "react-dom/client"];
674
+ }
675
+ async getPlugins(_root) {
676
+ return [...await loadReactPlugin(), rscDirectivePlugin()];
677
+ }
678
+ useFrameshotVite() {
679
+ return true;
680
+ }
681
+ skipProjectConfig() {
682
+ return true;
683
+ }
684
+ };
685
+
686
+ // src/infrastructure/adapters/solid.ts
687
+ import { join as join11 } from "path";
688
+ var SolidAdapter = class {
689
+ id = "solid";
690
+ detect(projectRoot) {
691
+ return hasDep(join11(projectRoot, "package.json"), ["solid-js"]);
692
+ }
693
+ getAliases(_root, stubsDir) {
694
+ return { "@solidjs/router": join11(stubsDir, "solid-router.js") };
695
+ }
696
+ getOptimizeDeps() {
697
+ return ["solid-js", "solid-js/web"];
698
+ }
699
+ async getPlugins(_root) {
700
+ return loadPlugin("vite-plugin-solid");
701
+ }
702
+ useFrameshotVite() {
703
+ return true;
704
+ }
705
+ skipProjectConfig() {
706
+ return true;
707
+ }
708
+ };
709
+
710
+ // src/infrastructure/adapters/solid-start.ts
711
+ import { join as join12 } from "path";
712
+ var SolidStartAdapter = class {
713
+ id = "solid-start";
714
+ detect(projectRoot) {
715
+ return hasDep(join12(projectRoot, "package.json"), ["@solidjs/start"]);
716
+ }
717
+ getAliases(_root, stubsDir) {
718
+ return {
719
+ "@solidjs/start": join12(stubsDir, "solid-start.js"),
720
+ "@solidjs/start/router": join12(stubsDir, "solid-router.js"),
721
+ "@solidjs/router": join12(stubsDir, "solid-router.js")
722
+ };
723
+ }
724
+ getOptimizeDeps(_root) {
725
+ return ["solid-js", "solid-js/web"];
726
+ }
727
+ async getPlugins(_root) {
728
+ return loadPlugin("vite-plugin-solid");
729
+ }
730
+ useFrameshotVite() {
731
+ return true;
732
+ }
733
+ skipProjectConfig() {
734
+ return true;
735
+ }
736
+ };
737
+
738
+ // src/infrastructure/adapters/svelte.ts
739
+ import { join as join13 } from "path";
740
+ var SvelteAdapter = class {
741
+ id = "svelte";
742
+ detect(projectRoot) {
743
+ return hasDep(join13(projectRoot, "package.json"), ["svelte"]);
744
+ }
745
+ getAliases() {
746
+ return {};
747
+ }
748
+ getOptimizeDeps() {
749
+ return [];
750
+ }
751
+ async getPlugins(_root) {
752
+ return loadPlugin("@sveltejs/vite-plugin-svelte", "svelte");
753
+ }
754
+ useFrameshotVite() {
755
+ return true;
756
+ }
757
+ skipProjectConfig() {
758
+ return true;
759
+ }
760
+ };
761
+
762
+ // src/infrastructure/adapters/sveltekit.ts
763
+ import { join as join14 } from "path";
764
+ var SvelteKitAdapter = class {
765
+ id = "sveltekit";
766
+ detect(projectRoot) {
767
+ return hasDep(join14(projectRoot, "package.json"), ["@sveltejs/kit"]);
768
+ }
769
+ getAliases(_root, stubsDir) {
770
+ return {
771
+ "$app/navigation": join14(stubsDir, "sveltekit-navigation.js"),
772
+ "$app/stores": join14(stubsDir, "sveltekit-stores.js"),
773
+ "$app/environment": join14(stubsDir, "sveltekit-environment.js")
774
+ };
775
+ }
776
+ getOptimizeDeps(_root) {
777
+ return [];
778
+ }
779
+ async getPlugins(_root) {
780
+ return loadPlugin("@sveltejs/vite-plugin-svelte", "svelte");
781
+ }
782
+ useFrameshotVite() {
783
+ return true;
784
+ }
785
+ skipProjectConfig() {
786
+ return true;
787
+ }
788
+ };
789
+
790
+ // src/infrastructure/adapters/vike.ts
791
+ import { join as join15 } from "path";
792
+ var VikeAdapter = class {
793
+ id = "vike";
794
+ detect(projectRoot) {
795
+ return hasDep(join15(projectRoot, "package.json"), ["vike"]);
796
+ }
797
+ getAliases(_root, stubsDir) {
798
+ return {
799
+ vike: join15(stubsDir, "vike.js"),
800
+ "vike/client/router": join15(stubsDir, "vike.js")
801
+ };
802
+ }
803
+ getOptimizeDeps(projectRoot) {
804
+ const deps = [];
805
+ if (hasInstalled(projectRoot, "react"))
806
+ deps.push("react", "react-dom/client");
807
+ if (hasInstalled(projectRoot, "vue")) deps.push("vue");
808
+ return deps;
809
+ }
810
+ async getPlugins(projectRoot) {
811
+ const plugins = [];
812
+ if (hasInstalled(projectRoot, "react"))
813
+ plugins.push(...await loadReactPlugin());
814
+ if (hasInstalled(projectRoot, "vue"))
815
+ plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
816
+ return plugins;
817
+ }
818
+ useFrameshotVite() {
819
+ return true;
820
+ }
821
+ skipProjectConfig() {
822
+ return true;
823
+ }
824
+ };
825
+
826
+ // src/infrastructure/adapters/vite-react.ts
827
+ import { existsSync as existsSync3 } from "fs";
828
+ import { join as join16 } from "path";
829
+ var ViteReactAdapter = class {
830
+ id = "vite-react";
831
+ detect(projectRoot) {
832
+ return hasDep(join16(projectRoot, "package.json"), ["react"]) && this.hasViteConfig(projectRoot);
833
+ }
834
+ hasViteConfig(root) {
835
+ return ["vite.config.ts", "vite.config.js", "vite.config.mjs"].some(
836
+ (n) => existsSync3(join16(root, n))
837
+ );
838
+ }
839
+ getAliases() {
840
+ return {};
841
+ }
842
+ getOptimizeDeps() {
843
+ return ["react", "react-dom/client"];
844
+ }
845
+ async getPlugins() {
846
+ return [];
847
+ }
848
+ useFrameshotVite() {
849
+ return false;
850
+ }
851
+ skipProjectConfig() {
852
+ return false;
853
+ }
854
+ };
855
+
856
+ // src/infrastructure/adapters/vue.ts
857
+ import { join as join17 } from "path";
858
+ var VueAdapter = class {
859
+ id = "vue";
860
+ detect(projectRoot) {
861
+ return hasDep(join17(projectRoot, "package.json"), ["vue"]);
862
+ }
863
+ getAliases() {
864
+ return {};
865
+ }
866
+ getOptimizeDeps() {
867
+ return ["vue"];
868
+ }
869
+ async getPlugins(_root) {
870
+ return loadPlugin("@vitejs/plugin-vue");
871
+ }
872
+ useFrameshotVite() {
873
+ return true;
874
+ }
875
+ skipProjectConfig() {
876
+ return true;
877
+ }
878
+ };
879
+
880
+ // src/infrastructure/adapters/registry.ts
881
+ var ADAPTERS = [
882
+ // Meta-frameworks first — most specific match wins.
883
+ new NextJsAdapter(),
884
+ new NuxtAdapter(),
885
+ new SvelteKitAdapter(),
886
+ new SolidStartAdapter(),
887
+ new ReactRouterAdapter(),
888
+ new GatsbyAdapter(),
889
+ new QwikAdapter(),
890
+ new AstroAdapter(),
891
+ new VikeAdapter(),
892
+ // Core UI libraries.
893
+ new VueAdapter(),
894
+ new SvelteAdapter(),
895
+ new SolidAdapter(),
896
+ new PreactAdapter(),
897
+ new LitAdapter(),
898
+ new ViteReactAdapter(),
899
+ // Catch-all.
900
+ new GenericAdapter()
901
+ ];
902
+ function selectAdapter(projectRoot) {
903
+ for (const adapter of ADAPTERS) {
904
+ if (adapter.detect(projectRoot)) return adapter;
905
+ }
906
+ return new GenericAdapter();
907
+ }
908
+
909
+ // src/infrastructure/vite-bundler.ts
910
+ var STUBS_DIR = join18(dirname2(fileURLToPath(import.meta.url)), "stubs");
297
911
  var ViteBundler = class {
298
912
  servers = /* @__PURE__ */ new Map();
299
913
  detector = new ProjectDetector();
300
914
  async getUrl(filePath, options = {}) {
301
915
  const absPath = resolve2(filePath);
302
- const project = options.projectRoot ? this.detector.detect(join2(options.projectRoot, "package.json")) : this.detector.detect(absPath);
303
- if (!project.hasVite) {
916
+ const project = options.projectRoot ? this.detector.detect(join18(options.projectRoot, "package.json")) : this.detector.detect(absPath);
917
+ const adapter = selectAdapter(project.root);
918
+ if (!project.hasVite && !adapter.useFrameshotVite()) {
304
919
  throw new Error(
305
920
  `Vite not found in ${project.root}. Install vite: npm install -D vite`
306
921
  );
@@ -312,7 +927,7 @@ var ViteBundler = class {
312
927
  options.props,
313
928
  project.root
314
929
  );
315
- const cached = await this.ensureServer(project);
930
+ const cached = await this.ensureServer(project, adapter);
316
931
  cached.currentEntry = entryCode;
317
932
  const mod = cached.server.moduleGraph.getModuleById(
318
933
  "\0virtual:frameshot-entry"
@@ -332,17 +947,94 @@ var ViteBundler = class {
332
947
  }
333
948
  this.servers.clear();
334
949
  }
335
- async ensureServer(project) {
950
+ // Public for testing
951
+ generateEntry(componentPath, framework, props, projectRoot) {
952
+ const propsJson = props ? JSON.stringify(props) : "{}";
953
+ const cssImport = projectRoot ? this.findGlobalCss(projectRoot) : "";
954
+ switch (framework) {
955
+ case "react": {
956
+ const hasRouter = projectRoot && this.hasPackage(projectRoot, "react-router-dom");
957
+ const routerImport = hasRouter ? `import { MemoryRouter } from "react-router-dom";` : "";
958
+ const wrap = hasRouter ? (inner) => `createElement(MemoryRouter, null, ${inner})` : (inner) => inner;
959
+ return `${cssImport}
960
+ import { createElement, Suspense } from "react";
961
+ import { createRoot } from "react-dom/client";
962
+ ${routerImport}
963
+ import Component from "${componentPath}";
964
+ const props = ${propsJson};
965
+ const root = createRoot(document.getElementById("app"));
966
+ root.render(${wrap(`createElement(Suspense, { fallback: createElement("div", null, "Loading...") }, createElement(Component, props))`)});
967
+ `;
968
+ }
969
+ case "vue":
970
+ return `${cssImport}
971
+ import { createApp } from "vue";
972
+ import Component from "${componentPath}";
973
+ const props = ${propsJson};
974
+ createApp(Component, props).mount("#app");
975
+ `;
976
+ case "svelte":
977
+ return `${cssImport}
978
+ import Component from "${componentPath}";
979
+ const props = ${propsJson};
980
+ const target = document.getElementById("app");
981
+ // Svelte 5 uses mount(); Svelte 4 uses new Component(). Try v5 first.
982
+ try {
983
+ const svelte = await import("svelte");
984
+ if (typeof svelte.mount === "function") {
985
+ svelte.mount(Component, { target, props });
986
+ } else {
987
+ new Component({ target, props });
988
+ }
989
+ } catch {
990
+ new Component({ target, props });
991
+ }
992
+ `;
993
+ case "solid":
994
+ return `${cssImport}
995
+ import { render } from "solid-js/web";
996
+ import Component from "${componentPath}";
997
+ const props = ${propsJson};
998
+ render(() => Component(props), document.getElementById("app"));
999
+ `;
1000
+ case "preact":
1001
+ return `${cssImport}
1002
+ import { h, render } from "preact";
1003
+ import Component from "${componentPath}";
1004
+ const props = ${propsJson};
1005
+ render(h(Component, props), document.getElementById("app"));
1006
+ `;
1007
+ default:
1008
+ return `${cssImport}
1009
+ const res = await fetch("${componentPath}");
1010
+ const html = await res.text();
1011
+ document.getElementById("app").innerHTML = html;
1012
+ `;
1013
+ }
1014
+ }
1015
+ async ensureServer(project, adapter) {
336
1016
  const existing = this.servers.get(project.root);
337
1017
  if (existing) return existing;
338
- const vite = await this.importVite(project.root);
1018
+ const vite = adapter.useFrameshotVite() ? await this.importFrameshotVite() : await this.importVite(project.root);
339
1019
  if (!vite) {
340
- throw new Error(`Failed to import vite from ${project.root}`);
1020
+ throw new Error(`Failed to import vite for ${project.root}`);
1021
+ }
1022
+ const adapterPlugins = await adapter.getPlugins(project.root);
1023
+ const adapterAliases = adapter.getAliases(project.root, STUBS_DIR);
1024
+ const aliasEntries = [];
1025
+ for (const [key, value] of Object.entries(project.pathAliases)) {
1026
+ aliasEntries.push({
1027
+ find: new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`),
1028
+ replacement: `${value}/`
1029
+ });
1030
+ }
1031
+ for (const [key, value] of Object.entries(adapterAliases)) {
1032
+ aliasEntries.push({ find: key, replacement: value });
341
1033
  }
342
1034
  const self = this;
343
1035
  const server = await vite.createServer({
344
1036
  root: project.root,
345
- configFile: project.viteConfigPath ?? false,
1037
+ configFile: adapter.skipProjectConfig() ? false : project.viteConfigPath ?? false,
346
1038
  server: {
347
1039
  port: 0,
348
1040
  strictPort: false,
@@ -350,6 +1042,10 @@ var ViteBundler = class {
350
1042
  hmr: false
351
1043
  },
352
1044
  logLevel: "error",
1045
+ resolve: aliasEntries.length > 0 ? {
1046
+ alias: aliasEntries,
1047
+ dedupe: ["react", "react-dom", "vue", "svelte"]
1048
+ } : void 0,
353
1049
  plugins: [
354
1050
  {
355
1051
  name: "frameshot-virtual",
@@ -396,10 +1092,11 @@ var ViteBundler = class {
396
1092
  }
397
1093
  });
398
1094
  }
399
- }
1095
+ },
1096
+ ...adapterPlugins
400
1097
  ],
401
1098
  optimizeDeps: {
402
- include: this.getOptimizeDepsInclude(project.framework)
1099
+ include: adapter.getOptimizeDeps(project.root)
403
1100
  }
404
1101
  });
405
1102
  await server.listen();
@@ -408,15 +1105,16 @@ var ViteBundler = class {
408
1105
  const cached = {
409
1106
  server,
410
1107
  port,
411
- currentEntry: ""
1108
+ currentEntry: "",
1109
+ adapter
412
1110
  };
413
1111
  this.servers.set(project.root, cached);
414
1112
  return cached;
415
1113
  }
416
- async importVite(projectRoot) {
1114
+ async importFrameshotVite() {
417
1115
  try {
418
- const require2 = createRequire(join2(projectRoot, "package.json"));
419
- const vitePath = require2.resolve("vite");
1116
+ const requireFromHere = createRequire(import.meta.url);
1117
+ const vitePath = requireFromHere.resolve("vite");
420
1118
  const mod = await import(vitePath);
421
1119
  if (mod && typeof mod === "object" && "createServer" in mod) {
422
1120
  return mod;
@@ -432,54 +1130,27 @@ var ViteBundler = class {
432
1130
  }
433
1131
  return null;
434
1132
  }
1133
+ async importVite(projectRoot) {
1134
+ try {
1135
+ const require2 = createRequire(join18(projectRoot, "package.json"));
1136
+ const vitePath = require2.resolve("vite");
1137
+ const mod = await import(vitePath);
1138
+ if (mod && typeof mod === "object" && "createServer" in mod) {
1139
+ return mod;
1140
+ }
1141
+ } catch {
1142
+ }
1143
+ return this.importFrameshotVite();
1144
+ }
435
1145
  hasPackage(projectRoot, name) {
436
1146
  try {
437
- const require2 = createRequire(join2(projectRoot, "package.json"));
1147
+ const require2 = createRequire(join18(projectRoot, "package.json"));
438
1148
  require2.resolve(name);
439
1149
  return true;
440
1150
  } catch {
441
1151
  return false;
442
1152
  }
443
1153
  }
444
- generateEntry(componentPath, framework, props, projectRoot) {
445
- const propsJson = props ? JSON.stringify(props) : "{}";
446
- const cssImport = projectRoot ? this.findGlobalCss(projectRoot) : "";
447
- switch (framework) {
448
- case "react": {
449
- const hasRouter = projectRoot && this.hasPackage(projectRoot, "react-router-dom");
450
- const routerImport = hasRouter ? `import { MemoryRouter } from "react-router-dom";` : "";
451
- const wrap = hasRouter ? (inner) => `createElement(MemoryRouter, null, ${inner})` : (inner) => inner;
452
- return `${cssImport}
453
- import { createElement, Suspense } from "react";
454
- import { createRoot } from "react-dom/client";
455
- ${routerImport}
456
- import Component from "${componentPath}";
457
- const props = ${propsJson};
458
- const root = createRoot(document.getElementById("app"));
459
- root.render(${wrap(`createElement(Suspense, { fallback: createElement("div", null, "Loading...") }, createElement(Component, props))`)});
460
- `;
461
- }
462
- case "vue":
463
- return `${cssImport}
464
- import { createApp } from "vue";
465
- import Component from "${componentPath}";
466
- const props = ${propsJson};
467
- createApp(Component, props).mount("#app");
468
- `;
469
- case "svelte":
470
- return `${cssImport}
471
- import Component from "${componentPath}";
472
- const props = ${propsJson};
473
- new Component({ target: document.getElementById("app"), props });
474
- `;
475
- default:
476
- return `${cssImport}
477
- const res = await fetch("${componentPath}");
478
- const html = await res.text();
479
- document.getElementById("app").innerHTML = html;
480
- `;
481
- }
482
- }
483
1154
  findGlobalCss(projectRoot) {
484
1155
  const candidates = [
485
1156
  "src/index.css",
@@ -491,25 +1162,13 @@ document.getElementById("app").innerHTML = html;
491
1162
  "app/globals.css"
492
1163
  ];
493
1164
  for (const candidate of candidates) {
494
- const fullPath = join2(projectRoot, candidate);
495
- if (existsSync2(fullPath)) {
1165
+ const fullPath = join18(projectRoot, candidate);
1166
+ if (existsSync4(fullPath)) {
496
1167
  return `import "${fullPath}";`;
497
1168
  }
498
1169
  }
499
1170
  return "";
500
1171
  }
501
- getOptimizeDepsInclude(framework) {
502
- switch (framework) {
503
- case "react":
504
- return ["react", "react-dom/client"];
505
- case "vue":
506
- return ["vue"];
507
- case "svelte":
508
- return [];
509
- default:
510
- return [];
511
- }
512
- }
513
1172
  };
514
1173
 
515
1174
  // src/use-cases/audit.ts
@@ -591,34 +1250,9 @@ var AuditUseCase = class {
591
1250
  }
592
1251
  };
593
1252
 
594
- // src/domain/types.ts
595
- var DEFAULT_RENDER_OPTIONS = {
596
- viewport: { width: 1280, height: 800 },
597
- engines: ["chromium"],
598
- fullPage: true,
599
- darkMode: false,
600
- css: "",
601
- tailwindVersion: "3",
602
- waitFor: 0,
603
- autoFit: false
604
- };
605
- var DEVICE_PRESETS = {
606
- mobile: { width: 375, height: 667 },
607
- tablet: { width: 768, height: 1024 },
608
- desktop: { width: 1280, height: 800 }
609
- };
610
- var EXT_TO_FRAMEWORK = {
611
- ".jsx": "react",
612
- ".tsx": "react",
613
- ".vue": "vue",
614
- ".svelte": "svelte",
615
- ".html": "html",
616
- ".htm": "html"
617
- };
618
-
619
1253
  // src/use-cases/catalog.ts
620
1254
  import { readdirSync, statSync } from "fs";
621
- import { extname, join as join3, relative } from "path";
1255
+ import { extname, join as join19, relative } from "path";
622
1256
  var CatalogUseCase = class {
623
1257
  constructor(renderUseCase) {
624
1258
  this.renderUseCase = renderUseCase;
@@ -676,7 +1310,7 @@ var CatalogUseCase = class {
676
1310
  const entries = readdirSync(dir);
677
1311
  for (const entry of entries) {
678
1312
  if (entry.startsWith(".") || entry === "node_modules") continue;
679
- const fullPath = join3(dir, entry);
1313
+ const fullPath = join19(dir, entry);
680
1314
  const stat = statSync(fullPath);
681
1315
  if (stat.isDirectory() && recursive) {
682
1316
  files.push(...this.scanDirectory(fullPath, recursive));
@@ -740,7 +1374,7 @@ var DiffUseCase = class {
740
1374
  };
741
1375
 
742
1376
  // src/use-cases/render.ts
743
- import { readFileSync as readFileSync2 } from "fs";
1377
+ import { readFileSync as readFileSync3 } from "fs";
744
1378
  import { extname as extname2 } from "path";
745
1379
 
746
1380
  // src/infrastructure/page-utils.ts
@@ -825,7 +1459,7 @@ var RenderUseCase = class {
825
1459
  );
826
1460
  }
827
1461
  }
828
- const code = readFileSync2(filePath, "utf-8");
1462
+ const code = readFileSync3(filePath, "utf-8");
829
1463
  const results = await this.render(code, framework, options);
830
1464
  return { results, mode: "cdn" };
831
1465
  }
@@ -962,6 +1596,23 @@ var RenderUseCase = class {
962
1596
  const viewport = options.autoFit ? await this.resolveAutoFitViewport(page, url, options) : options.viewport;
963
1597
  await page.setViewportSize(viewport);
964
1598
  await this.navigateAndWait(page, url, options.waitFor);
1599
+ const overlayState = await page.evaluate(() => {
1600
+ const overlay = document.querySelector("vite-error-overlay");
1601
+ const app = document.getElementById("app");
1602
+ const hasContent = !!app?.firstElementChild;
1603
+ if (!overlay) return { overlay: null, hasContent };
1604
+ const shadow = overlay.shadowRoot;
1605
+ const messageEl = shadow?.querySelector(".message-body");
1606
+ return {
1607
+ overlay: messageEl?.textContent?.trim() ?? "Vite error overlay",
1608
+ hasContent
1609
+ };
1610
+ }).catch(() => ({ overlay: null, hasContent: false }));
1611
+ if (overlayState.overlay && !overlayState.hasContent) {
1612
+ cleanup();
1613
+ this.pool.releasePage(engine, page);
1614
+ throw new Error(`Vite compilation error: ${overlayState.overlay}`);
1615
+ }
965
1616
  const result = await takeScreenshot(
966
1617
  page,
967
1618
  engine,
@@ -1106,6 +1757,8 @@ var SnapshotUseCase = class {
1106
1757
 
1107
1758
  export {
1108
1759
  __export,
1760
+ DEVICE_PRESETS,
1761
+ EXT_TO_FRAMEWORK,
1109
1762
  BrowserPool,
1110
1763
  HtmlBuilder,
1111
1764
  ImageComparator,
@@ -1113,8 +1766,6 @@ export {
1113
1766
  ProjectDetector,
1114
1767
  ViteBundler,
1115
1768
  AuditUseCase,
1116
- DEVICE_PRESETS,
1117
- EXT_TO_FRAMEWORK,
1118
1769
  CatalogUseCase,
1119
1770
  DiffUseCase,
1120
1771
  RenderUseCase,