frameshot-mcp 0.3.0 → 0.7.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.
@@ -0,0 +1,203 @@
1
+ // src/infrastructure/snapshot-store.ts
2
+ var SnapshotStore = class {
3
+ store = /* @__PURE__ */ new Map();
4
+ save(key, image) {
5
+ this.store.set(key, { image, timestamp: Date.now() });
6
+ }
7
+ get(key) {
8
+ return this.store.get(key);
9
+ }
10
+ list() {
11
+ return Array.from(this.store.entries()).map(([key, val]) => ({
12
+ key,
13
+ timestamp: val.timestamp
14
+ }));
15
+ }
16
+ };
17
+
18
+ // src/use-cases/audit.ts
19
+ var AuditUseCase = class {
20
+ constructor(pool, htmlBuilder) {
21
+ this.pool = pool;
22
+ this.htmlBuilder = htmlBuilder;
23
+ }
24
+ pool;
25
+ htmlBuilder;
26
+ async auditA11y(code, framework, options = {}) {
27
+ const html = this.htmlBuilder.build(code, framework, {
28
+ darkMode: options.darkMode ?? false,
29
+ css: options.css ?? "",
30
+ tailwindVersion: options.tailwindVersion ?? "3"
31
+ });
32
+ const page = await this.pool.getPage("chromium");
33
+ const viewport = options.viewport ?? { width: 1280, height: 800 };
34
+ await this.pool.setViewport("chromium", viewport);
35
+ await page.setContent(html, { waitUntil: "load", timeout: 1e4 });
36
+ const axeSource = await import("axe-core").then((m) => m.source);
37
+ await page.addScriptTag({ content: axeSource });
38
+ const results = await page.evaluate(() => {
39
+ return window.axe.run();
40
+ });
41
+ return {
42
+ violations: results.violations.map(
43
+ (v) => ({
44
+ id: v.id,
45
+ impact: v.impact,
46
+ description: v.description,
47
+ helpUrl: v.helpUrl,
48
+ nodes: v.nodes.map((n) => ({
49
+ html: n.html,
50
+ target: n.target
51
+ }))
52
+ })
53
+ ),
54
+ passes: results.passes.length,
55
+ incomplete: results.incomplete.length
56
+ };
57
+ }
58
+ async perfAudit(code, framework, options = {}) {
59
+ const html = this.htmlBuilder.build(code, framework, {
60
+ darkMode: options.darkMode ?? false,
61
+ css: options.css ?? "",
62
+ tailwindVersion: options.tailwindVersion ?? "3"
63
+ });
64
+ const page = await this.pool.getPage("chromium");
65
+ const viewport = options.viewport ?? { width: 1280, height: 800 };
66
+ await this.pool.setViewport("chromium", viewport);
67
+ const start = performance.now();
68
+ await page.setContent(html, { waitUntil: "load", timeout: 1e4 });
69
+ const renderTimeMs = Math.round(performance.now() - start);
70
+ const metrics = await page.evaluate(() => {
71
+ const all = document.querySelectorAll("*");
72
+ let maxDepth = 0;
73
+ for (const el of all) {
74
+ let depth = 0;
75
+ let node = el;
76
+ while (node) {
77
+ depth++;
78
+ node = node.parentElement;
79
+ }
80
+ if (depth > maxDepth) maxDepth = depth;
81
+ }
82
+ return {
83
+ domElements: all.length,
84
+ domDepth: maxDepth,
85
+ scriptCount: document.querySelectorAll("script").length,
86
+ styleSheetCount: document.styleSheets.length,
87
+ imageCount: document.querySelectorAll("img").length,
88
+ totalDomSize: document.documentElement.outerHTML.length
89
+ };
90
+ });
91
+ return { renderTimeMs, ...metrics };
92
+ }
93
+ };
94
+
95
+ // src/use-cases/screenshot.ts
96
+ var ScreenshotUseCase = class {
97
+ constructor(pool) {
98
+ this.pool = pool;
99
+ }
100
+ pool;
101
+ async screenshotUrl(url, options = {}) {
102
+ const engines = options.engines ?? ["chromium"];
103
+ const { waitForNetworkIdle = true } = options;
104
+ return Promise.all(
105
+ engines.map(async (engine) => {
106
+ const { width = 1280, height = 800 } = options.viewport ?? {};
107
+ const fullPage = options.fullPage ?? true;
108
+ const waitFor = options.waitFor ?? 0;
109
+ const page = await this.pool.getPage(engine);
110
+ await this.pool.setViewport(engine, { width, height });
111
+ const consoleErrors = [];
112
+ const onError = (msg) => {
113
+ if (msg.type() === "error") consoleErrors.push(msg.text());
114
+ };
115
+ page.on("console", onError);
116
+ page.on("pageerror", (err) => consoleErrors.push(err.message));
117
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 });
118
+ if (waitForNetworkIdle) {
119
+ await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
120
+ });
121
+ }
122
+ if (options.waitForSelector) {
123
+ await page.waitForSelector(options.waitForSelector, {
124
+ timeout: 1e4
125
+ });
126
+ }
127
+ if (waitFor > 0) {
128
+ await page.waitForTimeout(waitFor);
129
+ }
130
+ const screenshot = await page.screenshot({ type: "png", fullPage });
131
+ const metrics = await page.evaluate(() => ({
132
+ w: document.documentElement.scrollWidth,
133
+ h: document.documentElement.scrollHeight
134
+ }));
135
+ page.removeListener("console", onError);
136
+ return {
137
+ engine,
138
+ image: screenshot.toString("base64"),
139
+ width: metrics.w,
140
+ height: metrics.h,
141
+ consoleErrors
142
+ };
143
+ })
144
+ );
145
+ }
146
+ async screenshotUrlWithRetry(url, options = {}) {
147
+ const { retryCount = 0, ...screenshotOpts } = options;
148
+ const maxAttempts = retryCount + 1;
149
+ let lastError;
150
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
151
+ try {
152
+ if (attempt > 0) {
153
+ const delay = 300 * 2 ** (attempt - 1);
154
+ await new Promise((resolve) => setTimeout(resolve, delay));
155
+ }
156
+ return await this.screenshotUrl(url, screenshotOpts);
157
+ } catch (error) {
158
+ lastError = error;
159
+ }
160
+ }
161
+ throw lastError;
162
+ }
163
+ };
164
+
165
+ // src/use-cases/snapshot.ts
166
+ var SnapshotUseCase = class {
167
+ constructor(store, renderUseCase, diffUseCase) {
168
+ this.store = store;
169
+ this.renderUseCase = renderUseCase;
170
+ this.diffUseCase = diffUseCase;
171
+ }
172
+ store;
173
+ renderUseCase;
174
+ diffUseCase;
175
+ async save(key, code, framework, options = {}) {
176
+ const [result] = await this.renderUseCase.render(code, framework, {
177
+ ...options,
178
+ engines: ["chromium"]
179
+ });
180
+ this.store.save(key, result.image);
181
+ return { image: result.image, width: result.width, height: result.height };
182
+ }
183
+ async check(key, code, framework, options = {}) {
184
+ const snapshot = this.store.get(key);
185
+ if (!snapshot) return null;
186
+ return this.diffUseCase.diffFromReference(
187
+ code,
188
+ framework,
189
+ snapshot.image,
190
+ options
191
+ );
192
+ }
193
+ list() {
194
+ return this.store.list();
195
+ }
196
+ };
197
+
198
+ export {
199
+ SnapshotStore,
200
+ AuditUseCase,
201
+ ScreenshotUseCase,
202
+ SnapshotUseCase
203
+ };