@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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/build-types.d.ts +15 -0
- package/dist/build-types.d.ts.map +1 -0
- package/dist/build-types.js +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/gallery.d.ts +38 -0
- package/dist/gallery.d.ts.map +1 -0
- package/dist/gallery.js +1939 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +680 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +247 -0
- package/dist/rspack-runner.d.ts +2 -0
- package/dist/rspack-runner.d.ts.map +1 -0
- package/dist/rspack-runner.js +295 -0
- package/dist/rspack.d.ts +11 -0
- package/dist/rspack.d.ts.map +1 -0
- package/dist/rspack.js +11 -0
- package/dist/runtime/default-setup.d.ts +4 -0
- package/dist/runtime/default-setup.d.ts.map +1 -0
- package/dist/runtime/default-setup.js +2 -0
- package/dist/runtime/entry.d.ts +8 -0
- package/dist/runtime/entry.d.ts.map +1 -0
- package/dist/runtime/entry.js +76 -0
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/skill.d.ts +17 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +217 -0
- package/package.json +78 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentType, CSSProperties, ReactNode } from 'react';
|
|
2
|
+
export type ComponentShotMaybePromise<T> = T | Promise<T>;
|
|
3
|
+
export type ComponentShotWrapper = ComponentType<{
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}>;
|
|
6
|
+
export type ComponentShotAppProvider<ProviderOptions = unknown> = ComponentType<{
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
options?: ProviderOptions;
|
|
9
|
+
}>;
|
|
10
|
+
export type ComponentShotAppSetup<ProviderOptions = unknown> = {
|
|
11
|
+
Provider?: ComponentShotAppProvider<ProviderOptions>;
|
|
12
|
+
rootStyle?: CSSProperties;
|
|
13
|
+
};
|
|
14
|
+
export type ComponentShotScenarioObject<ProviderOptions = unknown> = {
|
|
15
|
+
beforeScreenshot?: () => ComponentShotMaybePromise<void>;
|
|
16
|
+
providerOptions?: ProviderOptions | false;
|
|
17
|
+
render: () => ComponentShotMaybePromise<ReactNode>;
|
|
18
|
+
rootStyle?: CSSProperties;
|
|
19
|
+
setup?: () => ComponentShotMaybePromise<void>;
|
|
20
|
+
wrapper?: ComponentShotWrapper;
|
|
21
|
+
};
|
|
22
|
+
export type ComponentShotScenario<ProviderOptions = unknown> = ComponentShotScenarioObject<ProviderOptions> | ReactNode | (() => ComponentShotMaybePromise<ReactNode>);
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpE,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAEzD,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC,CAAA;AAEzE,MAAM,MAAM,wBAAwB,CAAC,eAAe,GAAG,OAAO,IAAI,aAAa,CAAC;IAC/E,QAAQ,EAAE,SAAS,CAAA;IACnB,OAAO,CAAC,EAAE,eAAe,CAAA;CACzB,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,CAAC,eAAe,GAAG,OAAO,IAAI;IAC9D,QAAQ,CAAC,EAAE,wBAAwB,CAAC,eAAe,CAAC,CAAA;IACpD,SAAS,CAAC,EAAE,aAAa,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,2BAA2B,CAAC,eAAe,GAAG,OAAO,IAAI;IACpE,gBAAgB,CAAC,EAAE,MAAM,yBAAyB,CAAC,IAAI,CAAC,CAAA;IACxD,eAAe,CAAC,EAAE,eAAe,GAAG,KAAK,CAAA;IACzC,MAAM,EAAE,MAAM,yBAAyB,CAAC,SAAS,CAAC,CAAA;IAClD,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,yBAAyB,CAAC,IAAI,CAAC,CAAA;IAC7C,OAAO,CAAC,EAAE,oBAAoB,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,CAAC,eAAe,GAAG,OAAO,IACxD,2BAA2B,CAAC,eAAe,CAAC,GAC5C,SAAS,GACT,CAAC,MAAM,yBAAyB,CAAC,SAAS,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/skill.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ComponentShotSkillInstallOptions = {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
json?: boolean;
|
|
4
|
+
name?: string;
|
|
5
|
+
outputDir?: string;
|
|
6
|
+
overwrite?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ComponentShotSkillInstallResult = {
|
|
9
|
+
files: string[];
|
|
10
|
+
skillDir: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const installComponentShotSkill: (optionsInput?: ComponentShotSkillInstallOptions) => Promise<ComponentShotSkillInstallResult>;
|
|
13
|
+
export declare const runComponentShotSkillCli: ({ argv, usageCommand, }?: {
|
|
14
|
+
argv?: string[];
|
|
15
|
+
usageCommand?: string;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
//# sourceMappingURL=skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gCAAgC,GAAG;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC7C,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CAChB,CAAA;AAyMD,eAAO,MAAM,yBAAyB,GACrC,eAAc,gCAAqC,KACjD,OAAO,CAAC,+BAA+B,CA0CzC,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAU,0BAG5C;IACF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CAChB,kBAaL,CAAA"}
|
package/dist/skill.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const defaultSkillOptions = {
|
|
4
|
+
name: 'component-shot',
|
|
5
|
+
outputDir: '.codex/skills',
|
|
6
|
+
};
|
|
7
|
+
const isValidSkillName = (value) => value.length <= 64 &&
|
|
8
|
+
/^[a-z0-9-]+$/.test(value) &&
|
|
9
|
+
!value.startsWith('-') &&
|
|
10
|
+
!value.endsWith('-') &&
|
|
11
|
+
!value.includes('--');
|
|
12
|
+
const readFlagValue = (args, index, flag) => {
|
|
13
|
+
const inlineValue = flag.includes('=') ? flag.slice(flag.indexOf('=') + 1) : undefined;
|
|
14
|
+
if (inlineValue) {
|
|
15
|
+
return [inlineValue, index];
|
|
16
|
+
}
|
|
17
|
+
const value = args[index + 1];
|
|
18
|
+
if (!value || value.startsWith('--')) {
|
|
19
|
+
throw new Error(`Missing value for ${flag}`);
|
|
20
|
+
}
|
|
21
|
+
return [value, index + 1];
|
|
22
|
+
};
|
|
23
|
+
const createSkillUsage = (usageCommand) => `Usage:
|
|
24
|
+
${usageCommand}
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--output-dir <path> Skill parent directory. Defaults to .codex/skills.
|
|
28
|
+
--path <path> Alias for --output-dir.
|
|
29
|
+
--name <name> Skill folder/name. Defaults to component-shot.
|
|
30
|
+
--cwd <path> Repository root. Defaults to the current directory.
|
|
31
|
+
--overwrite Replace an existing skill.
|
|
32
|
+
--json Print machine-readable install details.
|
|
33
|
+
--help Show this help message.`;
|
|
34
|
+
const parseSkillCliArgs = ({ argv, usageCommand, }) => {
|
|
35
|
+
const options = { ...defaultSkillOptions };
|
|
36
|
+
const usage = createSkillUsage(usageCommand);
|
|
37
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
38
|
+
const arg = argv[index];
|
|
39
|
+
const flag = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
|
|
40
|
+
switch (flag) {
|
|
41
|
+
case '--cwd': {
|
|
42
|
+
const [value, nextIndex] = readFlagValue(argv, index, arg);
|
|
43
|
+
options.cwd = value;
|
|
44
|
+
index = nextIndex;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case '--help':
|
|
48
|
+
case '-h':
|
|
49
|
+
process.stdout.write(`${usage}\n`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
break;
|
|
52
|
+
case '--json':
|
|
53
|
+
options.json = true;
|
|
54
|
+
break;
|
|
55
|
+
case '--name': {
|
|
56
|
+
const [value, nextIndex] = readFlagValue(argv, index, arg);
|
|
57
|
+
options.name = value;
|
|
58
|
+
index = nextIndex;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case '--output-dir':
|
|
62
|
+
case '--path': {
|
|
63
|
+
const [value, nextIndex] = readFlagValue(argv, index, arg);
|
|
64
|
+
options.outputDir = value;
|
|
65
|
+
index = nextIndex;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case '--overwrite':
|
|
69
|
+
options.overwrite = true;
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unknown skill option "${arg}"\n\n${usage}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!isValidSkillName(options.name)) {
|
|
76
|
+
throw new Error('--name must use lowercase letters, numbers, and hyphens, be 1-64 characters long, and not start/end with or contain consecutive hyphens');
|
|
77
|
+
}
|
|
78
|
+
return options;
|
|
79
|
+
};
|
|
80
|
+
const pathExists = async (filePath) => {
|
|
81
|
+
try {
|
|
82
|
+
await fs.access(filePath);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const code = typeof error === 'object' && error && 'code' in error ? error.code : undefined;
|
|
87
|
+
if (code === 'ENOENT') {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const ensureTrailingNewline = (value) => (value.endsWith('\n') ? value : `${value}\n`);
|
|
94
|
+
const createSkillMarkdown = (skillName) => ensureTrailingNewline(`---
|
|
95
|
+
name: ${skillName}
|
|
96
|
+
description: Use when creating, updating, rendering, inspecting, visually testing, or reviewing component-shot scenarios for UI components during component design iteration; capture screenshots through the component-shot CLI or MCP server; run the live scenario gallery; debug visual regressions; or produce reusable scenario files under component-shot/scenarios.
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# Component Shot
|
|
100
|
+
|
|
101
|
+
Use component-shot to iterate component designs with live-rendered scenarios, browser screenshots, and reusable visual states.
|
|
102
|
+
|
|
103
|
+
## Workflow
|
|
104
|
+
|
|
105
|
+
1. Locate the project root and existing component-shot assets.
|
|
106
|
+
- Scenarios usually live in \`component-shot/scenarios\`.
|
|
107
|
+
- App providers usually live in \`component-shot/setup.tsx\`, \`setup.ts\`, \`setup.jsx\`, or \`setup.js\`.
|
|
108
|
+
- Screenshot history usually lives in \`component-shot/screenshots\`.
|
|
109
|
+
2. Prefer creating or updating deterministic scenario files for important design states over one-off screenshots when the state may be reused.
|
|
110
|
+
3. Use the local project binary when possible. Prefer \`component-shot ...\` when available; otherwise use the repo package manager, for example \`pnpm exec component-shot ...\`, \`npm exec component-shot -- ...\`, or \`yarn component-shot ...\`.
|
|
111
|
+
4. Run the live gallery during UI iteration:
|
|
112
|
+
|
|
113
|
+
\`\`\`bash
|
|
114
|
+
component-shot gallery
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
Use \`--scenario-dir <path>\` when scenarios are outside \`component-shot/scenarios\`.
|
|
118
|
+
|
|
119
|
+
5. Capture an existing scenario when a static screenshot is needed:
|
|
120
|
+
|
|
121
|
+
\`\`\`bash
|
|
122
|
+
component-shot --scenario component-shot/scenarios/example.tsx --save --json
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
6. If a component-shot MCP server is available, use it for direct visual inspection:
|
|
126
|
+
- \`capture_component_shot\` for an existing scenario file.
|
|
127
|
+
- \`capture_component_source\` to write a scenario source file, capture it, and receive the image.
|
|
128
|
+
|
|
129
|
+
## Scenario Pattern
|
|
130
|
+
|
|
131
|
+
Create one file per important UI state. Export either a React node/function or a scenario object.
|
|
132
|
+
|
|
133
|
+
\`\`\`tsx
|
|
134
|
+
import type { ComponentShotScenarioObject } from '@lioneltay/component-shot'
|
|
135
|
+
import { ProductCard } from '../../src/components/ProductCard'
|
|
136
|
+
|
|
137
|
+
const scenario: ComponentShotScenarioObject = {
|
|
138
|
+
render: () => (
|
|
139
|
+
<ProductCard
|
|
140
|
+
badge="Popular"
|
|
141
|
+
ctaLabel="Add kit"
|
|
142
|
+
description="Reusable capture defaults, tuned for review."
|
|
143
|
+
name="Shot Runner"
|
|
144
|
+
price="$49"
|
|
145
|
+
/>
|
|
146
|
+
),
|
|
147
|
+
rootStyle: {
|
|
148
|
+
display: 'block',
|
|
149
|
+
width: 380,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default scenario
|
|
154
|
+
\`\`\`
|
|
155
|
+
|
|
156
|
+
Use \`providerOptions\` when \`component-shot/setup.*\` defines a Provider that accepts options. Use \`beforeScreenshot\` for deterministic async setup, such as waiting for animations or data mocks.
|
|
157
|
+
|
|
158
|
+
## Review Guidance
|
|
159
|
+
|
|
160
|
+
- Treat the gallery live render as the source of truth while iterating.
|
|
161
|
+
- Treat screenshot history as audit output. Do not delete screenshot history unless explicitly asked.
|
|
162
|
+
- Keep scenarios deterministic: fixed props, stable dates, mocked randomness, and no live network dependency.
|
|
163
|
+
- When a component is clipped, set an explicit \`rootStyle.width\` or update the scenario layout before capturing.
|
|
164
|
+
- When adding multiple states, prefer descriptive filenames such as \`empty-state.tsx\`, \`loading.tsx\`, and \`error-banner.tsx\`.
|
|
165
|
+
|
|
166
|
+
## Validation
|
|
167
|
+
|
|
168
|
+
After changing scenarios or setup:
|
|
169
|
+
|
|
170
|
+
1. Run the relevant app typecheck/build if available.
|
|
171
|
+
2. Run \`component-shot gallery\` for live inspection, or capture with \`component-shot --scenario ... --save --json\`.
|
|
172
|
+
3. Inspect the image or live render before reporting completion.
|
|
173
|
+
`);
|
|
174
|
+
export const installComponentShotSkill = async (optionsInput = {}) => {
|
|
175
|
+
const options = {
|
|
176
|
+
...defaultSkillOptions,
|
|
177
|
+
...optionsInput,
|
|
178
|
+
};
|
|
179
|
+
if (!isValidSkillName(options.name)) {
|
|
180
|
+
throw new Error('--name must use lowercase letters, numbers, and hyphens, be 1-64 characters long, and not start/end with or contain consecutive hyphens');
|
|
181
|
+
}
|
|
182
|
+
const cwd = path.resolve(process.cwd(), options.cwd ?? '.');
|
|
183
|
+
const outputDir = path.resolve(cwd, options.outputDir);
|
|
184
|
+
const skillDir = path.join(outputDir, options.name);
|
|
185
|
+
const files = [
|
|
186
|
+
{
|
|
187
|
+
content: createSkillMarkdown(options.name),
|
|
188
|
+
path: path.join(skillDir, 'SKILL.md'),
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
if (!options.overwrite) {
|
|
192
|
+
const existingFiles = (await Promise.all(files.map(async (file) => ((await pathExists(file.path)) ? file.path : undefined)))).filter((file) => Boolean(file));
|
|
193
|
+
if (existingFiles.length > 0) {
|
|
194
|
+
throw new Error(`Component Shot skill already exists:\n${existingFiles.join('\n')}\nUse --overwrite to replace it.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
await fs.mkdir(path.dirname(file.path), { recursive: true });
|
|
199
|
+
await fs.writeFile(file.path, file.content, 'utf8');
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
files: files.map((file) => file.path),
|
|
203
|
+
skillDir,
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
export const runComponentShotSkillCli = async ({ argv = process.argv.slice(2), usageCommand = 'component-shot skill [options]', } = {}) => {
|
|
207
|
+
const options = parseSkillCliArgs({ argv, usageCommand });
|
|
208
|
+
const result = await installComponentShotSkill(options);
|
|
209
|
+
if (options.json) {
|
|
210
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
process.stdout.write(`Installed Component Shot skill: ${result.skillDir}\n`);
|
|
214
|
+
for (const file of result.files) {
|
|
215
|
+
process.stdout.write(`- ${file}\n`);
|
|
216
|
+
}
|
|
217
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lioneltay/component-shot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Capture, inspect, and iterate React component scenarios with Playwright.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Lionel Tay",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"component-shot": "dist/cli.js",
|
|
12
|
+
"component-shot-mcp": "dist/mcp.js"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/lioneltay/component-shot.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/lioneltay/component-shot/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/lioneltay/component-shot#readme",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"component",
|
|
36
|
+
"screenshot",
|
|
37
|
+
"playwright",
|
|
38
|
+
"visual-testing",
|
|
39
|
+
"react",
|
|
40
|
+
"mcp",
|
|
41
|
+
"codex"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20.11"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"browsers": "playwright install chromium",
|
|
51
|
+
"build": "tsc -p tsconfig.json",
|
|
52
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"prepare": "pnpm build",
|
|
54
|
+
"prepublishOnly": "pnpm verify",
|
|
55
|
+
"release:dry-run": "pnpm verify && npm pack --dry-run",
|
|
56
|
+
"release:publish": "pnpm verify && npm publish --access public",
|
|
57
|
+
"verify": "pnpm check && pnpm build && pnpm --dir demo build"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
61
|
+
"@rspack/core": "2.0.5",
|
|
62
|
+
"buffer": "6.0.3",
|
|
63
|
+
"playwright": "1.56.1"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "24.10.4",
|
|
67
|
+
"@types/react": "18.0.17",
|
|
68
|
+
"@types/react-dom": "18.0.6",
|
|
69
|
+
"react": "18.3.1",
|
|
70
|
+
"react-dom": "18.3.1",
|
|
71
|
+
"typescript": "6.0.3"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"react": ">=18",
|
|
75
|
+
"react-dom": ">=18"
|
|
76
|
+
},
|
|
77
|
+
"packageManager": "pnpm@10.26.1"
|
|
78
|
+
}
|