@lioneltay/component-shot 0.1.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/dist/mcp.js ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import { captureComponentShot, captureComponentSource, } from './index.js';
8
+ const projectRootEnv = 'COMPONENT_SHOT_PROJECT_ROOT';
9
+ const scenarioDirEnv = 'COMPONENT_SHOT_SCENARIO_DIR';
10
+ const isObject = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
11
+ const getString = (args, name) => {
12
+ const value = args[name];
13
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
14
+ };
15
+ const getBoolean = (args, name) => {
16
+ const value = args[name];
17
+ return typeof value === 'boolean' ? value : undefined;
18
+ };
19
+ const getNumber = (args, name) => {
20
+ const value = args[name];
21
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
22
+ };
23
+ const getViewport = (args) => {
24
+ const value = args.viewport;
25
+ if (!isObject(value)) {
26
+ return undefined;
27
+ }
28
+ const width = getNumber(value, 'width');
29
+ const height = getNumber(value, 'height');
30
+ if (!width || !height || width <= 0 || height <= 0) {
31
+ throw new Error('viewport must include positive width and height numbers');
32
+ }
33
+ return { height, width };
34
+ };
35
+ const requireString = (args, name) => {
36
+ const value = getString(args, name);
37
+ if (!value) {
38
+ throw new Error(`${name} is required`);
39
+ }
40
+ return value;
41
+ };
42
+ const resolveCwd = (args) => path.resolve(getString(args, 'cwd') ?? process.env[projectRootEnv] ?? process.cwd());
43
+ const toCaptureOptions = ({ args, cwd, scenario, }) => ({
44
+ browserChannel: getString(args, 'browserChannel'),
45
+ cwd,
46
+ debug: getBoolean(args, 'debug'),
47
+ fullPage: getBoolean(args, 'fullPage'),
48
+ output: getString(args, 'output'),
49
+ save: getBoolean(args, 'save') ?? true,
50
+ saveName: getString(args, 'saveName'),
51
+ scenario,
52
+ screenshotsDir: getString(args, 'screenshotsDir'),
53
+ selector: getString(args, 'selector'),
54
+ setup: getString(args, 'setup'),
55
+ timeoutMs: getNumber(args, 'timeoutMs'),
56
+ viewport: getViewport(args),
57
+ waitFor: getString(args, 'waitFor'),
58
+ });
59
+ const readPngContent = async (outputPath) => ({
60
+ data: await fs.readFile(outputPath, 'base64'),
61
+ mimeType: 'image/png',
62
+ type: 'image',
63
+ });
64
+ const createResult = async ({ result, scenarioPath, }) => ({
65
+ content: [
66
+ {
67
+ text: JSON.stringify({
68
+ historyPath: result.historyPath,
69
+ latestPath: result.latestPath,
70
+ outputPath: result.outputPath,
71
+ scenarioPath,
72
+ url: result.url,
73
+ }, null, 2),
74
+ type: 'text',
75
+ },
76
+ await readPngContent(result.outputPath),
77
+ ],
78
+ });
79
+ const toErrorResult = (error) => ({
80
+ content: [
81
+ {
82
+ text: error instanceof Error ? error.stack ?? error.message : String(error),
83
+ type: 'text',
84
+ },
85
+ ],
86
+ isError: true,
87
+ });
88
+ const captureScenario = async (args) => {
89
+ const cwd = resolveCwd(args);
90
+ const scenario = requireString(args, 'scenario');
91
+ const scenarioPath = path.resolve(cwd, scenario);
92
+ const result = await captureComponentShot(toCaptureOptions({ args, cwd, scenario }));
93
+ return createResult({ result, scenarioPath });
94
+ };
95
+ const captureSource = async (args) => {
96
+ const cwd = resolveCwd(args);
97
+ const source = requireString(args, 'source');
98
+ const result = await captureComponentSource({
99
+ ...toCaptureOptions({
100
+ args,
101
+ cwd,
102
+ scenario: getString(args, 'scenario') ?? '',
103
+ }),
104
+ name: getString(args, 'name'),
105
+ overwrite: getBoolean(args, 'overwrite'),
106
+ scenario: getString(args, 'scenario'),
107
+ scenarioDir: getString(args, 'scenarioDir') ?? process.env[scenarioDirEnv],
108
+ source,
109
+ });
110
+ return createResult({ result, scenarioPath: result.scenarioPath });
111
+ };
112
+ const commonCaptureProperties = {
113
+ browserChannel: {
114
+ description: 'Optional Playwright browser channel, for example chrome.',
115
+ type: 'string',
116
+ },
117
+ cwd: {
118
+ description: `Project root. Defaults to ${projectRootEnv} or the MCP process cwd.`,
119
+ type: 'string',
120
+ },
121
+ debug: {
122
+ description: 'Print bundler and browser diagnostics.',
123
+ type: 'boolean',
124
+ },
125
+ fullPage: {
126
+ description: 'Capture the full page instead of the component root selector.',
127
+ type: 'boolean',
128
+ },
129
+ output: {
130
+ description: 'Optional PNG output path.',
131
+ type: 'string',
132
+ },
133
+ save: {
134
+ description: 'Save latest.png and a timestamped history PNG. Defaults to true for MCP.',
135
+ type: 'boolean',
136
+ },
137
+ saveName: {
138
+ description: 'Name to use under the screenshots directory.',
139
+ type: 'string',
140
+ },
141
+ screenshotsDir: {
142
+ description: 'Screenshot audit directory. Defaults to the nearest component-shot/screenshots.',
143
+ type: 'string',
144
+ },
145
+ selector: {
146
+ description: 'CSS selector to capture.',
147
+ type: 'string',
148
+ },
149
+ setup: {
150
+ description: 'Optional setup module. Defaults to setup.* beside the scenario component-shot folder.',
151
+ type: 'string',
152
+ },
153
+ timeoutMs: {
154
+ description: 'Capture timeout in milliseconds.',
155
+ type: 'number',
156
+ },
157
+ viewport: {
158
+ additionalProperties: false,
159
+ description: 'Browser viewport.',
160
+ properties: {
161
+ height: { type: 'number' },
162
+ width: { type: 'number' },
163
+ },
164
+ required: ['width', 'height'],
165
+ type: 'object',
166
+ },
167
+ waitFor: {
168
+ description: 'Optional selector to wait for before capture.',
169
+ type: 'string',
170
+ },
171
+ };
172
+ const tools = [
173
+ {
174
+ description: 'Capture a PNG screenshot for an existing component-shot scenario file.',
175
+ inputSchema: {
176
+ additionalProperties: false,
177
+ properties: {
178
+ scenario: {
179
+ description: 'Scenario path, usually packages/client/component-shot/scenarios/name.tsx.',
180
+ type: 'string',
181
+ },
182
+ ...commonCaptureProperties,
183
+ },
184
+ required: ['scenario'],
185
+ type: 'object',
186
+ },
187
+ name: 'capture_component_shot',
188
+ },
189
+ {
190
+ description: 'Write scenario source to the repo, capture it, and return the PNG image.',
191
+ inputSchema: {
192
+ additionalProperties: false,
193
+ properties: {
194
+ name: {
195
+ description: 'Scenario filename stem when scenario is omitted.',
196
+ type: 'string',
197
+ },
198
+ overwrite: {
199
+ description: 'Allow replacing an existing scenario file.',
200
+ type: 'boolean',
201
+ },
202
+ scenario: {
203
+ description: 'Optional scenario path to write.',
204
+ type: 'string',
205
+ },
206
+ scenarioDir: {
207
+ description: `Directory for generated scenario files. Defaults to ${scenarioDirEnv} or component-shot/scenarios.`,
208
+ type: 'string',
209
+ },
210
+ source: {
211
+ description: 'Complete TSX scenario module source.',
212
+ type: 'string',
213
+ },
214
+ ...commonCaptureProperties,
215
+ },
216
+ required: ['source'],
217
+ type: 'object',
218
+ },
219
+ name: 'capture_component_source',
220
+ },
221
+ ];
222
+ const server = new Server({
223
+ name: 'component-shot',
224
+ version: '0.1.0',
225
+ }, {
226
+ capabilities: {
227
+ tools: {},
228
+ },
229
+ });
230
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
231
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
232
+ try {
233
+ const args = isObject(request.params.arguments) ? request.params.arguments : {};
234
+ switch (request.params.name) {
235
+ case 'capture_component_shot':
236
+ return await captureScenario(args);
237
+ case 'capture_component_source':
238
+ return await captureSource(args);
239
+ default:
240
+ throw new Error(`Unknown tool "${request.params.name}"`);
241
+ }
242
+ }
243
+ catch (error) {
244
+ return toErrorResult(error);
245
+ }
246
+ });
247
+ await server.connect(new StdioServerTransport());
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rspack-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack-runner.d.ts","sourceRoot":"","sources":["../src/rspack-runner.ts"],"names":[],"mappings":""}
@@ -0,0 +1,295 @@
1
+ import { rspack } from '@rspack/core';
2
+ import fs from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const require = createRequire(import.meta.url);
8
+ const htmlTemplate = () => `<!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+ <title>Component Shot</title>
14
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16
+ <link
17
+ href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
18
+ rel="stylesheet"
19
+ />
20
+ <style>
21
+ html,
22
+ body {
23
+ margin: 0;
24
+ min-height: 100%;
25
+ background: #fff;
26
+ font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
27
+ }
28
+
29
+ body {
30
+ box-sizing: border-box;
31
+ padding: 24px;
32
+ }
33
+
34
+ * {
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ pre[data-component-shot-error='true'] {
39
+ margin: 0;
40
+ max-width: 960px;
41
+ overflow: auto;
42
+ padding: 16px;
43
+ white-space: pre-wrap;
44
+ }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div id="root"></div>
49
+ </body>
50
+ </html>`;
51
+ const readJson = async (filePath) => {
52
+ try {
53
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
54
+ }
55
+ catch (error) {
56
+ const code = typeof error === 'object' && error && 'code' in error ? error.code : undefined;
57
+ if (code === 'ENOENT') {
58
+ return undefined;
59
+ }
60
+ throw error;
61
+ }
62
+ };
63
+ const fileExists = async (filePath) => {
64
+ try {
65
+ await fs.access(filePath);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ };
72
+ const findWorkspaceRoot = async (cwd) => {
73
+ let current = path.resolve(cwd);
74
+ while (true) {
75
+ if (await fileExists(path.join(current, 'pnpm-workspace.yaml'))) {
76
+ return current;
77
+ }
78
+ const parent = path.dirname(current);
79
+ if (parent === current) {
80
+ return cwd;
81
+ }
82
+ current = parent;
83
+ }
84
+ };
85
+ const splitPathList = (value) => (value ?? '')
86
+ .split(/[,:]/)
87
+ .map((entry) => entry.trim())
88
+ .filter(Boolean);
89
+ const findNodeModulesDirs = async (root, packageDirs) => {
90
+ const nodeModulesDirs = [];
91
+ const rootNodeModules = path.join(root, 'node_modules');
92
+ if (await fileExists(rootNodeModules)) {
93
+ nodeModulesDirs.push(rootNodeModules);
94
+ }
95
+ for (const packageDir of packageDirs) {
96
+ const packagesRoot = path.resolve(root, packageDir);
97
+ let entries;
98
+ try {
99
+ entries = await fs.readdir(packagesRoot);
100
+ }
101
+ catch (error) {
102
+ const code = typeof error === 'object' && error && 'code' in error ? error.code : undefined;
103
+ if (code === 'ENOENT') {
104
+ continue;
105
+ }
106
+ throw error;
107
+ }
108
+ await Promise.all(entries.map(async (entry) => {
109
+ const nodeModulesDir = path.join(packagesRoot, entry, 'node_modules');
110
+ if (await fileExists(nodeModulesDir)) {
111
+ nodeModulesDirs.push(nodeModulesDir);
112
+ }
113
+ }));
114
+ }
115
+ return nodeModulesDirs;
116
+ };
117
+ const discoverDependencyModules = async (options, packageDirs) => {
118
+ const dependencyRoots = [
119
+ ...splitPathList(process.env.COMPONENT_SHOT_DEPENDENCY_ROOT),
120
+ ...splitPathList(process.env.COMPONENT_SHOT_DEPENDENCY_ROOTS),
121
+ ...(options.dependencyRoots ?? []),
122
+ ];
123
+ const nodeModulesDirs = await Promise.all(dependencyRoots.map((root) => findNodeModulesDirs(path.resolve(root), packageDirs)));
124
+ return [...new Set(nodeModulesDirs.flat())];
125
+ };
126
+ const discoverWorkspaceAliases = async (cwd, packageDirs) => {
127
+ const aliases = {};
128
+ const workspaceRoot = await findWorkspaceRoot(cwd);
129
+ const roots = [...new Set([cwd, workspaceRoot])];
130
+ for (const root of roots) {
131
+ for (const packageDir of packageDirs) {
132
+ const packagesRoot = path.resolve(root, packageDir);
133
+ let entries;
134
+ try {
135
+ entries = await fs.readdir(packagesRoot);
136
+ }
137
+ catch (error) {
138
+ const code = typeof error === 'object' && error && 'code' in error ? error.code : undefined;
139
+ if (code === 'ENOENT') {
140
+ continue;
141
+ }
142
+ throw error;
143
+ }
144
+ await Promise.all(entries.map(async (entry) => {
145
+ const packageRoot = path.join(packagesRoot, entry);
146
+ const packageJson = await readJson(path.join(packageRoot, 'package.json'));
147
+ if (packageJson?.name) {
148
+ aliases[packageJson.name] = packageRoot;
149
+ }
150
+ }));
151
+ }
152
+ }
153
+ return aliases;
154
+ };
155
+ const resolveFromProject = (cwd, request) => path.dirname(require.resolve(`${request}/package.json`, { paths: [cwd, dirname] }));
156
+ const createConfig = async ({ context, options, }) => {
157
+ const setupPath = options.setup
158
+ ? path.resolve(context.cwd, options.setup)
159
+ : path.join(dirname, 'runtime/default-setup.js');
160
+ const packageDirs = options.workspacePackageDirs ?? ['packages'];
161
+ const [workspaceAliases, dependencyModules] = await Promise.all([
162
+ discoverWorkspaceAliases(context.cwd, packageDirs),
163
+ discoverDependencyModules(options, packageDirs),
164
+ ]);
165
+ const aliases = Object.fromEntries(Object.entries({
166
+ ...workspaceAliases,
167
+ ...options.aliases,
168
+ }).map(([name, target]) => [name, path.resolve(context.cwd, target)]));
169
+ const tsConfigPath = path.resolve(context.cwd, 'tsconfig.json');
170
+ const tsConfig = (await fileExists(tsConfigPath)) ? tsConfigPath : undefined;
171
+ return {
172
+ devtool: 'cheap-module-source-map',
173
+ entry: options.entry ? path.resolve(context.cwd, options.entry) : path.join(dirname, 'runtime/entry.js'),
174
+ externals: ['tinymce'],
175
+ mode: 'development',
176
+ module: {
177
+ rules: [
178
+ {
179
+ resolve: {
180
+ fullySpecified: false,
181
+ },
182
+ test: /\.mjs$/,
183
+ type: 'javascript/auto',
184
+ },
185
+ {
186
+ test: /\.(j|t)sx?$/,
187
+ type: 'javascript/auto',
188
+ use: {
189
+ loader: 'builtin:swc-loader',
190
+ options: {
191
+ env: {
192
+ targets: '> 1%, not dead',
193
+ },
194
+ jsc: {
195
+ parser: {
196
+ syntax: 'typescript',
197
+ tsx: true,
198
+ },
199
+ transform: {
200
+ react: {
201
+ runtime: 'automatic',
202
+ },
203
+ },
204
+ },
205
+ sourceMap: true,
206
+ },
207
+ },
208
+ },
209
+ {
210
+ test: /\.css$/,
211
+ type: 'css/auto',
212
+ },
213
+ {
214
+ test: /\.(png|jpe?g|webp|svg)$/,
215
+ type: 'asset/resource',
216
+ },
217
+ {
218
+ test: /\.(gql|graphql)$/,
219
+ type: 'asset/source',
220
+ },
221
+ ],
222
+ },
223
+ optimization: {
224
+ minimize: false,
225
+ splitChunks: false,
226
+ },
227
+ output: {
228
+ filename: 'component-shot.js',
229
+ path: context.publicDir,
230
+ publicPath: options.publicPath ?? '/',
231
+ },
232
+ plugins: [
233
+ new rspack.HtmlRspackPlugin({
234
+ filename: 'index.html',
235
+ templateContent: htmlTemplate(),
236
+ }),
237
+ new rspack.ProvidePlugin({
238
+ Buffer: ['buffer', 'Buffer'],
239
+ }),
240
+ ],
241
+ resolve: {
242
+ alias: {
243
+ ...aliases,
244
+ __component_shot_scenario__: context.scenarioPath,
245
+ __component_shot_setup__: setupPath,
246
+ react: resolveFromProject(context.cwd, 'react'),
247
+ 'react-dom': resolveFromProject(context.cwd, 'react-dom'),
248
+ 'react-dom/client': require.resolve('react-dom/client', { paths: [context.cwd, dirname] }),
249
+ 'react/jsx-dev-runtime': require.resolve('react/jsx-dev-runtime', {
250
+ paths: [context.cwd, dirname],
251
+ }),
252
+ 'react/jsx-runtime': require.resolve('react/jsx-runtime', { paths: [context.cwd, dirname] }),
253
+ },
254
+ extensions: ['.mjs', '.js', '.jsx', '.ts', '.tsx', '.json', '.gql', '.graphql'],
255
+ fallback: {
256
+ buffer: require.resolve('buffer/', { paths: [context.cwd, dirname] }),
257
+ },
258
+ modules: ['node_modules', ...dependencyModules],
259
+ tsConfig,
260
+ },
261
+ target: 'web',
262
+ };
263
+ };
264
+ const runRspack = async (context, options) => {
265
+ const config = await createConfig({ context, options });
266
+ await new Promise((resolve, reject) => {
267
+ rspack(config, (error, stats) => {
268
+ if (error) {
269
+ reject(error);
270
+ return;
271
+ }
272
+ if (stats?.hasErrors()) {
273
+ reject(new Error(stats.toString({ colors: true, errorDetails: true })));
274
+ return;
275
+ }
276
+ if (context.debug && stats) {
277
+ process.stderr.write(`${stats.toString({ colors: true })}\n`);
278
+ }
279
+ resolve();
280
+ });
281
+ });
282
+ };
283
+ const main = async () => {
284
+ const contextJson = process.env.COMPONENT_SHOT_RSPACK_CONTEXT;
285
+ if (!contextJson) {
286
+ throw new Error('COMPONENT_SHOT_RSPACK_CONTEXT is required');
287
+ }
288
+ const context = JSON.parse(contextJson);
289
+ const options = JSON.parse(process.env.COMPONENT_SHOT_RSPACK_OPTIONS ?? '{}');
290
+ await runRspack(context, options);
291
+ };
292
+ main().catch((error) => {
293
+ process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
294
+ process.exitCode = 1;
295
+ });
@@ -0,0 +1,11 @@
1
+ import type { ComponentShotBuild } from './build-types.js';
2
+ export type ComponentShotRspackOptions = {
3
+ aliases?: Record<string, string>;
4
+ dependencyRoots?: string[];
5
+ entry?: string;
6
+ publicPath?: string;
7
+ setup?: string;
8
+ workspacePackageDirs?: string[];
9
+ };
10
+ export declare const createRspackBuild: (options?: ComponentShotRspackOptions) => ComponentShotBuild;
11
+ //# sourceMappingURL=rspack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack.d.ts","sourceRoot":"","sources":["../src/rspack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,MAAM,MAAM,0BAA0B,GAAG;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC/B,CAAA;AAID,eAAO,MAAM,iBAAiB,GAC5B,UAAS,0BAA+B,KAAG,kBAQ1C,CAAA"}
package/dist/rspack.js ADDED
@@ -0,0 +1,11 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
4
+ export const createRspackBuild = (options = {}) => (context) => ({
5
+ args: [path.join(dirname, 'rspack-runner.js')],
6
+ command: process.execPath,
7
+ env: {
8
+ COMPONENT_SHOT_RSPACK_CONTEXT: JSON.stringify(context),
9
+ COMPONENT_SHOT_RSPACK_OPTIONS: JSON.stringify(options),
10
+ },
11
+ });
@@ -0,0 +1,4 @@
1
+ import type { ComponentShotAppSetup } from './types.js';
2
+ declare const defaultSetup: ComponentShotAppSetup;
3
+ export default defaultSetup;
4
+ //# sourceMappingURL=default-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-setup.d.ts","sourceRoot":"","sources":["../../src/runtime/default-setup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,QAAA,MAAM,YAAY,EAAE,qBAA0B,CAAA;AAE9C,eAAe,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ const defaultSetup = {};
2
+ export default defaultSetup;
@@ -0,0 +1,8 @@
1
+ declare global {
2
+ interface Window {
3
+ __COMPONENT_SHOT_ERROR__?: string;
4
+ __COMPONENT_SHOT_READY__?: boolean;
5
+ }
6
+ }
7
+ export {};
8
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/runtime/entry.tsx"],"names":[],"mappings":"AAUA,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,MAAM;QACf,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,wBAAwB,CAAC,EAAE,OAAO,CAAA;KAClC;CACD"}
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import scenarioExport from '__component_shot_scenario__';
3
+ import setupExport from '__component_shot_setup__';
4
+ import React from 'react';
5
+ import { createRoot } from 'react-dom/client';
6
+ const defaultRootStyle = {
7
+ display: 'inline-block',
8
+ maxWidth: '100%',
9
+ };
10
+ const nextFrame = () => new Promise((resolve) => {
11
+ let isSettled = false;
12
+ const finish = () => {
13
+ if (!isSettled) {
14
+ isSettled = true;
15
+ resolve();
16
+ }
17
+ };
18
+ window.requestAnimationFrame(() => {
19
+ window.requestAnimationFrame(finish);
20
+ });
21
+ window.setTimeout(finish, 100);
22
+ });
23
+ const isScenarioObject = (scenario) => typeof scenario === 'object' &&
24
+ scenario !== null &&
25
+ !React.isValidElement(scenario) &&
26
+ 'render' in scenario &&
27
+ typeof scenario.render === 'function';
28
+ const renderScenario = async (scenario) => {
29
+ if (isScenarioObject(scenario)) {
30
+ await scenario.setup?.();
31
+ return {
32
+ node: await scenario.render(),
33
+ objectScenario: scenario,
34
+ };
35
+ }
36
+ if (typeof scenario === 'function') {
37
+ return {
38
+ node: await scenario(),
39
+ };
40
+ }
41
+ return {
42
+ node: scenario,
43
+ };
44
+ };
45
+ const mount = async () => {
46
+ window.__COMPONENT_SHOT_READY__ = false;
47
+ window.__COMPONENT_SHOT_ERROR__ = undefined;
48
+ const rootElement = document.getElementById('root');
49
+ if (!rootElement) {
50
+ throw new Error('Missing #root element for component-shot');
51
+ }
52
+ const appSetup = setupExport ?? {};
53
+ const { node, objectScenario } = await renderScenario(scenarioExport);
54
+ const Wrapper = objectScenario?.wrapper;
55
+ const Provider = appSetup.Provider;
56
+ const providerOptions = objectScenario?.providerOptions;
57
+ const content = Wrapper ? _jsx(Wrapper, { children: node }) : node;
58
+ const wrappedContent = providerOptions === false || !Provider ? content : _jsx(Provider, { options: providerOptions, children: content });
59
+ createRoot(rootElement).render(_jsx("div", { "data-component-shot-root": true, style: objectScenario?.rootStyle ?? appSetup.rootStyle ?? defaultRootStyle, children: wrappedContent }));
60
+ await nextFrame();
61
+ await objectScenario?.beforeScreenshot?.();
62
+ await nextFrame();
63
+ window.__COMPONENT_SHOT_READY__ = true;
64
+ };
65
+ mount().catch((error) => {
66
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
67
+ window.__COMPONENT_SHOT_ERROR__ = message;
68
+ window.__COMPONENT_SHOT_READY__ = true;
69
+ const rootElement = document.getElementById('root');
70
+ if (rootElement) {
71
+ const errorElement = document.createElement('pre');
72
+ errorElement.dataset.componentShotError = 'true';
73
+ errorElement.textContent = message;
74
+ rootElement.replaceChildren(errorElement);
75
+ }
76
+ });