@noego/forge 0.0.27 → 0.1.1

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,151 @@
1
+ import { SvelteComponent } from 'svelte';
2
+ import { SvelteImport as SvelteImport_2 } from '../base';
3
+ import { SvelteModuleComponent as SvelteModuleComponent_2 } from '../base';
4
+
5
+ declare interface Body_2 {
6
+ description: string;
7
+ required?: boolean;
8
+ content: any;
9
+ }
10
+
11
+ declare class ComponentManager {
12
+ private componentLoader;
13
+ constructor(componentLoader: IComponentLoader);
14
+ getLayoutComponents(route: IRoute): Promise<(SvelteImport_2<SvelteComponent<Record<string, any>, any, any>> & SvelteModuleComponent_2)[]>;
15
+ getLayouts(route: IRoute): Promise<SvelteComponent<Record<string, any>, any, any>[]>;
16
+ getLayoutLoaders(route: IRoute): Promise<(((...args: any[]) => any) | null)[]>;
17
+ getViewComponent(route: IRoute): Promise<SvelteImport_2<SvelteComponent<Record<string, any>, any, any>> & SvelteModuleComponent_2>;
18
+ hasLoaders(route: IRoute): Promise<boolean>;
19
+ getLoaders(route: IRoute): Promise<{
20
+ layouts: (((...args: any[]) => any) | null)[];
21
+ view: ((...args: any[]) => any) | null;
22
+ }>;
23
+ private getLoaderFilePath;
24
+ private resolveLoader;
25
+ getView(route: IRoute): Promise<SvelteComponent<Record<string, any>, any, any>>;
26
+ }
27
+
28
+ export declare function createStaticRenderer(config: StaticRendererConfig): Promise<StaticRenderer>;
29
+
30
+ declare interface IComponentLoader {
31
+ load(component: string, options?: any): Promise<SvelteResource<SvelteComponent>>;
32
+ getComponentFullPath(component: string, options?: any): string;
33
+ }
34
+
35
+ declare interface IRoute {
36
+ path: string;
37
+ summary?: string;
38
+ description?: string;
39
+ method: string;
40
+ layout?: string[];
41
+ middleware?: string[];
42
+ view: string;
43
+ responses?: Response_2;
44
+ parameters?: Parameter[];
45
+ query?: Parameter[];
46
+ body?: Body_2;
47
+ }
48
+
49
+ declare type Middleware = (req: any, reply: any, next: any) => void;
50
+
51
+ declare type Parameter = any;
52
+
53
+ export declare interface RenderOptions {
54
+ /** The URL to render (e.g., '/admin/clients/42?tab=invoices') */
55
+ route: string;
56
+ /** HTTP method (default: 'get') */
57
+ method?: string;
58
+ /** Mock data for layouts and view */
59
+ data?: {
60
+ layout?: any[];
61
+ view?: any;
62
+ };
63
+ /** Override: manually specify view component */
64
+ view?: any;
65
+ /** Override: manually specify layout components */
66
+ layouts?: any[];
67
+ }
68
+
69
+ export declare interface RenderResult {
70
+ /** Rendered component HTML */
71
+ html: string;
72
+ /** Content from <svelte:head> tags */
73
+ head: string;
74
+ /** Component CSS */
75
+ css: string;
76
+ /** Parsed path parameters */
77
+ params: Record<string, string>;
78
+ /** Parsed query parameters */
79
+ query: Record<string, string | string[]>;
80
+ /** The matched route (if auto-resolved) */
81
+ route: IRoute | null;
82
+ }
83
+
84
+ export declare interface RenderToPageOptions extends RenderOptions {
85
+ /** Custom HTML template. Use {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */
86
+ template?: string;
87
+ }
88
+
89
+ declare type Response_2 = Record<number, ResponseData>;
90
+
91
+ declare type ResponseData = {
92
+ description: string;
93
+ };
94
+
95
+ export declare class StaticRenderer {
96
+ private routes;
97
+ private matchers;
98
+ private componentManager;
99
+ constructor(routes: IRoute[], componentManager: ComponentManager);
100
+ render(options: RenderOptions): Promise<RenderResult>;
101
+ /**
102
+ * Render to a full HTML document.
103
+ * Uses default template or custom template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders.
104
+ */
105
+ renderToPage(options: RenderToPageOptions): Promise<string>;
106
+ }
107
+
108
+ export declare interface StaticRendererConfig {
109
+ /** Path to stitch.yaml (e.g., './ui/stitch.yaml') */
110
+ stitchConfig: string;
111
+ /** Base directory for components (e.g., './ui') */
112
+ componentDir: string;
113
+ /** Use production loader with pre-compiled components (default: false) */
114
+ production?: boolean;
115
+ }
116
+
117
+ declare type SvelteImport<K> = {
118
+ default: K;
119
+ };
120
+
121
+ declare type SvelteModuleComponent = {
122
+ load?: (data: any) => any;
123
+ middleware?: Middleware[];
124
+ middlewares?: Middleware[];
125
+ };
126
+
127
+ declare type SvelteResource<K> = SvelteImport<K> & SvelteModuleComponent;
128
+
129
+ export { }
130
+
131
+ /// <reference types="vite/client" />
132
+
133
+ declare module '@noego/stitch/browser' {
134
+ export function stitchFromViteImports(modules: Record<string, string>): Promise<{
135
+ success: boolean;
136
+ data?: any;
137
+ error?: string;
138
+ }>;
139
+ }
140
+
141
+
142
+
143
+ declare module '@noego/stitch' {
144
+ export class StitchEngine {
145
+ buildSync(filePath: string, options?: { format?: string }): {
146
+ success: boolean;
147
+ data?: any;
148
+ error?: string;
149
+ };
150
+ }
151
+ }
@@ -0,0 +1,107 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { render } from "svelte/server";
5
+ import { i as initialize_route_matchers, f as findRoute, p as parse_openapi_config, P as ProdComponentLoader, B as BasicComponentLoader, C as ComponentManager } from "./url_parser-DRWHePkU.js";
6
+ import RecursiveRender from "../src/components/RecursiveRender.svelte";
7
+ class StaticRenderer {
8
+ constructor(routes, componentManager) {
9
+ __publicField(this, "routes");
10
+ __publicField(this, "matchers");
11
+ __publicField(this, "componentManager");
12
+ this.routes = routes;
13
+ this.matchers = initialize_route_matchers(routes);
14
+ this.componentManager = componentManager;
15
+ }
16
+ async render(options) {
17
+ var _a;
18
+ const { route: url, method = "get", data = {} } = options;
19
+ const urlObj = new URL(url, "http://localhost");
20
+ const pathname = urlObj.pathname;
21
+ const query = {};
22
+ urlObj.searchParams.forEach((value, key) => {
23
+ const existing = query[key];
24
+ if (existing) {
25
+ query[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
26
+ } else {
27
+ query[key] = value;
28
+ }
29
+ });
30
+ let params = {};
31
+ let matchedRoute = null;
32
+ let view;
33
+ let layouts = [];
34
+ if (options.view) {
35
+ view = options.view;
36
+ layouts = options.layouts || [];
37
+ } else {
38
+ const match = findRoute(pathname, this.matchers);
39
+ if (!match) {
40
+ throw new Error(`No route found for: ${pathname}`);
41
+ }
42
+ matchedRoute = this.routes.find(
43
+ (r) => r.path === match.pattern && r.method.toLowerCase() === method.toLowerCase()
44
+ ) || null;
45
+ if (!matchedRoute) {
46
+ throw new Error(`No ${method.toUpperCase()} route for: ${pathname}`);
47
+ }
48
+ params = match.params;
49
+ view = await this.componentManager.getView(matchedRoute);
50
+ layouts = await this.componentManager.getLayouts(matchedRoute);
51
+ }
52
+ const page = { url, pathname, params, query };
53
+ const serverData = {
54
+ layout: data.layout || [],
55
+ view: data.view || {}
56
+ };
57
+ const result = render(RecursiveRender, {
58
+ props: {
59
+ layouts,
60
+ view,
61
+ data: serverData,
62
+ params,
63
+ urlParams: params,
64
+ query,
65
+ page
66
+ }
67
+ });
68
+ return {
69
+ html: result.html,
70
+ head: result.head,
71
+ css: ((_a = result.css) == null ? void 0 : _a.code) || "",
72
+ params,
73
+ query,
74
+ route: matchedRoute
75
+ };
76
+ }
77
+ /**
78
+ * Render to a full HTML document.
79
+ * Uses default template or custom template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders.
80
+ */
81
+ async renderToPage(options) {
82
+ const { html, head, css } = await this.render(options);
83
+ const defaultTemplate = `<!DOCTYPE html>
84
+ <html>
85
+ <head>
86
+ {{{HEAD}}}
87
+ <style>{{{CSS}}}</style>
88
+ </head>
89
+ <body>
90
+ <div id="app">{{{APP}}}</div>
91
+ </body>
92
+ </html>`;
93
+ const template = options.template || defaultTemplate;
94
+ return template.replace("{{{HEAD}}}", head).replace("{{{CSS}}}", css).replace("{{{APP}}}", html);
95
+ }
96
+ }
97
+ async function createStaticRenderer(config) {
98
+ const serverConfig = await parse_openapi_config(config.stitchConfig);
99
+ const loader = config.production ? new ProdComponentLoader(config.componentDir) : new BasicComponentLoader(config.componentDir);
100
+ const componentManager = new ComponentManager(loader);
101
+ return new StaticRenderer(serverConfig.routes, componentManager);
102
+ }
103
+ export {
104
+ StaticRenderer,
105
+ createStaticRenderer
106
+ };
107
+ //# sourceMappingURL=static.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.js","sources":["../src/static/index.ts"],"sourcesContent":["import { render } from 'svelte/server';\nimport { parse_openapi_config } from '../parser/openapi';\nimport { initialize_route_matchers, findRoute } from '../routing/url_parser';\nimport { ComponentManager } from '../routing/component_loader/component_manager';\nimport { BasicComponentLoader, ProdComponentLoader } from '../routing/component_loader/component_loader';\nimport RecursiveRender from '../components/RecursiveRender.svelte';\nimport type { IRoute } from '../parser/IRoute';\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface StaticRendererConfig {\n /** Path to stitch.yaml (e.g., './ui/stitch.yaml') */\n stitchConfig: string;\n /** Base directory for components (e.g., './ui') */\n componentDir: string;\n /** Use production loader with pre-compiled components (default: false) */\n production?: boolean;\n}\n\nexport interface RenderOptions {\n /** The URL to render (e.g., '/admin/clients/42?tab=invoices') */\n route: string;\n /** HTTP method (default: 'get') */\n method?: string;\n /** Mock data for layouts and view */\n data?: {\n layout?: any[]; // One object per layout\n view?: any; // View component data\n };\n /** Override: manually specify view component */\n view?: any;\n /** Override: manually specify layout components */\n layouts?: any[];\n}\n\nexport interface RenderToPageOptions extends RenderOptions {\n /** Custom HTML template. Use {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders */\n template?: string;\n}\n\nexport interface RenderResult {\n /** Rendered component HTML */\n html: string;\n /** Content from <svelte:head> tags */\n head: string;\n /** Component CSS */\n css: string;\n /** Parsed path parameters */\n params: Record<string, string>;\n /** Parsed query parameters */\n query: Record<string, string | string[]>;\n /** The matched route (if auto-resolved) */\n route: IRoute | null;\n}\n\n// ─────────────────────────────────────────────────────────────\n// StaticRenderer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class StaticRenderer {\n private routes: IRoute[];\n private matchers: any[];\n private componentManager: ComponentManager;\n\n constructor(routes: IRoute[], componentManager: ComponentManager) {\n this.routes = routes;\n this.matchers = initialize_route_matchers(routes);\n this.componentManager = componentManager;\n }\n\n async render(options: RenderOptions): Promise<RenderResult> {\n const { route: url, method = 'get', data = {} } = options;\n\n // Parse URL\n const urlObj = new URL(url, 'http://localhost');\n const pathname = urlObj.pathname;\n\n // Parse query params\n const query: Record<string, string | string[]> = {};\n urlObj.searchParams.forEach((value, key) => {\n const existing = query[key];\n if (existing) {\n query[key] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n } else {\n query[key] = value;\n }\n });\n\n // Find matching route and extract params\n let params: Record<string, string> = {};\n let matchedRoute: IRoute | null = null;\n let view: any;\n let layouts: any[] = [];\n\n // If view/layouts provided, use those (manual override)\n if (options.view) {\n view = options.view;\n layouts = options.layouts || [];\n } else {\n // Auto-resolve from stitch config\n const match = findRoute(pathname, this.matchers);\n if (!match) {\n throw new Error(`No route found for: ${pathname}`);\n }\n\n matchedRoute = this.routes.find(r =>\n r.path === match.pattern &&\n r.method.toLowerCase() === method.toLowerCase()\n ) || null;\n\n if (!matchedRoute) {\n throw new Error(`No ${method.toUpperCase()} route for: ${pathname}`);\n }\n\n params = match.params as Record<string, string>;\n\n // Load components\n view = await this.componentManager.getView(matchedRoute);\n layouts = await this.componentManager.getLayouts(matchedRoute);\n }\n\n // Build page object\n const page = { url, pathname, params, query };\n\n // Build server data\n const serverData = {\n layout: data.layout || [],\n view: data.view || {}\n };\n\n // Render using RecursiveRender (same as production)\n const result = render(RecursiveRender, {\n props: {\n layouts,\n view,\n data: serverData,\n params,\n urlParams: params,\n query,\n page\n }\n });\n\n return {\n html: result.html,\n head: result.head,\n css: (result as any).css?.code || '',\n params,\n query,\n route: matchedRoute\n };\n }\n\n /**\n * Render to a full HTML document.\n * Uses default template or custom template with {{{HEAD}}}, {{{CSS}}}, {{{APP}}} placeholders.\n */\n async renderToPage(options: RenderToPageOptions): Promise<string> {\n const { html, head, css } = await this.render(options);\n\n const defaultTemplate = `<!DOCTYPE html>\n<html>\n<head>\n {{{HEAD}}}\n <style>{{{CSS}}}</style>\n</head>\n<body>\n <div id=\"app\">{{{APP}}}</div>\n</body>\n</html>`;\n\n const template = options.template || defaultTemplate;\n\n return template\n .replace('{{{HEAD}}}', head)\n .replace('{{{CSS}}}', css)\n .replace('{{{APP}}}', html);\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Factory Function\n// ─────────────────────────────────────────────────────────────\n\nexport async function createStaticRenderer(\n config: StaticRendererConfig\n): Promise<StaticRenderer> {\n // Parse stitch config to get routes\n const serverConfig = await parse_openapi_config(config.stitchConfig);\n\n // Create component loader based on mode\n const loader = config.production\n ? new ProdComponentLoader(config.componentDir) // Pre-compiled, production perf\n : new BasicComponentLoader(config.componentDir); // On-demand compilation\n\n const componentManager = new ComponentManager(loader);\n\n return new StaticRenderer(serverConfig.routes, componentManager);\n}\n"],"names":[],"mappings":";;;;;;AA6DO,MAAM,eAAe;AAAA,EAKxB,YAAY,QAAkB,kBAAoC;AAJ1D;AACA;AACA;AAGJ,SAAK,SAAS;AACT,SAAA,WAAW,0BAA0B,MAAM;AAChD,SAAK,mBAAmB;AAAA,EAAA;AAAA,EAG5B,MAAM,OAAO,SAA+C;;AAClD,UAAA,EAAE,OAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAGlD,UAAM,SAAS,IAAI,IAAI,KAAK,kBAAkB;AAC9C,UAAM,WAAW,OAAO;AAGxB,UAAM,QAA2C,CAAC;AAClD,WAAO,aAAa,QAAQ,CAAC,OAAO,QAAQ;AAClC,YAAA,WAAW,MAAM,GAAG;AAC1B,UAAI,UAAU;AACV,cAAM,GAAG,IAAI,MAAM,QAAQ,QAAQ,IAC7B,CAAC,GAAG,UAAU,KAAK,IACnB,CAAC,UAAU,KAAK;AAAA,MAAA,OACnB;AACH,cAAM,GAAG,IAAI;AAAA,MAAA;AAAA,IACjB,CACH;AAGD,QAAI,SAAiC,CAAC;AACtC,QAAI,eAA8B;AAC9B,QAAA;AACJ,QAAI,UAAiB,CAAC;AAGtB,QAAI,QAAQ,MAAM;AACd,aAAO,QAAQ;AACL,gBAAA,QAAQ,WAAW,CAAC;AAAA,IAAA,OAC3B;AAEH,YAAM,QAAQ,UAAU,UAAU,KAAK,QAAQ;AAC/C,UAAI,CAAC,OAAO;AACR,cAAM,IAAI,MAAM,uBAAuB,QAAQ,EAAE;AAAA,MAAA;AAGrD,qBAAe,KAAK,OAAO;AAAA,QAAK,CAAA,MAC5B,EAAE,SAAS,MAAM,WACjB,EAAE,OAAO,kBAAkB,OAAO,YAAY;AAAA,MAAA,KAC7C;AAEL,UAAI,CAAC,cAAc;AACT,cAAA,IAAI,MAAM,MAAM,OAAO,aAAa,eAAe,QAAQ,EAAE;AAAA,MAAA;AAGvE,eAAS,MAAM;AAGf,aAAO,MAAM,KAAK,iBAAiB,QAAQ,YAAY;AACvD,gBAAU,MAAM,KAAK,iBAAiB,WAAW,YAAY;AAAA,IAAA;AAIjE,UAAM,OAAO,EAAE,KAAK,UAAU,QAAQ,MAAM;AAG5C,UAAM,aAAa;AAAA,MACf,QAAQ,KAAK,UAAU,CAAC;AAAA,MACxB,MAAM,KAAK,QAAQ,CAAA;AAAA,IACvB;AAGM,UAAA,SAAS,OAAO,iBAAiB;AAAA,MACnC,OAAO;AAAA,QACH;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA;AAAA,MAAA;AAAA,IACJ,CACH;AAEM,WAAA;AAAA,MACH,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,OAAM,YAAe,QAAf,mBAAoB,SAAQ;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACX;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOJ,MAAM,aAAa,SAA+C;AACxD,UAAA,EAAE,MAAM,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO;AAErD,UAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlB,UAAA,WAAW,QAAQ,YAAY;AAE9B,WAAA,SACF,QAAQ,cAAc,IAAI,EAC1B,QAAQ,aAAa,GAAG,EACxB,QAAQ,aAAa,IAAI;AAAA,EAAA;AAEtC;AAMA,eAAsB,qBAClB,QACuB;AAEvB,QAAM,eAAe,MAAM,qBAAqB,OAAO,YAAY;AAG7D,QAAA,SAAS,OAAO,aAChB,IAAI,oBAAoB,OAAO,YAAY,IAC3C,IAAI,qBAAqB,OAAO,YAAY;AAE5C,QAAA,mBAAmB,IAAI,iBAAiB,MAAM;AAEpD,SAAO,IAAI,eAAe,aAAa,QAAQ,gBAAgB;AACnE;"}
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
26
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
27
+ const _static = require("./static.cjs");
28
+ const fs = require("fs");
29
+ const path = require("path");
30
+ function _interopNamespaceDefault(e) {
31
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
32
+ if (e) {
33
+ for (const k in e) {
34
+ if (k !== "default") {
35
+ const d = Object.getOwnPropertyDescriptor(e, k);
36
+ Object.defineProperty(n, k, d.get ? d : {
37
+ enumerable: true,
38
+ get: () => e[k]
39
+ });
40
+ }
41
+ }
42
+ }
43
+ n.default = e;
44
+ return Object.freeze(n);
45
+ }
46
+ const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
47
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
48
+ class PlaywrightNotInstalledError extends Error {
49
+ constructor() {
50
+ super(
51
+ "ImageRenderer requires Playwright. Install it with:\n npm install playwright\n npx playwright install chromium"
52
+ );
53
+ __publicField(this, "name", "PlaywrightNotInstalledError");
54
+ }
55
+ }
56
+ class BrowserLaunchError extends Error {
57
+ constructor(message, cause) {
58
+ super(message);
59
+ __publicField(this, "name", "BrowserLaunchError");
60
+ this.cause = cause;
61
+ }
62
+ }
63
+ class RenderTimeoutError extends Error {
64
+ constructor(timeoutMs) {
65
+ super(`Render timed out after ${timeoutMs}ms`);
66
+ __publicField(this, "name", "RenderTimeoutError");
67
+ }
68
+ }
69
+ class RenderError extends Error {
70
+ constructor(message, cause) {
71
+ super(message);
72
+ __publicField(this, "name", "RenderError");
73
+ this.cause = cause;
74
+ }
75
+ }
76
+ class ForgeCLIRequiredError extends Error {
77
+ constructor() {
78
+ super(
79
+ '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.'
80
+ );
81
+ __publicField(this, "name", "ForgeCLIRequiredError");
82
+ }
83
+ }
84
+ async function loadPlaywright() {
85
+ try {
86
+ const playwright = await import("playwright");
87
+ return playwright;
88
+ } catch {
89
+ throw new PlaywrightNotInstalledError();
90
+ }
91
+ }
92
+ class ImageRenderer {
93
+ constructor(browser, staticRenderer, config) {
94
+ __publicField(this, "browser");
95
+ __publicField(this, "staticRenderer");
96
+ __publicField(this, "config");
97
+ this.browser = browser;
98
+ this.staticRenderer = staticRenderer;
99
+ this.config = {
100
+ outputDir: config.outputDir,
101
+ width: config.width ?? 1920,
102
+ height: config.height ?? 1080,
103
+ deviceScaleFactor: config.deviceScaleFactor ?? 1,
104
+ format: config.format ?? "png",
105
+ quality: config.quality ?? 80,
106
+ waitUntil: config.waitUntil ?? "networkidle",
107
+ timeoutMs: config.timeoutMs ?? 1e4,
108
+ template: config.template
109
+ };
110
+ }
111
+ /**
112
+ * Capture a route and save to outputDir
113
+ */
114
+ async capture(name, route, options) {
115
+ const { view, layout } = options ?? {};
116
+ const renderResult = await this.staticRenderer.renderToPage({
117
+ route,
118
+ data: { view, layout },
119
+ template: this.config.template
120
+ });
121
+ return this.captureHtmlInternal(name, renderResult, options);
122
+ }
123
+ /**
124
+ * Capture raw HTML and save to outputDir
125
+ */
126
+ async captureHtml(name, html, options) {
127
+ return this.captureHtmlInternal(name, html, options);
128
+ }
129
+ /**
130
+ * Internal method to capture HTML to image
131
+ */
132
+ async captureHtmlInternal(name, html, options) {
133
+ const width = (options == null ? void 0 : options.width) ?? this.config.width;
134
+ const height = (options == null ? void 0 : options.height) ?? this.config.height;
135
+ let context;
136
+ let page;
137
+ try {
138
+ context = await this.browser.newContext({
139
+ viewport: { width, height },
140
+ deviceScaleFactor: this.config.deviceScaleFactor
141
+ });
142
+ page = await context.newPage();
143
+ const waitUntilMapping = {
144
+ "load": "load",
145
+ "domcontentloaded": "domcontentloaded",
146
+ "networkidle": "networkidle"
147
+ };
148
+ try {
149
+ await page.setContent(html, {
150
+ waitUntil: waitUntilMapping[this.config.waitUntil],
151
+ timeout: this.config.timeoutMs
152
+ });
153
+ } catch (error) {
154
+ if (error instanceof Error && error.message.includes("timeout")) {
155
+ throw new RenderTimeoutError(this.config.timeoutMs);
156
+ }
157
+ throw new RenderError(
158
+ "Failed to render HTML content",
159
+ error instanceof Error ? error : void 0
160
+ );
161
+ }
162
+ const screenshotOptions = {
163
+ type: this.config.format,
164
+ fullPage: false
165
+ };
166
+ if (this.config.format === "jpeg") {
167
+ screenshotOptions.quality = this.config.quality;
168
+ }
169
+ const screenshot = await page.screenshot(screenshotOptions);
170
+ const buffer = Buffer.from(screenshot);
171
+ const filename = `${name}@${width}x${height}.${this.config.format}`;
172
+ const outputPath = path__namespace.join(this.config.outputDir, filename);
173
+ await fs__namespace.promises.mkdir(this.config.outputDir, { recursive: true });
174
+ await fs__namespace.promises.writeFile(outputPath, buffer);
175
+ return { buffer, path: outputPath };
176
+ } finally {
177
+ if (page) {
178
+ await page.close().catch(() => {
179
+ });
180
+ }
181
+ if (context) {
182
+ await context.close().catch(() => {
183
+ });
184
+ }
185
+ }
186
+ }
187
+ /**
188
+ * Close browser and clean up resources
189
+ */
190
+ async close() {
191
+ if (this.browser) {
192
+ await this.browser.close().catch(() => {
193
+ });
194
+ }
195
+ }
196
+ }
197
+ async function createImageRenderer(config) {
198
+ if (!process.env.FORGE_CLI) {
199
+ throw new ForgeCLIRequiredError();
200
+ }
201
+ const playwright = await loadPlaywright();
202
+ let browser;
203
+ try {
204
+ browser = await playwright.chromium.launch({
205
+ headless: true
206
+ });
207
+ } catch (error) {
208
+ throw new BrowserLaunchError(
209
+ "Failed to launch Chromium. Ensure it is installed with: npx playwright install chromium",
210
+ error instanceof Error ? error : void 0
211
+ );
212
+ }
213
+ let staticRenderer;
214
+ if (config.staticRenderer) {
215
+ staticRenderer = config.staticRenderer;
216
+ } else if (config.stitchConfig && config.componentDir) {
217
+ staticRenderer = await _static.createStaticRenderer({
218
+ stitchConfig: config.stitchConfig,
219
+ componentDir: config.componentDir
220
+ });
221
+ } else {
222
+ throw new Error(
223
+ "ImageRendererConfig requires either staticRenderer or both stitchConfig and componentDir"
224
+ );
225
+ }
226
+ return new ImageRenderer(browser, staticRenderer, config);
227
+ }
228
+ exports.BrowserLaunchError = BrowserLaunchError;
229
+ exports.ForgeCLIRequiredError = ForgeCLIRequiredError;
230
+ exports.ImageRenderer = ImageRenderer;
231
+ exports.PlaywrightNotInstalledError = PlaywrightNotInstalledError;
232
+ exports.RenderError = RenderError;
233
+ exports.RenderTimeoutError = RenderTimeoutError;
234
+ exports.createImageRenderer = createImageRenderer;
235
+ //# sourceMappingURL=test.cjs.map
@@ -0,0 +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\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// ─────────────────────────────────────────────────────────────\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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"}