@noego/forge 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/forge.js +1 -0
- package/dist-ssr/test.cjs +15 -0
- package/dist-ssr/test.cjs.map +1 -1
- package/dist-ssr/test.d.ts +5 -0
- package/dist-ssr/test.js +15 -0
- package/dist-ssr/test.js.map +1 -1
- package/package.json +1 -1
package/bin/forge.js
CHANGED
package/dist-ssr/test.cjs
CHANGED
|
@@ -45,6 +45,17 @@ function _interopNamespaceDefault(e) {
|
|
|
45
45
|
}
|
|
46
46
|
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
|
|
47
47
|
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
48
|
+
class ForgeCLIRequiredError extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(
|
|
51
|
+
'This file must be run with the forge CLI, not directly with tsx/node.\n\nUsage:\n npx forge test/ui/your-test.ts\n npx forge "test/ui/**/*.test.ts"\n\nThe forge CLI provides the necessary Svelte loader for rendering components.'
|
|
52
|
+
);
|
|
53
|
+
__publicField(this, "name", "ForgeCLIRequiredError");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!process.env.FORGE_CLI) {
|
|
57
|
+
throw new ForgeCLIRequiredError();
|
|
58
|
+
}
|
|
48
59
|
class PlaywrightNotInstalledError extends Error {
|
|
49
60
|
constructor() {
|
|
50
61
|
super(
|
|
@@ -187,6 +198,9 @@ class ImageRenderer {
|
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
200
|
async function createImageRenderer(config) {
|
|
201
|
+
if (!process.env.FORGE_CLI) {
|
|
202
|
+
throw new ForgeCLIRequiredError();
|
|
203
|
+
}
|
|
190
204
|
const playwright = await loadPlaywright();
|
|
191
205
|
let browser;
|
|
192
206
|
try {
|
|
@@ -215,6 +229,7 @@ async function createImageRenderer(config) {
|
|
|
215
229
|
return new ImageRenderer(browser, staticRenderer, config);
|
|
216
230
|
}
|
|
217
231
|
exports.BrowserLaunchError = BrowserLaunchError;
|
|
232
|
+
exports.ForgeCLIRequiredError = ForgeCLIRequiredError;
|
|
218
233
|
exports.ImageRenderer = ImageRenderer;
|
|
219
234
|
exports.PlaywrightNotInstalledError = PlaywrightNotInstalledError;
|
|
220
235
|
exports.RenderError = RenderError;
|
package/dist-ssr/test.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.cjs","sources":["../src/test/index.ts"],"sourcesContent":["import { createStaticRenderer, type StaticRenderer } from '../static/index';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n// ─────────────────────────────────────────────────────────────\n// Error Classes\n// ─────────────────────────────────────────────────────────────\n\nexport class PlaywrightNotInstalledError extends Error {\n name = 'PlaywrightNotInstalledError' as const;\n constructor() {\n super(\n 'ImageRenderer requires Playwright. Install it with:\\n' +\n ' npm install playwright\\n' +\n ' npx playwright install chromium'\n );\n }\n}\n\nexport class BrowserLaunchError extends Error {\n name = 'BrowserLaunchError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\nexport class RenderTimeoutError extends Error {\n name = 'RenderTimeoutError' as const;\n constructor(timeoutMs: number) {\n super(`Render timed out after ${timeoutMs}ms`);\n }\n}\n\nexport class RenderError extends Error {\n name = 'RenderError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface ImageRendererConfig {\n /** Output directory for screenshots */\n outputDir: string;\n /** Default width (default: 1920) */\n width?: number;\n /** Default height (default: 1080) */\n height?: number;\n /** Device scale factor for retina (default: 1) */\n deviceScaleFactor?: number;\n /** Image format (default: 'png') */\n format?: 'png' | 'jpeg';\n /** JPEG quality 0-100 (default: 80) */\n quality?: number;\n /** Wait condition (default: 'networkidle') */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Timeout in ms (default: 10000) */\n timeoutMs?: number;\n /** Custom HTML template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */\n template?: string;\n\n // Static renderer config (one of these)\n /** Pre-created StaticRenderer instance */\n staticRenderer?: StaticRenderer;\n /** OR: Path to stitch.yaml */\n stitchConfig?: string;\n /** Component directory (required if stitchConfig provided) */\n componentDir?: string;\n}\n\nexport interface CaptureOptions {\n /** View component data */\n view?: any;\n /** Layout data (one object per layout) */\n layout?: any[];\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\nexport interface CaptureHtmlOptions {\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\ninterface CaptureResult {\n /** Raw image buffer */\n buffer: Buffer;\n /** Path where image was saved */\n path: string;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Playwright Loader\n// ─────────────────────────────────────────────────────────────\n\ntype PlaywrightModule = typeof import('playwright');\n\nasync function loadPlaywright(): Promise<PlaywrightModule> {\n try {\n const playwright = await import('playwright');\n return playwright;\n } catch {\n throw new PlaywrightNotInstalledError();\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// ImageRenderer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class ImageRenderer {\n private browser: any;\n private staticRenderer: StaticRenderer;\n private config: Required<Omit<ImageRendererConfig, 'staticRenderer' | 'stitchConfig' | 'componentDir' | 'template'>> & {\n outputDir: string;\n template?: string;\n };\n\n constructor(\n browser: any,\n staticRenderer: StaticRenderer,\n config: ImageRendererConfig\n ) {\n this.browser = browser;\n this.staticRenderer = staticRenderer;\n this.config = {\n outputDir: config.outputDir,\n width: config.width ?? 1920,\n height: config.height ?? 1080,\n deviceScaleFactor: config.deviceScaleFactor ?? 1,\n format: config.format ?? 'png',\n quality: config.quality ?? 80,\n waitUntil: config.waitUntil ?? 'networkidle',\n timeoutMs: config.timeoutMs ?? 10000,\n template: config.template,\n };\n }\n\n /**\n * Capture a route and save to outputDir\n */\n async capture(\n name: string,\n route: string,\n options?: CaptureOptions\n ): Promise<CaptureResult> {\n // Render route to HTML using StaticRenderer\n const { view, layout } = options ?? {};\n const renderResult = await this.staticRenderer.renderToPage({\n route,\n data: { view, layout },\n template: this.config.template,\n });\n\n // Capture the HTML\n return this.captureHtmlInternal(name, renderResult, options);\n }\n\n /**\n * Capture raw HTML and save to outputDir\n */\n async captureHtml(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n return this.captureHtmlInternal(name, html, options);\n }\n\n /**\n * Internal method to capture HTML to image\n */\n private async captureHtmlInternal(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n const width = options?.width ?? this.config.width;\n const height = options?.height ?? this.config.height;\n\n let context: any;\n let page: any;\n\n try {\n // Create new context with viewport\n context = await this.browser.newContext({\n viewport: { width, height },\n deviceScaleFactor: this.config.deviceScaleFactor,\n });\n\n // Create page\n page = await context.newPage();\n\n // Set content with timeout\n const waitUntilMapping = {\n 'load': 'load',\n 'domcontentloaded': 'domcontentloaded',\n 'networkidle': 'networkidle',\n } as const;\n\n try {\n await page.setContent(html, {\n waitUntil: waitUntilMapping[this.config.waitUntil],\n timeout: this.config.timeoutMs,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('timeout')) {\n throw new RenderTimeoutError(this.config.timeoutMs);\n }\n throw new RenderError(\n 'Failed to render HTML content',\n error instanceof Error ? error : undefined\n );\n }\n\n // Take screenshot\n const screenshotOptions: {\n type: 'png' | 'jpeg';\n quality?: number;\n fullPage: boolean;\n } = {\n type: this.config.format,\n fullPage: false,\n };\n\n // Quality only applies to JPEG\n if (this.config.format === 'jpeg') {\n screenshotOptions.quality = this.config.quality;\n }\n\n const screenshot = await page.screenshot(screenshotOptions);\n const buffer = Buffer.from(screenshot);\n\n // Generate filename: {name}@{width}x{height}.{format}\n const filename = `${name}@${width}x${height}.${this.config.format}`;\n const outputPath = path.join(this.config.outputDir, filename);\n\n // Ensure output directory exists\n await fs.promises.mkdir(this.config.outputDir, { recursive: true });\n\n // Write file\n await fs.promises.writeFile(outputPath, buffer);\n\n return { buffer, path: outputPath };\n\n } finally {\n // Cleanup context and page (browser stays open)\n if (page) {\n await page.close().catch(() => {});\n }\n if (context) {\n await context.close().catch(() => {});\n }\n }\n }\n\n /**\n * Close browser and clean up resources\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Factory Function\n// ─────────────────────────────────────────────────────────────\n\nexport async function createImageRenderer(\n config: ImageRendererConfig\n): Promise<ImageRenderer> {\n // Load Playwright dynamically\n const playwright = await loadPlaywright();\n\n // Launch browser\n let browser: any;\n try {\n browser = await playwright.chromium.launch({\n headless: true,\n });\n } catch (error) {\n throw new BrowserLaunchError(\n 'Failed to launch Chromium. Ensure it is installed with: npx playwright install chromium',\n error instanceof Error ? error : undefined\n );\n }\n\n // Get or create StaticRenderer\n let staticRenderer: StaticRenderer;\n if (config.staticRenderer) {\n staticRenderer = config.staticRenderer;\n } else if (config.stitchConfig && config.componentDir) {\n staticRenderer = await createStaticRenderer({\n stitchConfig: config.stitchConfig,\n componentDir: config.componentDir,\n });\n } else {\n throw new Error(\n 'ImageRendererConfig requires either staticRenderer or both stitchConfig and componentDir'\n );\n }\n\n return new ImageRenderer(browser, staticRenderer, config);\n}\n"],"names":["path","fs","createStaticRenderer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"test.cjs","sources":["../src/test/index.ts"],"sourcesContent":["import { createStaticRenderer, type StaticRenderer } from '../static/index';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n\n\n// ─────────────────────────────────────────────────────────────\n// Error Classes\n// ─────────────────────────────────────────────────────────────\nexport class ForgeCLIRequiredError extends Error {\n name = 'ForgeCLIRequiredError' as const;\n constructor() {\n super(\n 'This file must be run with the forge CLI, not directly with tsx/node.\\n\\n' +\n 'Usage:\\n' +\n ' npx forge test/ui/your-test.ts\\n' +\n ' npx forge \"test/ui/**/*.test.ts\"\\n\\n' +\n 'The forge CLI provides the necessary Svelte loader for rendering components.'\n );\n }\n}\n\n//We need to throw on import so that this crashes right away\nif (!process.env.FORGE_CLI) {\n throw new ForgeCLIRequiredError();\n}\n\n\nexport class PlaywrightNotInstalledError extends Error {\n name = 'PlaywrightNotInstalledError' as const;\n constructor() {\n super(\n 'ImageRenderer requires Playwright. Install it with:\\n' +\n ' npm install playwright\\n' +\n ' npx playwright install chromium'\n );\n }\n}\n\nexport class BrowserLaunchError extends Error {\n name = 'BrowserLaunchError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\nexport class RenderTimeoutError extends Error {\n name = 'RenderTimeoutError' as const;\n constructor(timeoutMs: number) {\n super(`Render timed out after ${timeoutMs}ms`);\n }\n}\n\nexport class RenderError extends Error {\n name = 'RenderError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface ImageRendererConfig {\n /** Output directory for screenshots */\n outputDir: string;\n /** Default width (default: 1920) */\n width?: number;\n /** Default height (default: 1080) */\n height?: number;\n /** Device scale factor for retina (default: 1) */\n deviceScaleFactor?: number;\n /** Image format (default: 'png') */\n format?: 'png' | 'jpeg';\n /** JPEG quality 0-100 (default: 80) */\n quality?: number;\n /** Wait condition (default: 'networkidle') */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Timeout in ms (default: 10000) */\n timeoutMs?: number;\n /** Custom HTML template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */\n template?: string;\n\n // Static renderer config (one of these)\n /** Pre-created StaticRenderer instance */\n staticRenderer?: StaticRenderer;\n /** OR: Path to stitch.yaml */\n stitchConfig?: string;\n /** Component directory (required if stitchConfig provided) */\n componentDir?: string;\n}\n\nexport interface CaptureOptions {\n /** View component data */\n view?: any;\n /** Layout data (one object per layout) */\n layout?: any[];\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\nexport interface CaptureHtmlOptions {\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\ninterface CaptureResult {\n /** Raw image buffer */\n buffer: Buffer;\n /** Path where image was saved */\n path: string;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Playwright Loader\n// ─────────────────────────────────────────────────────────────\n\ntype PlaywrightModule = typeof import('playwright');\n\nasync function loadPlaywright(): Promise<PlaywrightModule> {\n try {\n const playwright = await import('playwright');\n return playwright;\n } catch {\n throw new PlaywrightNotInstalledError();\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// ImageRenderer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class ImageRenderer {\n private browser: any;\n private staticRenderer: StaticRenderer;\n private config: Required<Omit<ImageRendererConfig, 'staticRenderer' | 'stitchConfig' | 'componentDir' | 'template'>> & {\n outputDir: string;\n template?: string;\n };\n\n constructor(\n browser: any,\n staticRenderer: StaticRenderer,\n config: ImageRendererConfig\n ) {\n this.browser = browser;\n this.staticRenderer = staticRenderer;\n this.config = {\n outputDir: config.outputDir,\n width: config.width ?? 1920,\n height: config.height ?? 1080,\n deviceScaleFactor: config.deviceScaleFactor ?? 1,\n format: config.format ?? 'png',\n quality: config.quality ?? 80,\n waitUntil: config.waitUntil ?? 'networkidle',\n timeoutMs: config.timeoutMs ?? 10000,\n template: config.template,\n };\n }\n\n /**\n * Capture a route and save to outputDir\n */\n async capture(\n name: string,\n route: string,\n options?: CaptureOptions\n ): Promise<CaptureResult> {\n // Render route to HTML using StaticRenderer\n const { view, layout } = options ?? {};\n const renderResult = await this.staticRenderer.renderToPage({\n route,\n data: { view, layout },\n template: this.config.template,\n });\n\n // Capture the HTML\n return this.captureHtmlInternal(name, renderResult, options);\n }\n\n /**\n * Capture raw HTML and save to outputDir\n */\n async captureHtml(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n return this.captureHtmlInternal(name, html, options);\n }\n\n /**\n * Internal method to capture HTML to image\n */\n private async captureHtmlInternal(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n const width = options?.width ?? this.config.width;\n const height = options?.height ?? this.config.height;\n\n let context: any;\n let page: any;\n\n try {\n // Create new context with viewport\n context = await this.browser.newContext({\n viewport: { width, height },\n deviceScaleFactor: this.config.deviceScaleFactor,\n });\n\n // Create page\n page = await context.newPage();\n\n // Set content with timeout\n const waitUntilMapping = {\n 'load': 'load',\n 'domcontentloaded': 'domcontentloaded',\n 'networkidle': 'networkidle',\n } as const;\n\n try {\n await page.setContent(html, {\n waitUntil: waitUntilMapping[this.config.waitUntil],\n timeout: this.config.timeoutMs,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('timeout')) {\n throw new RenderTimeoutError(this.config.timeoutMs);\n }\n throw new RenderError(\n 'Failed to render HTML content',\n error instanceof Error ? error : undefined\n );\n }\n\n // Take screenshot\n const screenshotOptions: {\n type: 'png' | 'jpeg';\n quality?: number;\n fullPage: boolean;\n } = {\n type: this.config.format,\n fullPage: false,\n };\n\n // Quality only applies to JPEG\n if (this.config.format === 'jpeg') {\n screenshotOptions.quality = this.config.quality;\n }\n\n const screenshot = await page.screenshot(screenshotOptions);\n const buffer = Buffer.from(screenshot);\n\n // Generate filename: {name}@{width}x{height}.{format}\n const filename = `${name}@${width}x${height}.${this.config.format}`;\n const outputPath = path.join(this.config.outputDir, filename);\n\n // Ensure output directory exists\n await fs.promises.mkdir(this.config.outputDir, { recursive: true });\n\n // Write file\n await fs.promises.writeFile(outputPath, buffer);\n\n return { buffer, path: outputPath };\n\n } finally {\n // Cleanup context and page (browser stays open)\n if (page) {\n await page.close().catch(() => {});\n }\n if (context) {\n await context.close().catch(() => {});\n }\n }\n }\n\n /**\n * Close browser and clean up resources\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Factory Function\n// ─────────────────────────────────────────────────────────────\n\nexport async function createImageRenderer(\n config: ImageRendererConfig\n): Promise<ImageRenderer> {\n // Check if running via forge CLI\n if (!process.env.FORGE_CLI) {\n throw new ForgeCLIRequiredError();\n }\n\n // Load Playwright dynamically\n const playwright = await loadPlaywright();\n\n // Launch browser\n let browser: any;\n try {\n browser = await playwright.chromium.launch({\n headless: true,\n });\n } catch (error) {\n throw new BrowserLaunchError(\n 'Failed to launch Chromium. Ensure it is installed with: npx playwright install chromium',\n error instanceof Error ? error : undefined\n );\n }\n\n // Get or create StaticRenderer\n let staticRenderer: StaticRenderer;\n if (config.staticRenderer) {\n staticRenderer = config.staticRenderer;\n } else if (config.stitchConfig && config.componentDir) {\n staticRenderer = await createStaticRenderer({\n stitchConfig: config.stitchConfig,\n componentDir: config.componentDir,\n });\n } else {\n throw new Error(\n 'ImageRendererConfig requires either staticRenderer or both stitchConfig and componentDir'\n );\n }\n\n return new ImageRenderer(browser, staticRenderer, config);\n}\n"],"names":["path","fs","createStaticRenderer"],"mappings":"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"}
|
package/dist-ssr/test.d.ts
CHANGED
|
@@ -58,6 +58,11 @@ declare class ComponentManager {
|
|
|
58
58
|
|
|
59
59
|
export declare function createImageRenderer(config: ImageRendererConfig): Promise<ImageRenderer>;
|
|
60
60
|
|
|
61
|
+
export declare class ForgeCLIRequiredError extends Error {
|
|
62
|
+
name: "ForgeCLIRequiredError";
|
|
63
|
+
constructor();
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
declare interface IComponentLoader {
|
|
62
67
|
load(component: string, options?: any): Promise<SvelteResource<SvelteComponent>>;
|
|
63
68
|
getComponentFullPath(component: string, options?: any): string;
|
package/dist-ssr/test.js
CHANGED
|
@@ -4,6 +4,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4
4
|
import { createStaticRenderer } from "./static.js";
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
|
+
class ForgeCLIRequiredError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(
|
|
10
|
+
'This file must be run with the forge CLI, not directly with tsx/node.\n\nUsage:\n npx forge test/ui/your-test.ts\n npx forge "test/ui/**/*.test.ts"\n\nThe forge CLI provides the necessary Svelte loader for rendering components.'
|
|
11
|
+
);
|
|
12
|
+
__publicField(this, "name", "ForgeCLIRequiredError");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (!process.env.FORGE_CLI) {
|
|
16
|
+
throw new ForgeCLIRequiredError();
|
|
17
|
+
}
|
|
7
18
|
class PlaywrightNotInstalledError extends Error {
|
|
8
19
|
constructor() {
|
|
9
20
|
super(
|
|
@@ -146,6 +157,9 @@ class ImageRenderer {
|
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
async function createImageRenderer(config) {
|
|
160
|
+
if (!process.env.FORGE_CLI) {
|
|
161
|
+
throw new ForgeCLIRequiredError();
|
|
162
|
+
}
|
|
149
163
|
const playwright = await loadPlaywright();
|
|
150
164
|
let browser;
|
|
151
165
|
try {
|
|
@@ -175,6 +189,7 @@ async function createImageRenderer(config) {
|
|
|
175
189
|
}
|
|
176
190
|
export {
|
|
177
191
|
BrowserLaunchError,
|
|
192
|
+
ForgeCLIRequiredError,
|
|
178
193
|
ImageRenderer,
|
|
179
194
|
PlaywrightNotInstalledError,
|
|
180
195
|
RenderError,
|
package/dist-ssr/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.js","sources":["../src/test/index.ts"],"sourcesContent":["import { createStaticRenderer, type StaticRenderer } from '../static/index';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n// ─────────────────────────────────────────────────────────────\n// Error Classes\n// ─────────────────────────────────────────────────────────────\n\nexport class PlaywrightNotInstalledError extends Error {\n name = 'PlaywrightNotInstalledError' as const;\n constructor() {\n super(\n 'ImageRenderer requires Playwright. Install it with:\\n' +\n ' npm install playwright\\n' +\n ' npx playwright install chromium'\n );\n }\n}\n\nexport class BrowserLaunchError extends Error {\n name = 'BrowserLaunchError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\nexport class RenderTimeoutError extends Error {\n name = 'RenderTimeoutError' as const;\n constructor(timeoutMs: number) {\n super(`Render timed out after ${timeoutMs}ms`);\n }\n}\n\nexport class RenderError extends Error {\n name = 'RenderError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface ImageRendererConfig {\n /** Output directory for screenshots */\n outputDir: string;\n /** Default width (default: 1920) */\n width?: number;\n /** Default height (default: 1080) */\n height?: number;\n /** Device scale factor for retina (default: 1) */\n deviceScaleFactor?: number;\n /** Image format (default: 'png') */\n format?: 'png' | 'jpeg';\n /** JPEG quality 0-100 (default: 80) */\n quality?: number;\n /** Wait condition (default: 'networkidle') */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Timeout in ms (default: 10000) */\n timeoutMs?: number;\n /** Custom HTML template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */\n template?: string;\n\n // Static renderer config (one of these)\n /** Pre-created StaticRenderer instance */\n staticRenderer?: StaticRenderer;\n /** OR: Path to stitch.yaml */\n stitchConfig?: string;\n /** Component directory (required if stitchConfig provided) */\n componentDir?: string;\n}\n\nexport interface CaptureOptions {\n /** View component data */\n view?: any;\n /** Layout data (one object per layout) */\n layout?: any[];\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\nexport interface CaptureHtmlOptions {\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\ninterface CaptureResult {\n /** Raw image buffer */\n buffer: Buffer;\n /** Path where image was saved */\n path: string;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Playwright Loader\n// ─────────────────────────────────────────────────────────────\n\ntype PlaywrightModule = typeof import('playwright');\n\nasync function loadPlaywright(): Promise<PlaywrightModule> {\n try {\n const playwright = await import('playwright');\n return playwright;\n } catch {\n throw new PlaywrightNotInstalledError();\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// ImageRenderer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class ImageRenderer {\n private browser: any;\n private staticRenderer: StaticRenderer;\n private config: Required<Omit<ImageRendererConfig, 'staticRenderer' | 'stitchConfig' | 'componentDir' | 'template'>> & {\n outputDir: string;\n template?: string;\n };\n\n constructor(\n browser: any,\n staticRenderer: StaticRenderer,\n config: ImageRendererConfig\n ) {\n this.browser = browser;\n this.staticRenderer = staticRenderer;\n this.config = {\n outputDir: config.outputDir,\n width: config.width ?? 1920,\n height: config.height ?? 1080,\n deviceScaleFactor: config.deviceScaleFactor ?? 1,\n format: config.format ?? 'png',\n quality: config.quality ?? 80,\n waitUntil: config.waitUntil ?? 'networkidle',\n timeoutMs: config.timeoutMs ?? 10000,\n template: config.template,\n };\n }\n\n /**\n * Capture a route and save to outputDir\n */\n async capture(\n name: string,\n route: string,\n options?: CaptureOptions\n ): Promise<CaptureResult> {\n // Render route to HTML using StaticRenderer\n const { view, layout } = options ?? {};\n const renderResult = await this.staticRenderer.renderToPage({\n route,\n data: { view, layout },\n template: this.config.template,\n });\n\n // Capture the HTML\n return this.captureHtmlInternal(name, renderResult, options);\n }\n\n /**\n * Capture raw HTML and save to outputDir\n */\n async captureHtml(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n return this.captureHtmlInternal(name, html, options);\n }\n\n /**\n * Internal method to capture HTML to image\n */\n private async captureHtmlInternal(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n const width = options?.width ?? this.config.width;\n const height = options?.height ?? this.config.height;\n\n let context: any;\n let page: any;\n\n try {\n // Create new context with viewport\n context = await this.browser.newContext({\n viewport: { width, height },\n deviceScaleFactor: this.config.deviceScaleFactor,\n });\n\n // Create page\n page = await context.newPage();\n\n // Set content with timeout\n const waitUntilMapping = {\n 'load': 'load',\n 'domcontentloaded': 'domcontentloaded',\n 'networkidle': 'networkidle',\n } as const;\n\n try {\n await page.setContent(html, {\n waitUntil: waitUntilMapping[this.config.waitUntil],\n timeout: this.config.timeoutMs,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('timeout')) {\n throw new RenderTimeoutError(this.config.timeoutMs);\n }\n throw new RenderError(\n 'Failed to render HTML content',\n error instanceof Error ? error : undefined\n );\n }\n\n // Take screenshot\n const screenshotOptions: {\n type: 'png' | 'jpeg';\n quality?: number;\n fullPage: boolean;\n } = {\n type: this.config.format,\n fullPage: false,\n };\n\n // Quality only applies to JPEG\n if (this.config.format === 'jpeg') {\n screenshotOptions.quality = this.config.quality;\n }\n\n const screenshot = await page.screenshot(screenshotOptions);\n const buffer = Buffer.from(screenshot);\n\n // Generate filename: {name}@{width}x{height}.{format}\n const filename = `${name}@${width}x${height}.${this.config.format}`;\n const outputPath = path.join(this.config.outputDir, filename);\n\n // Ensure output directory exists\n await fs.promises.mkdir(this.config.outputDir, { recursive: true });\n\n // Write file\n await fs.promises.writeFile(outputPath, buffer);\n\n return { buffer, path: outputPath };\n\n } finally {\n // Cleanup context and page (browser stays open)\n if (page) {\n await page.close().catch(() => {});\n }\n if (context) {\n await context.close().catch(() => {});\n }\n }\n }\n\n /**\n * Close browser and clean up resources\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Factory Function\n// ─────────────────────────────────────────────────────────────\n\nexport async function createImageRenderer(\n config: ImageRendererConfig\n): Promise<ImageRenderer> {\n // Load Playwright dynamically\n const playwright = await loadPlaywright();\n\n // Launch browser\n let browser: any;\n try {\n browser = await playwright.chromium.launch({\n headless: true,\n });\n } catch (error) {\n throw new BrowserLaunchError(\n 'Failed to launch Chromium. Ensure it is installed with: npx playwright install chromium',\n error instanceof Error ? error : undefined\n );\n }\n\n // Get or create StaticRenderer\n let staticRenderer: StaticRenderer;\n if (config.staticRenderer) {\n staticRenderer = config.staticRenderer;\n } else if (config.stitchConfig && config.componentDir) {\n staticRenderer = await createStaticRenderer({\n stitchConfig: config.stitchConfig,\n componentDir: config.componentDir,\n });\n } else {\n throw new Error(\n 'ImageRendererConfig requires either staticRenderer or both stitchConfig and componentDir'\n );\n }\n\n return new ImageRenderer(browser, staticRenderer, config);\n}\n"],"names":[],"mappings":";;;;;;AAQO,MAAM,oCAAoC,MAAM;AAAA,EAErD,cAAc;AACZ;AAAA,MACE;AAAA,IAGF;AANF,gCAAO;AAAA,EAML;AAEJ;AAEO,MAAM,2BAA2B,MAAM;AAAA,EAE5C,YAAY,SAAwB,OAAe;AACjD,UAAM,OAAO;AAFf,gCAAO;AAC6B,SAAA,QAAA;AAAA,EAAA;AAGtC;AAEO,MAAM,2BAA2B,MAAM;AAAA,EAE5C,YAAY,WAAmB;AACvB,UAAA,0BAA0B,SAAS,IAAI;AAF/C,gCAAO;AAAA,EAEwC;AAEjD;AAEO,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,SAAwB,OAAe;AACjD,UAAM,OAAO;AAFf,gCAAO;AAC6B,SAAA,QAAA;AAAA,EAAA;AAGtC;AAkEA,eAAe,iBAA4C;AACrD,MAAA;AACI,UAAA,aAAa,MAAM,OAAO,YAAY;AACrC,WAAA;AAAA,EAAA,QACD;AACN,UAAM,IAAI,4BAA4B;AAAA,EAAA;AAE1C;AAMO,MAAM,cAAc;AAAA,EAQzB,YACE,SACA,gBACA,QACA;AAXM;AACA;AACA;AAUN,SAAK,UAAU;AACf,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,MACZ,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO,SAAS;AAAA,MACvB,QAAQ,OAAO,UAAU;AAAA,MACzB,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,QAAQ,OAAO,UAAU;AAAA,MACzB,SAAS,OAAO,WAAW;AAAA,MAC3B,WAAW,OAAO,aAAa;AAAA,MAC/B,WAAW,OAAO,aAAa;AAAA,MAC/B,UAAU,OAAO;AAAA,IACnB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMF,MAAM,QACJ,MACA,OACA,SACwB;AAExB,UAAM,EAAE,MAAM,OAAO,IAAI,WAAW,CAAC;AACrC,UAAM,eAAe,MAAM,KAAK,eAAe,aAAa;AAAA,MAC1D;AAAA,MACA,MAAM,EAAE,MAAM,OAAO;AAAA,MACrB,UAAU,KAAK,OAAO;AAAA,IAAA,CACvB;AAGD,WAAO,KAAK,oBAAoB,MAAM,cAAc,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM7D,MAAM,YACJ,MACA,MACA,SACwB;AACxB,WAAO,KAAK,oBAAoB,MAAM,MAAM,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMrD,MAAc,oBACZ,MACA,MACA,SACwB;AACxB,UAAM,SAAQ,mCAAS,UAAS,KAAK,OAAO;AAC5C,UAAM,UAAS,mCAAS,WAAU,KAAK,OAAO;AAE1C,QAAA;AACA,QAAA;AAEA,QAAA;AAEQ,gBAAA,MAAM,KAAK,QAAQ,WAAW;AAAA,QACtC,UAAU,EAAE,OAAO,OAAO;AAAA,QAC1B,mBAAmB,KAAK,OAAO;AAAA,MAAA,CAChC;AAGM,aAAA,MAAM,QAAQ,QAAQ;AAG7B,YAAM,mBAAmB;AAAA,QACvB,QAAQ;AAAA,QACR,oBAAoB;AAAA,QACpB,eAAe;AAAA,MACjB;AAEI,UAAA;AACI,cAAA,KAAK,WAAW,MAAM;AAAA,UAC1B,WAAW,iBAAiB,KAAK,OAAO,SAAS;AAAA,UACjD,SAAS,KAAK,OAAO;AAAA,QAAA,CACtB;AAAA,eACM,OAAO;AACd,YAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,SAAS,GAAG;AAC/D,gBAAM,IAAI,mBAAmB,KAAK,OAAO,SAAS;AAAA,QAAA;AAEpD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iBAAiB,QAAQ,QAAQ;AAAA,QACnC;AAAA,MAAA;AAIF,YAAM,oBAIF;AAAA,QACF,MAAM,KAAK,OAAO;AAAA,QAClB,UAAU;AAAA,MACZ;AAGI,UAAA,KAAK,OAAO,WAAW,QAAQ;AACf,0BAAA,UAAU,KAAK,OAAO;AAAA,MAAA;AAG1C,YAAM,aAAa,MAAM,KAAK,WAAW,iBAAiB;AACpD,YAAA,SAAS,OAAO,KAAK,UAAU;AAG/B,YAAA,WAAW,GAAG,IAAI,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,OAAO,MAAM;AACjE,YAAM,aAAa,KAAK,KAAK,KAAK,OAAO,WAAW,QAAQ;AAGtD,YAAA,GAAG,SAAS,MAAM,KAAK,OAAO,WAAW,EAAE,WAAW,MAAM;AAGlE,YAAM,GAAG,SAAS,UAAU,YAAY,MAAM;AAEvC,aAAA,EAAE,QAAQ,MAAM,WAAW;AAAA,IAAA,UAElC;AAEA,UAAI,MAAM;AACR,cAAM,KAAK,QAAQ,MAAM,MAAM;AAAA,QAAA,CAAE;AAAA,MAAA;AAEnC,UAAI,SAAS;AACX,cAAM,QAAQ,QAAQ,MAAM,MAAM;AAAA,QAAA,CAAE;AAAA,MAAA;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMF,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAA,CAAE;AAAA,IAAA;AAAA,EAC3C;AAEJ;AAMA,eAAsB,oBACpB,QACwB;AAElB,QAAA,aAAa,MAAM,eAAe;AAGpC,MAAA;AACA,MAAA;AACQ,cAAA,MAAM,WAAW,SAAS,OAAO;AAAA,MACzC,UAAU;AAAA,IAAA,CACX;AAAA,WACM,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB,QAAQ,QAAQ;AAAA,IACnC;AAAA,EAAA;AAIE,MAAA;AACJ,MAAI,OAAO,gBAAgB;AACzB,qBAAiB,OAAO;AAAA,EACf,WAAA,OAAO,gBAAgB,OAAO,cAAc;AACrD,qBAAiB,MAAM,qBAAqB;AAAA,MAC1C,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,IAAA,CACtB;AAAA,EAAA,OACI;AACL,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAGF,SAAO,IAAI,cAAc,SAAS,gBAAgB,MAAM;AAC1D;"}
|
|
1
|
+
{"version":3,"file":"test.js","sources":["../src/test/index.ts"],"sourcesContent":["import { createStaticRenderer, type StaticRenderer } from '../static/index';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n\n\n// ─────────────────────────────────────────────────────────────\n// Error Classes\n// ─────────────────────────────────────────────────────────────\nexport class ForgeCLIRequiredError extends Error {\n name = 'ForgeCLIRequiredError' as const;\n constructor() {\n super(\n 'This file must be run with the forge CLI, not directly with tsx/node.\\n\\n' +\n 'Usage:\\n' +\n ' npx forge test/ui/your-test.ts\\n' +\n ' npx forge \"test/ui/**/*.test.ts\"\\n\\n' +\n 'The forge CLI provides the necessary Svelte loader for rendering components.'\n );\n }\n}\n\n//We need to throw on import so that this crashes right away\nif (!process.env.FORGE_CLI) {\n throw new ForgeCLIRequiredError();\n}\n\n\nexport class PlaywrightNotInstalledError extends Error {\n name = 'PlaywrightNotInstalledError' as const;\n constructor() {\n super(\n 'ImageRenderer requires Playwright. Install it with:\\n' +\n ' npm install playwright\\n' +\n ' npx playwright install chromium'\n );\n }\n}\n\nexport class BrowserLaunchError extends Error {\n name = 'BrowserLaunchError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\nexport class RenderTimeoutError extends Error {\n name = 'RenderTimeoutError' as const;\n constructor(timeoutMs: number) {\n super(`Render timed out after ${timeoutMs}ms`);\n }\n}\n\nexport class RenderError extends Error {\n name = 'RenderError' as const;\n constructor(message: string, public cause?: Error) {\n super(message);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface ImageRendererConfig {\n /** Output directory for screenshots */\n outputDir: string;\n /** Default width (default: 1920) */\n width?: number;\n /** Default height (default: 1080) */\n height?: number;\n /** Device scale factor for retina (default: 1) */\n deviceScaleFactor?: number;\n /** Image format (default: 'png') */\n format?: 'png' | 'jpeg';\n /** JPEG quality 0-100 (default: 80) */\n quality?: number;\n /** Wait condition (default: 'networkidle') */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Timeout in ms (default: 10000) */\n timeoutMs?: number;\n /** Custom HTML template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */\n template?: string;\n\n // Static renderer config (one of these)\n /** Pre-created StaticRenderer instance */\n staticRenderer?: StaticRenderer;\n /** OR: Path to stitch.yaml */\n stitchConfig?: string;\n /** Component directory (required if stitchConfig provided) */\n componentDir?: string;\n}\n\nexport interface CaptureOptions {\n /** View component data */\n view?: any;\n /** Layout data (one object per layout) */\n layout?: any[];\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\nexport interface CaptureHtmlOptions {\n /** Override width */\n width?: number;\n /** Override height */\n height?: number;\n}\n\ninterface CaptureResult {\n /** Raw image buffer */\n buffer: Buffer;\n /** Path where image was saved */\n path: string;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Playwright Loader\n// ─────────────────────────────────────────────────────────────\n\ntype PlaywrightModule = typeof import('playwright');\n\nasync function loadPlaywright(): Promise<PlaywrightModule> {\n try {\n const playwright = await import('playwright');\n return playwright;\n } catch {\n throw new PlaywrightNotInstalledError();\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// ImageRenderer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class ImageRenderer {\n private browser: any;\n private staticRenderer: StaticRenderer;\n private config: Required<Omit<ImageRendererConfig, 'staticRenderer' | 'stitchConfig' | 'componentDir' | 'template'>> & {\n outputDir: string;\n template?: string;\n };\n\n constructor(\n browser: any,\n staticRenderer: StaticRenderer,\n config: ImageRendererConfig\n ) {\n this.browser = browser;\n this.staticRenderer = staticRenderer;\n this.config = {\n outputDir: config.outputDir,\n width: config.width ?? 1920,\n height: config.height ?? 1080,\n deviceScaleFactor: config.deviceScaleFactor ?? 1,\n format: config.format ?? 'png',\n quality: config.quality ?? 80,\n waitUntil: config.waitUntil ?? 'networkidle',\n timeoutMs: config.timeoutMs ?? 10000,\n template: config.template,\n };\n }\n\n /**\n * Capture a route and save to outputDir\n */\n async capture(\n name: string,\n route: string,\n options?: CaptureOptions\n ): Promise<CaptureResult> {\n // Render route to HTML using StaticRenderer\n const { view, layout } = options ?? {};\n const renderResult = await this.staticRenderer.renderToPage({\n route,\n data: { view, layout },\n template: this.config.template,\n });\n\n // Capture the HTML\n return this.captureHtmlInternal(name, renderResult, options);\n }\n\n /**\n * Capture raw HTML and save to outputDir\n */\n async captureHtml(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n return this.captureHtmlInternal(name, html, options);\n }\n\n /**\n * Internal method to capture HTML to image\n */\n private async captureHtmlInternal(\n name: string,\n html: string,\n options?: CaptureHtmlOptions\n ): Promise<CaptureResult> {\n const width = options?.width ?? this.config.width;\n const height = options?.height ?? this.config.height;\n\n let context: any;\n let page: any;\n\n try {\n // Create new context with viewport\n context = await this.browser.newContext({\n viewport: { width, height },\n deviceScaleFactor: this.config.deviceScaleFactor,\n });\n\n // Create page\n page = await context.newPage();\n\n // Set content with timeout\n const waitUntilMapping = {\n 'load': 'load',\n 'domcontentloaded': 'domcontentloaded',\n 'networkidle': 'networkidle',\n } as const;\n\n try {\n await page.setContent(html, {\n waitUntil: waitUntilMapping[this.config.waitUntil],\n timeout: this.config.timeoutMs,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('timeout')) {\n throw new RenderTimeoutError(this.config.timeoutMs);\n }\n throw new RenderError(\n 'Failed to render HTML content',\n error instanceof Error ? error : undefined\n );\n }\n\n // Take screenshot\n const screenshotOptions: {\n type: 'png' | 'jpeg';\n quality?: number;\n fullPage: boolean;\n } = {\n type: this.config.format,\n fullPage: false,\n };\n\n // Quality only applies to JPEG\n if (this.config.format === 'jpeg') {\n screenshotOptions.quality = this.config.quality;\n }\n\n const screenshot = await page.screenshot(screenshotOptions);\n const buffer = Buffer.from(screenshot);\n\n // Generate filename: {name}@{width}x{height}.{format}\n const filename = `${name}@${width}x${height}.${this.config.format}`;\n const outputPath = path.join(this.config.outputDir, filename);\n\n // Ensure output directory exists\n await fs.promises.mkdir(this.config.outputDir, { recursive: true });\n\n // Write file\n await fs.promises.writeFile(outputPath, buffer);\n\n return { buffer, path: outputPath };\n\n } finally {\n // Cleanup context and page (browser stays open)\n if (page) {\n await page.close().catch(() => {});\n }\n if (context) {\n await context.close().catch(() => {});\n }\n }\n }\n\n /**\n * Close browser and clean up resources\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Factory Function\n// ─────────────────────────────────────────────────────────────\n\nexport async function createImageRenderer(\n config: ImageRendererConfig\n): Promise<ImageRenderer> {\n // Check if running via forge CLI\n if (!process.env.FORGE_CLI) {\n throw new ForgeCLIRequiredError();\n }\n\n // Load Playwright dynamically\n const playwright = await loadPlaywright();\n\n // Launch browser\n let browser: any;\n try {\n browser = await playwright.chromium.launch({\n headless: true,\n });\n } catch (error) {\n throw new BrowserLaunchError(\n 'Failed to launch Chromium. Ensure it is installed with: npx playwright install chromium',\n error instanceof Error ? error : undefined\n );\n }\n\n // Get or create StaticRenderer\n let staticRenderer: StaticRenderer;\n if (config.staticRenderer) {\n staticRenderer = config.staticRenderer;\n } else if (config.stitchConfig && config.componentDir) {\n staticRenderer = await createStaticRenderer({\n stitchConfig: config.stitchConfig,\n componentDir: config.componentDir,\n });\n } else {\n throw new Error(\n 'ImageRendererConfig requires either staticRenderer or both stitchConfig and componentDir'\n );\n }\n\n return new ImageRenderer(browser, staticRenderer, config);\n}\n"],"names":[],"mappings":"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"}
|