frameshot-mcp 0.8.0 → 0.10.0
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/README.md +68 -122
- package/action.yml +14 -5
- package/dist/{chunk-MA3FOIQY.js → chunk-6OTGZDUU.js} +1 -1
- package/dist/{chunk-PYWXJZTZ.js → chunk-WACVDBUF.js} +809 -120
- package/dist/cli.js +123 -125
- package/dist/index.js +390 -532
- package/dist/renderer.d.ts +21 -3
- package/dist/renderer.js +1 -1
- package/dist/stubs/gatsby.js +20 -0
- package/dist/stubs/next-font.js +9 -0
- package/dist/stubs/next-headers.js +20 -0
- package/dist/stubs/next-image.js +34 -0
- package/dist/stubs/next-link.js +25 -0
- package/dist/stubs/next-navigation.js +17 -0
- package/dist/stubs/next-router.js +19 -0
- package/dist/stubs/nuxt-app.js +37 -0
- package/dist/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/qwik-city.js +33 -0
- package/dist/stubs/react-router.js +67 -0
- package/dist/stubs/server-only.js +2 -0
- package/dist/stubs/solid-router.js +27 -0
- package/dist/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/gatsby.js +20 -0
- package/dist/stubs/stubs/next-font.js +9 -0
- package/dist/stubs/stubs/next-headers.js +20 -0
- package/dist/stubs/stubs/next-image.js +34 -0
- package/dist/stubs/stubs/next-link.js +25 -0
- package/dist/stubs/stubs/next-navigation.js +17 -0
- package/dist/stubs/stubs/next-router.js +19 -0
- package/dist/stubs/stubs/nuxt-app.js +37 -0
- package/dist/stubs/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/stubs/qwik-city.js +33 -0
- package/dist/stubs/stubs/react-router.js +67 -0
- package/dist/stubs/stubs/server-only.js +2 -0
- package/dist/stubs/stubs/solid-router.js +27 -0
- package/dist/stubs/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/stubs/vike.js +11 -0
- package/dist/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/vike.js +11 -0
- package/package.json +14 -3
- package/scripts/render-changed.mjs +75 -21
- package/dist/chunk-3CDSNOX5.js +0 -869
- package/dist/chunk-3LVWVDET.js +0 -849
- package/dist/chunk-47YJG5HR.js +0 -690
- package/dist/chunk-67JZQ6OI.js +0 -819
- package/dist/chunk-AUACBLHM.js +0 -191
- package/dist/chunk-AZCGKIMU.js +0 -850
- package/dist/chunk-B3CLIGWU.js +0 -786
- package/dist/chunk-C6QSY4WR.js +0 -811
- package/dist/chunk-DX54PJKO.js +0 -603
- package/dist/chunk-EMCJGIMY.js +0 -984
- package/dist/chunk-FQNWGR62.js +0 -849
- package/dist/chunk-FTYTZW6D.js +0 -203
- package/dist/chunk-GVOEFYEX.js +0 -139
- package/dist/chunk-JGVKYXY2.js +0 -857
- package/dist/chunk-JYPEA4P2.js +0 -846
- package/dist/chunk-KHK35HDD.js +0 -855
- package/dist/chunk-L2CADTS7.js +0 -191
- package/dist/chunk-O7NWWFIU.js +0 -871
- package/dist/chunk-Q7A3DLED.js +0 -848
- package/dist/chunk-Q7NQA4ZM.js +0 -1095
- package/dist/chunk-SIA6XEHM.js +0 -811
- package/dist/chunk-ST35YDI6.js +0 -834
- package/dist/chunk-T5OBJK35.js +0 -855
- package/dist/chunk-U3GHS7KO.js +0 -837
- package/dist/chunk-WS2ASCD6.js +0 -683
- package/dist/chunk-WZMHVSUA.js +0 -847
- 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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
@@ -235,6 +264,7 @@ var SnapshotStore = class {
|
|
|
235
264
|
// src/infrastructure/project-detector.ts
|
|
236
265
|
import { existsSync, readFileSync } from "fs";
|
|
237
266
|
import { dirname, join, resolve } from "path";
|
|
267
|
+
import { parseTsconfig } from "get-tsconfig";
|
|
238
268
|
var VITE_CONFIG_NAMES = [
|
|
239
269
|
"vite.config.ts",
|
|
240
270
|
"vite.config.js",
|
|
@@ -245,9 +275,12 @@ var ProjectDetector = class {
|
|
|
245
275
|
detect(filePath) {
|
|
246
276
|
const root = this.findProjectRoot(filePath);
|
|
247
277
|
const viteConfigPath = this.findViteConfig(root);
|
|
248
|
-
const
|
|
278
|
+
const pkg = this.readPackageJson(root);
|
|
279
|
+
const framework = this.detectFramework(pkg);
|
|
249
280
|
const hasVite = this.checkViteAvailable(root);
|
|
250
|
-
|
|
281
|
+
const isNextJs = this.checkIsNextJs(pkg);
|
|
282
|
+
const pathAliases = this.readTsconfigAliases(root);
|
|
283
|
+
return { root, viteConfigPath, framework, hasVite, isNextJs, pathAliases };
|
|
251
284
|
}
|
|
252
285
|
findProjectRoot(startPath) {
|
|
253
286
|
let dir = dirname(resolve(startPath));
|
|
@@ -268,39 +301,616 @@ var ProjectDetector = class {
|
|
|
268
301
|
}
|
|
269
302
|
return void 0;
|
|
270
303
|
}
|
|
271
|
-
|
|
304
|
+
readPackageJson(root) {
|
|
272
305
|
const pkgPath = join(root, "package.json");
|
|
273
|
-
if (!existsSync(pkgPath)) return
|
|
306
|
+
if (!existsSync(pkgPath)) return null;
|
|
274
307
|
try {
|
|
275
|
-
|
|
276
|
-
const allDeps = {
|
|
277
|
-
...pkg.dependencies,
|
|
278
|
-
...pkg.devDependencies
|
|
279
|
-
};
|
|
280
|
-
if (allDeps.react || allDeps["react-dom"]) return "react";
|
|
281
|
-
if (allDeps.vue) return "vue";
|
|
282
|
-
if (allDeps.svelte) return "svelte";
|
|
308
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
283
309
|
} catch {
|
|
310
|
+
return null;
|
|
284
311
|
}
|
|
312
|
+
}
|
|
313
|
+
detectFramework(pkg) {
|
|
314
|
+
if (!pkg) return "html";
|
|
315
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
316
|
+
if (allDeps["solid-js"]) return "solid";
|
|
317
|
+
if (allDeps.preact) return "preact";
|
|
318
|
+
if (allDeps.react || allDeps["react-dom"]) return "react";
|
|
319
|
+
if (allDeps.vue) return "vue";
|
|
320
|
+
if (allDeps.svelte) return "svelte";
|
|
285
321
|
return "html";
|
|
286
322
|
}
|
|
287
323
|
checkViteAvailable(root) {
|
|
288
324
|
const vitePkgPath = join(root, "node_modules", "vite", "package.json");
|
|
289
325
|
return existsSync(vitePkgPath);
|
|
290
326
|
}
|
|
327
|
+
checkIsNextJs(pkg) {
|
|
328
|
+
if (!pkg) return false;
|
|
329
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
330
|
+
return !!allDeps.next;
|
|
331
|
+
}
|
|
332
|
+
readTsconfigAliases(root) {
|
|
333
|
+
const tscPath = join(root, "tsconfig.json");
|
|
334
|
+
if (!existsSync(tscPath)) return {};
|
|
335
|
+
try {
|
|
336
|
+
const tsconfig = parseTsconfig(tscPath);
|
|
337
|
+
const paths = tsconfig.compilerOptions?.paths ?? {};
|
|
338
|
+
const baseUrl = tsconfig.compilerOptions?.baseUrl ?? ".";
|
|
339
|
+
const result = {};
|
|
340
|
+
for (const [alias, targets] of Object.entries(paths)) {
|
|
341
|
+
const key = alias.replace(/\/\*$/, "");
|
|
342
|
+
const target = (targets[0] ?? "").replace(/\/\*$/, "");
|
|
343
|
+
result[key] = join(root, baseUrl, target);
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
} catch {
|
|
347
|
+
return {};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
291
350
|
};
|
|
292
351
|
|
|
293
352
|
// src/infrastructure/vite-bundler.ts
|
|
294
|
-
import { existsSync as
|
|
353
|
+
import { existsSync as existsSync4 } from "fs";
|
|
295
354
|
import { createRequire } from "module";
|
|
296
|
-
import { join as
|
|
355
|
+
import { dirname as dirname2, join as join18, resolve as resolve2 } from "path";
|
|
356
|
+
import { fileURLToPath } from "url";
|
|
357
|
+
|
|
358
|
+
// src/infrastructure/adapters/astro.ts
|
|
359
|
+
import { join as join3 } from "path";
|
|
360
|
+
|
|
361
|
+
// src/infrastructure/adapters/helpers.ts
|
|
362
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
363
|
+
import { join as join2 } from "path";
|
|
364
|
+
function hasDep(pkgPath, names) {
|
|
365
|
+
if (!existsSync2(pkgPath)) return false;
|
|
366
|
+
try {
|
|
367
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
368
|
+
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
369
|
+
return names.some((n) => !!all[n]);
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function hasInstalled(projectRoot, name) {
|
|
375
|
+
return existsSync2(join2(projectRoot, "node_modules", ...name.split("/")));
|
|
376
|
+
}
|
|
377
|
+
async function loadPlugin(pkg, fnName) {
|
|
378
|
+
try {
|
|
379
|
+
const mod = await import(pkg);
|
|
380
|
+
const candidate = fnName ? mod[fnName] : mod.default ?? mod;
|
|
381
|
+
if (typeof candidate === "function") {
|
|
382
|
+
const result = candidate();
|
|
383
|
+
return Array.isArray(result) ? result : [result];
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
process.stderr.write(`[frameshot] ${pkg} not available
|
|
387
|
+
`);
|
|
388
|
+
}
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
function loadReactPlugin() {
|
|
392
|
+
return loadPlugin("@vitejs/plugin-react");
|
|
393
|
+
}
|
|
394
|
+
function rscDirectivePlugin() {
|
|
395
|
+
return {
|
|
396
|
+
name: "frameshot-rsc-directives",
|
|
397
|
+
transform(code) {
|
|
398
|
+
return code.replace(/^['"]use (client|server)['"];?\s*/m, "");
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/infrastructure/adapters/astro.ts
|
|
404
|
+
var AstroAdapter = class {
|
|
405
|
+
id = "astro";
|
|
406
|
+
detect(projectRoot) {
|
|
407
|
+
return hasDep(join3(projectRoot, "package.json"), ["astro"]);
|
|
408
|
+
}
|
|
409
|
+
getAliases() {
|
|
410
|
+
return {};
|
|
411
|
+
}
|
|
412
|
+
getOptimizeDeps() {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
async getPlugins(projectRoot) {
|
|
416
|
+
const plugins = [];
|
|
417
|
+
if (hasInstalled(projectRoot, "react"))
|
|
418
|
+
plugins.push(...await loadReactPlugin());
|
|
419
|
+
if (hasInstalled(projectRoot, "vue"))
|
|
420
|
+
plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
|
|
421
|
+
if (hasInstalled(projectRoot, "svelte"))
|
|
422
|
+
plugins.push(
|
|
423
|
+
...await loadPlugin("@sveltejs/vite-plugin-svelte", "svelte")
|
|
424
|
+
);
|
|
425
|
+
if (hasInstalled(projectRoot, "solid-js"))
|
|
426
|
+
plugins.push(...await loadPlugin("vite-plugin-solid"));
|
|
427
|
+
return plugins;
|
|
428
|
+
}
|
|
429
|
+
useFrameshotVite() {
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
skipProjectConfig() {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/infrastructure/adapters/gatsby.ts
|
|
438
|
+
import { join as join4 } from "path";
|
|
439
|
+
var GatsbyAdapter = class {
|
|
440
|
+
id = "gatsby";
|
|
441
|
+
detect(projectRoot) {
|
|
442
|
+
return hasDep(join4(projectRoot, "package.json"), ["gatsby"]);
|
|
443
|
+
}
|
|
444
|
+
getAliases(_root, stubsDir) {
|
|
445
|
+
return { gatsby: join4(stubsDir, "gatsby.js") };
|
|
446
|
+
}
|
|
447
|
+
getOptimizeDeps(_root) {
|
|
448
|
+
return ["react", "react-dom/client"];
|
|
449
|
+
}
|
|
450
|
+
async getPlugins(_root) {
|
|
451
|
+
return loadReactPlugin();
|
|
452
|
+
}
|
|
453
|
+
useFrameshotVite() {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
skipProjectConfig() {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/infrastructure/adapters/generic.ts
|
|
462
|
+
var GenericAdapter = class {
|
|
463
|
+
id = "generic";
|
|
464
|
+
detect() {
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
getAliases() {
|
|
468
|
+
return {};
|
|
469
|
+
}
|
|
470
|
+
getOptimizeDeps(projectRoot) {
|
|
471
|
+
const deps = [];
|
|
472
|
+
if (hasInstalled(projectRoot, "react"))
|
|
473
|
+
deps.push("react", "react-dom/client");
|
|
474
|
+
if (hasInstalled(projectRoot, "vue")) deps.push("vue");
|
|
475
|
+
if (hasInstalled(projectRoot, "solid-js"))
|
|
476
|
+
deps.push("solid-js", "solid-js/web");
|
|
477
|
+
if (hasInstalled(projectRoot, "preact")) deps.push("preact");
|
|
478
|
+
return deps;
|
|
479
|
+
}
|
|
480
|
+
async getPlugins(projectRoot) {
|
|
481
|
+
const plugins = [];
|
|
482
|
+
if (hasInstalled(projectRoot, "react"))
|
|
483
|
+
plugins.push(...await loadReactPlugin());
|
|
484
|
+
if (hasInstalled(projectRoot, "vue"))
|
|
485
|
+
plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
|
|
486
|
+
if (hasInstalled(projectRoot, "svelte"))
|
|
487
|
+
plugins.push(
|
|
488
|
+
...await loadPlugin("@sveltejs/vite-plugin-svelte", "svelte")
|
|
489
|
+
);
|
|
490
|
+
if (hasInstalled(projectRoot, "solid-js"))
|
|
491
|
+
plugins.push(...await loadPlugin("vite-plugin-solid"));
|
|
492
|
+
if (hasInstalled(projectRoot, "preact"))
|
|
493
|
+
plugins.push(...await loadPlugin("@preact/preset-vite"));
|
|
494
|
+
return plugins;
|
|
495
|
+
}
|
|
496
|
+
useFrameshotVite() {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
skipProjectConfig() {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/infrastructure/adapters/lit.ts
|
|
505
|
+
import { join as join5 } from "path";
|
|
506
|
+
var LitAdapter = class {
|
|
507
|
+
id = "lit";
|
|
508
|
+
detect(projectRoot) {
|
|
509
|
+
return hasDep(join5(projectRoot, "package.json"), ["lit", "lit-element"]);
|
|
510
|
+
}
|
|
511
|
+
getAliases() {
|
|
512
|
+
return {};
|
|
513
|
+
}
|
|
514
|
+
getOptimizeDeps() {
|
|
515
|
+
return ["lit"];
|
|
516
|
+
}
|
|
517
|
+
async getPlugins(_root) {
|
|
518
|
+
return [];
|
|
519
|
+
}
|
|
520
|
+
useFrameshotVite() {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
skipProjectConfig() {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// src/infrastructure/adapters/next.ts
|
|
529
|
+
import { join as join6 } from "path";
|
|
530
|
+
var NextJsAdapter = class {
|
|
531
|
+
id = "next";
|
|
532
|
+
detect(projectRoot) {
|
|
533
|
+
return hasDep(join6(projectRoot, "package.json"), ["next"]);
|
|
534
|
+
}
|
|
535
|
+
getAliases(_root, stubsDir) {
|
|
536
|
+
return {
|
|
537
|
+
"next/navigation": join6(stubsDir, "next-navigation.js"),
|
|
538
|
+
"next/router": join6(stubsDir, "next-router.js"),
|
|
539
|
+
"next/image": join6(stubsDir, "next-image.js"),
|
|
540
|
+
"next/link": join6(stubsDir, "next-link.js"),
|
|
541
|
+
"next/font/google": join6(stubsDir, "next-font.js"),
|
|
542
|
+
"next/font/local": join6(stubsDir, "next-font.js"),
|
|
543
|
+
"next/headers": join6(stubsDir, "next-headers.js"),
|
|
544
|
+
"server-only": join6(stubsDir, "server-only.js"),
|
|
545
|
+
"client-only": join6(stubsDir, "server-only.js")
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
getOptimizeDeps(projectRoot) {
|
|
549
|
+
const base = ["react", "react-dom/client"];
|
|
550
|
+
const optional = [
|
|
551
|
+
"@mui/material",
|
|
552
|
+
"@mui/icons-material",
|
|
553
|
+
"@emotion/react",
|
|
554
|
+
"@emotion/styled",
|
|
555
|
+
"@chakra-ui/react",
|
|
556
|
+
"styled-components"
|
|
557
|
+
];
|
|
558
|
+
return [
|
|
559
|
+
...base,
|
|
560
|
+
...optional.filter((p) => hasInstalled(projectRoot, p.split("/")[0]))
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
async getPlugins(_root) {
|
|
564
|
+
return [...await loadReactPlugin(), rscDirectivePlugin()];
|
|
565
|
+
}
|
|
566
|
+
useFrameshotVite() {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
skipProjectConfig() {
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// src/infrastructure/adapters/nuxt.ts
|
|
575
|
+
import { join as join7 } from "path";
|
|
576
|
+
var NuxtAdapter = class {
|
|
577
|
+
id = "nuxt";
|
|
578
|
+
detect(projectRoot) {
|
|
579
|
+
return hasDep(join7(projectRoot, "package.json"), ["nuxt"]);
|
|
580
|
+
}
|
|
581
|
+
getAliases(_root, stubsDir) {
|
|
582
|
+
return {
|
|
583
|
+
"#app": join7(stubsDir, "nuxt-app.js"),
|
|
584
|
+
"#imports": join7(stubsDir, "nuxt-imports.js"),
|
|
585
|
+
"#head": join7(stubsDir, "nuxt-app.js")
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
getOptimizeDeps(_root) {
|
|
589
|
+
return ["vue"];
|
|
590
|
+
}
|
|
591
|
+
async getPlugins(_root) {
|
|
592
|
+
return loadPlugin("@vitejs/plugin-vue");
|
|
593
|
+
}
|
|
594
|
+
useFrameshotVite() {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
skipProjectConfig() {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// src/infrastructure/adapters/preact.ts
|
|
603
|
+
import { join as join8 } from "path";
|
|
604
|
+
var PreactAdapter = class {
|
|
605
|
+
id = "preact";
|
|
606
|
+
detect(projectRoot) {
|
|
607
|
+
return hasDep(join8(projectRoot, "package.json"), ["preact"]);
|
|
608
|
+
}
|
|
609
|
+
getAliases() {
|
|
610
|
+
return {};
|
|
611
|
+
}
|
|
612
|
+
getOptimizeDeps() {
|
|
613
|
+
return ["preact", "preact/hooks"];
|
|
614
|
+
}
|
|
615
|
+
async getPlugins(_root) {
|
|
616
|
+
return loadPlugin("@preact/preset-vite");
|
|
617
|
+
}
|
|
618
|
+
useFrameshotVite() {
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
skipProjectConfig() {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
// src/infrastructure/adapters/qwik.ts
|
|
627
|
+
import { join as join9 } from "path";
|
|
628
|
+
var QwikAdapter = class {
|
|
629
|
+
id = "qwik";
|
|
630
|
+
detect(projectRoot) {
|
|
631
|
+
return hasDep(join9(projectRoot, "package.json"), ["@builder.io/qwik"]);
|
|
632
|
+
}
|
|
633
|
+
getAliases(_root, stubsDir) {
|
|
634
|
+
return { "@builder.io/qwik-city": join9(stubsDir, "qwik-city.js") };
|
|
635
|
+
}
|
|
636
|
+
getOptimizeDeps(_root) {
|
|
637
|
+
return ["@builder.io/qwik"];
|
|
638
|
+
}
|
|
639
|
+
async getPlugins(_root) {
|
|
640
|
+
return loadPlugin("@builder.io/qwik/optimizer", "qwikVite");
|
|
641
|
+
}
|
|
642
|
+
useFrameshotVite() {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
skipProjectConfig() {
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/infrastructure/adapters/react-router.ts
|
|
651
|
+
import { join as join10 } from "path";
|
|
652
|
+
var ReactRouterAdapter = class {
|
|
653
|
+
id = "react-router";
|
|
654
|
+
detect(projectRoot) {
|
|
655
|
+
return hasDep(join10(projectRoot, "package.json"), [
|
|
656
|
+
"@react-router/dev",
|
|
657
|
+
"@remix-run/react"
|
|
658
|
+
]);
|
|
659
|
+
}
|
|
660
|
+
getAliases(_root, stubsDir) {
|
|
661
|
+
return {
|
|
662
|
+
"react-router": join10(stubsDir, "react-router.js"),
|
|
663
|
+
"react-router-dom": join10(stubsDir, "react-router.js"),
|
|
664
|
+
"@remix-run/react": join10(stubsDir, "react-router.js")
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
getOptimizeDeps(_root) {
|
|
668
|
+
return ["react", "react-dom/client"];
|
|
669
|
+
}
|
|
670
|
+
async getPlugins(_root) {
|
|
671
|
+
return [...await loadReactPlugin(), rscDirectivePlugin()];
|
|
672
|
+
}
|
|
673
|
+
useFrameshotVite() {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
skipProjectConfig() {
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/infrastructure/adapters/solid.ts
|
|
682
|
+
import { join as join11 } from "path";
|
|
683
|
+
var SolidAdapter = class {
|
|
684
|
+
id = "solid";
|
|
685
|
+
detect(projectRoot) {
|
|
686
|
+
return hasDep(join11(projectRoot, "package.json"), ["solid-js"]);
|
|
687
|
+
}
|
|
688
|
+
getAliases(_root, stubsDir) {
|
|
689
|
+
return { "@solidjs/router": join11(stubsDir, "solid-router.js") };
|
|
690
|
+
}
|
|
691
|
+
getOptimizeDeps() {
|
|
692
|
+
return ["solid-js", "solid-js/web"];
|
|
693
|
+
}
|
|
694
|
+
async getPlugins(_root) {
|
|
695
|
+
return loadPlugin("vite-plugin-solid");
|
|
696
|
+
}
|
|
697
|
+
useFrameshotVite() {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
skipProjectConfig() {
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
// src/infrastructure/adapters/solid-start.ts
|
|
706
|
+
import { join as join12 } from "path";
|
|
707
|
+
var SolidStartAdapter = class {
|
|
708
|
+
id = "solid-start";
|
|
709
|
+
detect(projectRoot) {
|
|
710
|
+
return hasDep(join12(projectRoot, "package.json"), ["@solidjs/start"]);
|
|
711
|
+
}
|
|
712
|
+
getAliases(_root, stubsDir) {
|
|
713
|
+
return {
|
|
714
|
+
"@solidjs/start": join12(stubsDir, "solid-start.js"),
|
|
715
|
+
"@solidjs/start/router": join12(stubsDir, "solid-router.js"),
|
|
716
|
+
"@solidjs/router": join12(stubsDir, "solid-router.js")
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
getOptimizeDeps(_root) {
|
|
720
|
+
return ["solid-js", "solid-js/web"];
|
|
721
|
+
}
|
|
722
|
+
async getPlugins(_root) {
|
|
723
|
+
return loadPlugin("vite-plugin-solid");
|
|
724
|
+
}
|
|
725
|
+
useFrameshotVite() {
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
skipProjectConfig() {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// src/infrastructure/adapters/svelte.ts
|
|
734
|
+
import { join as join13 } from "path";
|
|
735
|
+
var SvelteAdapter = class {
|
|
736
|
+
id = "svelte";
|
|
737
|
+
detect(projectRoot) {
|
|
738
|
+
return hasDep(join13(projectRoot, "package.json"), ["svelte"]);
|
|
739
|
+
}
|
|
740
|
+
getAliases() {
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
getOptimizeDeps() {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
async getPlugins(_root) {
|
|
747
|
+
return loadPlugin("@sveltejs/vite-plugin-svelte", "svelte");
|
|
748
|
+
}
|
|
749
|
+
useFrameshotVite() {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
skipProjectConfig() {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/infrastructure/adapters/sveltekit.ts
|
|
758
|
+
import { join as join14 } from "path";
|
|
759
|
+
var SvelteKitAdapter = class {
|
|
760
|
+
id = "sveltekit";
|
|
761
|
+
detect(projectRoot) {
|
|
762
|
+
return hasDep(join14(projectRoot, "package.json"), ["@sveltejs/kit"]);
|
|
763
|
+
}
|
|
764
|
+
getAliases(_root, stubsDir) {
|
|
765
|
+
return {
|
|
766
|
+
"$app/navigation": join14(stubsDir, "sveltekit-navigation.js"),
|
|
767
|
+
"$app/stores": join14(stubsDir, "sveltekit-stores.js"),
|
|
768
|
+
"$app/environment": join14(stubsDir, "sveltekit-environment.js")
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
getOptimizeDeps(_root) {
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
774
|
+
async getPlugins(_root) {
|
|
775
|
+
return loadPlugin("@sveltejs/vite-plugin-svelte", "svelte");
|
|
776
|
+
}
|
|
777
|
+
useFrameshotVite() {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
skipProjectConfig() {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/infrastructure/adapters/vike.ts
|
|
786
|
+
import { join as join15 } from "path";
|
|
787
|
+
var VikeAdapter = class {
|
|
788
|
+
id = "vike";
|
|
789
|
+
detect(projectRoot) {
|
|
790
|
+
return hasDep(join15(projectRoot, "package.json"), ["vike"]);
|
|
791
|
+
}
|
|
792
|
+
getAliases(_root, stubsDir) {
|
|
793
|
+
return {
|
|
794
|
+
vike: join15(stubsDir, "vike.js"),
|
|
795
|
+
"vike/client/router": join15(stubsDir, "vike.js")
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
getOptimizeDeps(projectRoot) {
|
|
799
|
+
const deps = [];
|
|
800
|
+
if (hasInstalled(projectRoot, "react"))
|
|
801
|
+
deps.push("react", "react-dom/client");
|
|
802
|
+
if (hasInstalled(projectRoot, "vue")) deps.push("vue");
|
|
803
|
+
return deps;
|
|
804
|
+
}
|
|
805
|
+
async getPlugins(projectRoot) {
|
|
806
|
+
const plugins = [];
|
|
807
|
+
if (hasInstalled(projectRoot, "react"))
|
|
808
|
+
plugins.push(...await loadReactPlugin());
|
|
809
|
+
if (hasInstalled(projectRoot, "vue"))
|
|
810
|
+
plugins.push(...await loadPlugin("@vitejs/plugin-vue"));
|
|
811
|
+
return plugins;
|
|
812
|
+
}
|
|
813
|
+
useFrameshotVite() {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
skipProjectConfig() {
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// src/infrastructure/adapters/vite-react.ts
|
|
822
|
+
import { existsSync as existsSync3 } from "fs";
|
|
823
|
+
import { join as join16 } from "path";
|
|
824
|
+
var ViteReactAdapter = class {
|
|
825
|
+
id = "vite-react";
|
|
826
|
+
detect(projectRoot) {
|
|
827
|
+
return hasDep(join16(projectRoot, "package.json"), ["react"]) && this.hasViteConfig(projectRoot);
|
|
828
|
+
}
|
|
829
|
+
hasViteConfig(root) {
|
|
830
|
+
return ["vite.config.ts", "vite.config.js", "vite.config.mjs"].some(
|
|
831
|
+
(n) => existsSync3(join16(root, n))
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
getAliases() {
|
|
835
|
+
return {};
|
|
836
|
+
}
|
|
837
|
+
getOptimizeDeps() {
|
|
838
|
+
return ["react", "react-dom/client"];
|
|
839
|
+
}
|
|
840
|
+
async getPlugins() {
|
|
841
|
+
return [];
|
|
842
|
+
}
|
|
843
|
+
useFrameshotVite() {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
skipProjectConfig() {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/infrastructure/adapters/vue.ts
|
|
852
|
+
import { join as join17 } from "path";
|
|
853
|
+
var VueAdapter = class {
|
|
854
|
+
id = "vue";
|
|
855
|
+
detect(projectRoot) {
|
|
856
|
+
return hasDep(join17(projectRoot, "package.json"), ["vue"]);
|
|
857
|
+
}
|
|
858
|
+
getAliases() {
|
|
859
|
+
return {};
|
|
860
|
+
}
|
|
861
|
+
getOptimizeDeps() {
|
|
862
|
+
return ["vue"];
|
|
863
|
+
}
|
|
864
|
+
async getPlugins(_root) {
|
|
865
|
+
return loadPlugin("@vitejs/plugin-vue");
|
|
866
|
+
}
|
|
867
|
+
useFrameshotVite() {
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
skipProjectConfig() {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// src/infrastructure/adapters/registry.ts
|
|
876
|
+
var ADAPTERS = [
|
|
877
|
+
// Meta-frameworks first — most specific match wins.
|
|
878
|
+
new NextJsAdapter(),
|
|
879
|
+
new NuxtAdapter(),
|
|
880
|
+
new SvelteKitAdapter(),
|
|
881
|
+
new SolidStartAdapter(),
|
|
882
|
+
new ReactRouterAdapter(),
|
|
883
|
+
new GatsbyAdapter(),
|
|
884
|
+
new QwikAdapter(),
|
|
885
|
+
new AstroAdapter(),
|
|
886
|
+
new VikeAdapter(),
|
|
887
|
+
// Core UI libraries.
|
|
888
|
+
new VueAdapter(),
|
|
889
|
+
new SvelteAdapter(),
|
|
890
|
+
new SolidAdapter(),
|
|
891
|
+
new PreactAdapter(),
|
|
892
|
+
new LitAdapter(),
|
|
893
|
+
new ViteReactAdapter(),
|
|
894
|
+
// Catch-all.
|
|
895
|
+
new GenericAdapter()
|
|
896
|
+
];
|
|
897
|
+
function selectAdapter(projectRoot) {
|
|
898
|
+
for (const adapter of ADAPTERS) {
|
|
899
|
+
if (adapter.detect(projectRoot)) return adapter;
|
|
900
|
+
}
|
|
901
|
+
return new GenericAdapter();
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/infrastructure/vite-bundler.ts
|
|
905
|
+
var STUBS_DIR = join18(dirname2(fileURLToPath(import.meta.url)), "stubs");
|
|
297
906
|
var ViteBundler = class {
|
|
298
907
|
servers = /* @__PURE__ */ new Map();
|
|
299
908
|
detector = new ProjectDetector();
|
|
300
909
|
async getUrl(filePath, options = {}) {
|
|
301
910
|
const absPath = resolve2(filePath);
|
|
302
|
-
const project = options.projectRoot ? this.detector.detect(
|
|
303
|
-
|
|
911
|
+
const project = options.projectRoot ? this.detector.detect(join18(options.projectRoot, "package.json")) : this.detector.detect(absPath);
|
|
912
|
+
const adapter = selectAdapter(project.root);
|
|
913
|
+
if (!project.hasVite && !adapter.useFrameshotVite()) {
|
|
304
914
|
throw new Error(
|
|
305
915
|
`Vite not found in ${project.root}. Install vite: npm install -D vite`
|
|
306
916
|
);
|
|
@@ -312,7 +922,7 @@ var ViteBundler = class {
|
|
|
312
922
|
options.props,
|
|
313
923
|
project.root
|
|
314
924
|
);
|
|
315
|
-
const cached = await this.ensureServer(project);
|
|
925
|
+
const cached = await this.ensureServer(project, adapter);
|
|
316
926
|
cached.currentEntry = entryCode;
|
|
317
927
|
const mod = cached.server.moduleGraph.getModuleById(
|
|
318
928
|
"\0virtual:frameshot-entry"
|
|
@@ -332,17 +942,94 @@ var ViteBundler = class {
|
|
|
332
942
|
}
|
|
333
943
|
this.servers.clear();
|
|
334
944
|
}
|
|
335
|
-
|
|
945
|
+
// Public for testing
|
|
946
|
+
generateEntry(componentPath, framework, props, projectRoot) {
|
|
947
|
+
const propsJson = props ? JSON.stringify(props) : "{}";
|
|
948
|
+
const cssImport = projectRoot ? this.findGlobalCss(projectRoot) : "";
|
|
949
|
+
switch (framework) {
|
|
950
|
+
case "react": {
|
|
951
|
+
const hasRouter = projectRoot && this.hasPackage(projectRoot, "react-router-dom");
|
|
952
|
+
const routerImport = hasRouter ? `import { MemoryRouter } from "react-router-dom";` : "";
|
|
953
|
+
const wrap = hasRouter ? (inner) => `createElement(MemoryRouter, null, ${inner})` : (inner) => inner;
|
|
954
|
+
return `${cssImport}
|
|
955
|
+
import { createElement, Suspense } from "react";
|
|
956
|
+
import { createRoot } from "react-dom/client";
|
|
957
|
+
${routerImport}
|
|
958
|
+
import Component from "${componentPath}";
|
|
959
|
+
const props = ${propsJson};
|
|
960
|
+
const root = createRoot(document.getElementById("app"));
|
|
961
|
+
root.render(${wrap(`createElement(Suspense, { fallback: createElement("div", null, "Loading...") }, createElement(Component, props))`)});
|
|
962
|
+
`;
|
|
963
|
+
}
|
|
964
|
+
case "vue":
|
|
965
|
+
return `${cssImport}
|
|
966
|
+
import { createApp } from "vue";
|
|
967
|
+
import Component from "${componentPath}";
|
|
968
|
+
const props = ${propsJson};
|
|
969
|
+
createApp(Component, props).mount("#app");
|
|
970
|
+
`;
|
|
971
|
+
case "svelte":
|
|
972
|
+
return `${cssImport}
|
|
973
|
+
import Component from "${componentPath}";
|
|
974
|
+
const props = ${propsJson};
|
|
975
|
+
const target = document.getElementById("app");
|
|
976
|
+
// Svelte 5 uses mount(); Svelte 4 uses new Component(). Try v5 first.
|
|
977
|
+
try {
|
|
978
|
+
const svelte = await import("svelte");
|
|
979
|
+
if (typeof svelte.mount === "function") {
|
|
980
|
+
svelte.mount(Component, { target, props });
|
|
981
|
+
} else {
|
|
982
|
+
new Component({ target, props });
|
|
983
|
+
}
|
|
984
|
+
} catch {
|
|
985
|
+
new Component({ target, props });
|
|
986
|
+
}
|
|
987
|
+
`;
|
|
988
|
+
case "solid":
|
|
989
|
+
return `${cssImport}
|
|
990
|
+
import { render } from "solid-js/web";
|
|
991
|
+
import Component from "${componentPath}";
|
|
992
|
+
const props = ${propsJson};
|
|
993
|
+
render(() => Component(props), document.getElementById("app"));
|
|
994
|
+
`;
|
|
995
|
+
case "preact":
|
|
996
|
+
return `${cssImport}
|
|
997
|
+
import { h, render } from "preact";
|
|
998
|
+
import Component from "${componentPath}";
|
|
999
|
+
const props = ${propsJson};
|
|
1000
|
+
render(h(Component, props), document.getElementById("app"));
|
|
1001
|
+
`;
|
|
1002
|
+
default:
|
|
1003
|
+
return `${cssImport}
|
|
1004
|
+
const res = await fetch("${componentPath}");
|
|
1005
|
+
const html = await res.text();
|
|
1006
|
+
document.getElementById("app").innerHTML = html;
|
|
1007
|
+
`;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
async ensureServer(project, adapter) {
|
|
336
1011
|
const existing = this.servers.get(project.root);
|
|
337
1012
|
if (existing) return existing;
|
|
338
|
-
const vite = await this.importVite(project.root);
|
|
1013
|
+
const vite = adapter.useFrameshotVite() ? await this.importFrameshotVite() : await this.importVite(project.root);
|
|
339
1014
|
if (!vite) {
|
|
340
|
-
throw new Error(`Failed to import vite
|
|
1015
|
+
throw new Error(`Failed to import vite for ${project.root}`);
|
|
1016
|
+
}
|
|
1017
|
+
const adapterPlugins = await adapter.getPlugins(project.root);
|
|
1018
|
+
const adapterAliases = adapter.getAliases(project.root, STUBS_DIR);
|
|
1019
|
+
const aliasEntries = [];
|
|
1020
|
+
for (const [key, value] of Object.entries(project.pathAliases)) {
|
|
1021
|
+
aliasEntries.push({
|
|
1022
|
+
find: new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`),
|
|
1023
|
+
replacement: `${value}/`
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
for (const [key, value] of Object.entries(adapterAliases)) {
|
|
1027
|
+
aliasEntries.push({ find: key, replacement: value });
|
|
341
1028
|
}
|
|
342
1029
|
const self = this;
|
|
343
1030
|
const server = await vite.createServer({
|
|
344
1031
|
root: project.root,
|
|
345
|
-
configFile: project.viteConfigPath ?? false,
|
|
1032
|
+
configFile: adapter.skipProjectConfig() ? false : project.viteConfigPath ?? false,
|
|
346
1033
|
server: {
|
|
347
1034
|
port: 0,
|
|
348
1035
|
strictPort: false,
|
|
@@ -350,6 +1037,10 @@ var ViteBundler = class {
|
|
|
350
1037
|
hmr: false
|
|
351
1038
|
},
|
|
352
1039
|
logLevel: "error",
|
|
1040
|
+
resolve: aliasEntries.length > 0 ? {
|
|
1041
|
+
alias: aliasEntries,
|
|
1042
|
+
dedupe: ["react", "react-dom", "vue", "svelte"]
|
|
1043
|
+
} : void 0,
|
|
353
1044
|
plugins: [
|
|
354
1045
|
{
|
|
355
1046
|
name: "frameshot-virtual",
|
|
@@ -396,10 +1087,11 @@ var ViteBundler = class {
|
|
|
396
1087
|
}
|
|
397
1088
|
});
|
|
398
1089
|
}
|
|
399
|
-
}
|
|
1090
|
+
},
|
|
1091
|
+
...adapterPlugins
|
|
400
1092
|
],
|
|
401
1093
|
optimizeDeps: {
|
|
402
|
-
include:
|
|
1094
|
+
include: adapter.getOptimizeDeps(project.root)
|
|
403
1095
|
}
|
|
404
1096
|
});
|
|
405
1097
|
await server.listen();
|
|
@@ -408,15 +1100,16 @@ var ViteBundler = class {
|
|
|
408
1100
|
const cached = {
|
|
409
1101
|
server,
|
|
410
1102
|
port,
|
|
411
|
-
currentEntry: ""
|
|
1103
|
+
currentEntry: "",
|
|
1104
|
+
adapter
|
|
412
1105
|
};
|
|
413
1106
|
this.servers.set(project.root, cached);
|
|
414
1107
|
return cached;
|
|
415
1108
|
}
|
|
416
|
-
async
|
|
1109
|
+
async importFrameshotVite() {
|
|
417
1110
|
try {
|
|
418
|
-
const
|
|
419
|
-
const vitePath =
|
|
1111
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
1112
|
+
const vitePath = requireFromHere.resolve("vite");
|
|
420
1113
|
const mod = await import(vitePath);
|
|
421
1114
|
if (mod && typeof mod === "object" && "createServer" in mod) {
|
|
422
1115
|
return mod;
|
|
@@ -432,54 +1125,27 @@ var ViteBundler = class {
|
|
|
432
1125
|
}
|
|
433
1126
|
return null;
|
|
434
1127
|
}
|
|
1128
|
+
async importVite(projectRoot) {
|
|
1129
|
+
try {
|
|
1130
|
+
const require2 = createRequire(join18(projectRoot, "package.json"));
|
|
1131
|
+
const vitePath = require2.resolve("vite");
|
|
1132
|
+
const mod = await import(vitePath);
|
|
1133
|
+
if (mod && typeof mod === "object" && "createServer" in mod) {
|
|
1134
|
+
return mod;
|
|
1135
|
+
}
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
return this.importFrameshotVite();
|
|
1139
|
+
}
|
|
435
1140
|
hasPackage(projectRoot, name) {
|
|
436
1141
|
try {
|
|
437
|
-
const require2 = createRequire(
|
|
1142
|
+
const require2 = createRequire(join18(projectRoot, "package.json"));
|
|
438
1143
|
require2.resolve(name);
|
|
439
1144
|
return true;
|
|
440
1145
|
} catch {
|
|
441
1146
|
return false;
|
|
442
1147
|
}
|
|
443
1148
|
}
|
|
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
1149
|
findGlobalCss(projectRoot) {
|
|
484
1150
|
const candidates = [
|
|
485
1151
|
"src/index.css",
|
|
@@ -491,25 +1157,13 @@ document.getElementById("app").innerHTML = html;
|
|
|
491
1157
|
"app/globals.css"
|
|
492
1158
|
];
|
|
493
1159
|
for (const candidate of candidates) {
|
|
494
|
-
const fullPath =
|
|
495
|
-
if (
|
|
1160
|
+
const fullPath = join18(projectRoot, candidate);
|
|
1161
|
+
if (existsSync4(fullPath)) {
|
|
496
1162
|
return `import "${fullPath}";`;
|
|
497
1163
|
}
|
|
498
1164
|
}
|
|
499
1165
|
return "";
|
|
500
1166
|
}
|
|
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
1167
|
};
|
|
514
1168
|
|
|
515
1169
|
// src/use-cases/audit.ts
|
|
@@ -591,34 +1245,9 @@ var AuditUseCase = class {
|
|
|
591
1245
|
}
|
|
592
1246
|
};
|
|
593
1247
|
|
|
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
1248
|
// src/use-cases/catalog.ts
|
|
620
1249
|
import { readdirSync, statSync } from "fs";
|
|
621
|
-
import { extname, join as
|
|
1250
|
+
import { extname, join as join19, relative } from "path";
|
|
622
1251
|
var CatalogUseCase = class {
|
|
623
1252
|
constructor(renderUseCase) {
|
|
624
1253
|
this.renderUseCase = renderUseCase;
|
|
@@ -676,7 +1305,7 @@ var CatalogUseCase = class {
|
|
|
676
1305
|
const entries = readdirSync(dir);
|
|
677
1306
|
for (const entry of entries) {
|
|
678
1307
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
679
|
-
const fullPath =
|
|
1308
|
+
const fullPath = join19(dir, entry);
|
|
680
1309
|
const stat = statSync(fullPath);
|
|
681
1310
|
if (stat.isDirectory() && recursive) {
|
|
682
1311
|
files.push(...this.scanDirectory(fullPath, recursive));
|
|
@@ -740,10 +1369,11 @@ var DiffUseCase = class {
|
|
|
740
1369
|
};
|
|
741
1370
|
|
|
742
1371
|
// src/use-cases/render.ts
|
|
743
|
-
import { readFileSync as
|
|
1372
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
744
1373
|
import { extname as extname2 } from "path";
|
|
745
1374
|
|
|
746
1375
|
// src/infrastructure/page-utils.ts
|
|
1376
|
+
import picomatch from "picomatch";
|
|
747
1377
|
function attachConsoleCapture(page) {
|
|
748
1378
|
const errors = [];
|
|
749
1379
|
const onMessage = (msg) => {
|
|
@@ -760,6 +1390,46 @@ function attachConsoleCapture(page) {
|
|
|
760
1390
|
}
|
|
761
1391
|
};
|
|
762
1392
|
}
|
|
1393
|
+
function normalizeResponse(value) {
|
|
1394
|
+
if (value && typeof value === "object" && "body" in value) {
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
return { body: value };
|
|
1398
|
+
}
|
|
1399
|
+
async function attachMocks(page, mocks) {
|
|
1400
|
+
const matchers = Object.entries(mocks).map(([pattern, value]) => ({
|
|
1401
|
+
pattern,
|
|
1402
|
+
isMatch: pattern.startsWith("http") ? (url) => url === pattern : picomatch(pattern, { dot: true }),
|
|
1403
|
+
response: normalizeResponse(value)
|
|
1404
|
+
}));
|
|
1405
|
+
if (matchers.length === 0) return;
|
|
1406
|
+
await page.route("**/*", async (route) => {
|
|
1407
|
+
const url = route.request().url();
|
|
1408
|
+
const pathname = (() => {
|
|
1409
|
+
try {
|
|
1410
|
+
return new URL(url).pathname;
|
|
1411
|
+
} catch {
|
|
1412
|
+
return url;
|
|
1413
|
+
}
|
|
1414
|
+
})();
|
|
1415
|
+
const hit = matchers.find((m) => m.isMatch(url)) ?? matchers.find((m) => m.isMatch(pathname));
|
|
1416
|
+
if (!hit) {
|
|
1417
|
+
await route.continue();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const spec = hit.response;
|
|
1421
|
+
const isString = typeof spec.body === "string";
|
|
1422
|
+
const isBinary = spec.body instanceof Uint8Array || spec.body instanceof ArrayBuffer;
|
|
1423
|
+
const body = isString ? spec.body : isBinary ? Buffer.from(spec.body) : JSON.stringify(spec.body ?? null);
|
|
1424
|
+
const contentType = spec.contentType ?? (isString ? "text/plain" : "application/json");
|
|
1425
|
+
await route.fulfill({
|
|
1426
|
+
status: spec.status ?? 200,
|
|
1427
|
+
contentType,
|
|
1428
|
+
body,
|
|
1429
|
+
headers: spec.headers
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
763
1433
|
async function takeScreenshot(page, engine, fullPage, errors, cleanup) {
|
|
764
1434
|
const screenshot = await page.screenshot({ type: "png", fullPage });
|
|
765
1435
|
const metrics = await page.evaluate(() => ({
|
|
@@ -825,7 +1495,7 @@ var RenderUseCase = class {
|
|
|
825
1495
|
);
|
|
826
1496
|
}
|
|
827
1497
|
}
|
|
828
|
-
const code =
|
|
1498
|
+
const code = readFileSync3(filePath, "utf-8");
|
|
829
1499
|
const results = await this.render(code, framework, options);
|
|
830
1500
|
return { results, mode: "cdn" };
|
|
831
1501
|
}
|
|
@@ -944,6 +1614,7 @@ var RenderUseCase = class {
|
|
|
944
1614
|
const page = await this.pool.getPage(engine);
|
|
945
1615
|
await page.setViewportSize(options.viewport);
|
|
946
1616
|
const { errors, cleanup } = attachConsoleCapture(page);
|
|
1617
|
+
if (options.mock) await attachMocks(page, options.mock);
|
|
947
1618
|
await page.setContent(html, { waitUntil: "load", timeout: 1e4 });
|
|
948
1619
|
if (options.waitFor > 0) await page.waitForTimeout(options.waitFor);
|
|
949
1620
|
const result = await takeScreenshot(
|
|
@@ -959,9 +1630,27 @@ var RenderUseCase = class {
|
|
|
959
1630
|
async renderUrl(engine, url, options) {
|
|
960
1631
|
const page = await this.pool.getPage(engine);
|
|
961
1632
|
const { errors, cleanup } = attachConsoleCapture(page);
|
|
1633
|
+
if (options.mock) await attachMocks(page, options.mock);
|
|
962
1634
|
const viewport = options.autoFit ? await this.resolveAutoFitViewport(page, url, options) : options.viewport;
|
|
963
1635
|
await page.setViewportSize(viewport);
|
|
964
1636
|
await this.navigateAndWait(page, url, options.waitFor);
|
|
1637
|
+
const overlayState = await page.evaluate(() => {
|
|
1638
|
+
const overlay = document.querySelector("vite-error-overlay");
|
|
1639
|
+
const app = document.getElementById("app");
|
|
1640
|
+
const hasContent = !!app?.firstElementChild;
|
|
1641
|
+
if (!overlay) return { overlay: null, hasContent };
|
|
1642
|
+
const shadow = overlay.shadowRoot;
|
|
1643
|
+
const messageEl = shadow?.querySelector(".message-body");
|
|
1644
|
+
return {
|
|
1645
|
+
overlay: messageEl?.textContent?.trim() ?? "Vite error overlay",
|
|
1646
|
+
hasContent
|
|
1647
|
+
};
|
|
1648
|
+
}).catch(() => ({ overlay: null, hasContent: false }));
|
|
1649
|
+
if (overlayState.overlay && !overlayState.hasContent) {
|
|
1650
|
+
cleanup();
|
|
1651
|
+
this.pool.releasePage(engine, page);
|
|
1652
|
+
throw new Error(`Vite compilation error: ${overlayState.overlay}`);
|
|
1653
|
+
}
|
|
965
1654
|
const result = await takeScreenshot(
|
|
966
1655
|
page,
|
|
967
1656
|
engine,
|
|
@@ -1106,6 +1795,8 @@ var SnapshotUseCase = class {
|
|
|
1106
1795
|
|
|
1107
1796
|
export {
|
|
1108
1797
|
__export,
|
|
1798
|
+
DEVICE_PRESETS,
|
|
1799
|
+
EXT_TO_FRAMEWORK,
|
|
1109
1800
|
BrowserPool,
|
|
1110
1801
|
HtmlBuilder,
|
|
1111
1802
|
ImageComparator,
|
|
@@ -1113,8 +1804,6 @@ export {
|
|
|
1113
1804
|
ProjectDetector,
|
|
1114
1805
|
ViteBundler,
|
|
1115
1806
|
AuditUseCase,
|
|
1116
|
-
DEVICE_PRESETS,
|
|
1117
|
-
EXT_TO_FRAMEWORK,
|
|
1118
1807
|
CatalogUseCase,
|
|
1119
1808
|
DiffUseCase,
|
|
1120
1809
|
RenderUseCase,
|