@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 ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to Component Shot will be documented here.
4
+
5
+ ## 0.1.0 - Initial public release
6
+
7
+ - Capture React component scenario modules with Playwright.
8
+ - Bundle scenarios through the built-in Rspack pipeline.
9
+ - Save latest and historical screenshots for named scenarios.
10
+ - Run a live scenario gallery with search, column controls, pinning, deletion, and detail pages.
11
+ - Expose MCP tools for existing scenarios and generated scenario source.
12
+ - Install a repo-local Component Shot Codex skill with `component-shot skill`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lionel Tay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,310 @@
1
+ # Component Shot
2
+
3
+ Capture, inspect, and iterate React component scenarios from the command line, a live gallery, or MCP tools.
4
+
5
+ Component Shot is built for design review and agent workflows. You write small scenario files that render important UI states, then Component Shot bundles them, serves them locally, renders them in Playwright, and captures or previews the result.
6
+
7
+ ## Features
8
+
9
+ - One-command screenshots for React scenario modules.
10
+ - A live scenario gallery with search, column layout controls, pinning, delete, clear, and detail views.
11
+ - Screenshot history for saved captures.
12
+ - App-level setup providers for themes, routers, data clients, or other wrappers.
13
+ - MCP tools that return screenshots as image content.
14
+ - A repo-local Codex skill installer for repeatable Component Shot workflows.
15
+ - Built-in Rspack bundling with a build-command escape hatch.
16
+
17
+ ## Requirements
18
+
19
+ - Node.js `>=20.11`
20
+ - React and React DOM in the app using Component Shot
21
+ - A Playwright browser, or an installed Chrome/Edge channel
22
+
23
+ Install Chromium for Playwright when needed:
24
+
25
+ ```bash
26
+ pnpm exec playwright install chromium
27
+ ```
28
+
29
+ If you already have Google Chrome installed, pass `--browser-channel chrome` to the capture command instead.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pnpm add -D @lioneltay/component-shot react react-dom
35
+ ```
36
+
37
+ The package exposes two binaries:
38
+
39
+ ```bash
40
+ component-shot
41
+ component-shot-mcp
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ Create a scenario:
47
+
48
+ ```tsx
49
+ // component-shot/scenarios/product-card.tsx
50
+ import type { ComponentShotScenarioObject } from '@lioneltay/component-shot'
51
+ import { ProductCard } from '../../src/components/ProductCard'
52
+
53
+ const scenario: ComponentShotScenarioObject = {
54
+ render: () => (
55
+ <ProductCard
56
+ badge="Popular"
57
+ ctaLabel="Add kit"
58
+ description="Reusable capture defaults, tuned for design review."
59
+ name="Shot Runner"
60
+ price="$49"
61
+ />
62
+ ),
63
+ rootStyle: {
64
+ display: 'block',
65
+ width: 380,
66
+ },
67
+ }
68
+
69
+ export default scenario
70
+ ```
71
+
72
+ Capture it:
73
+
74
+ ```bash
75
+ pnpm exec component-shot \
76
+ --scenario component-shot/scenarios/product-card.tsx \
77
+ --output /tmp/product-card.png
78
+ ```
79
+
80
+ Save latest and historical screenshots:
81
+
82
+ ```bash
83
+ pnpm exec component-shot \
84
+ --scenario component-shot/scenarios/product-card.tsx \
85
+ --save \
86
+ --json
87
+ ```
88
+
89
+ This writes:
90
+
91
+ ```text
92
+ component-shot/screenshots/product-card/latest.png
93
+ component-shot/screenshots/product-card/history/<timestamp>.png
94
+ ```
95
+
96
+ ## App Setup
97
+
98
+ Add `component-shot/setup.tsx` when scenarios need app providers:
99
+
100
+ ```tsx
101
+ import type { ComponentShotAppSetup } from '@lioneltay/component-shot'
102
+ import { ThemeProvider } from '../src/theme'
103
+
104
+ const setup: ComponentShotAppSetup = {
105
+ Provider: ({ children }) => <ThemeProvider>{children}</ThemeProvider>,
106
+ rootStyle: {
107
+ display: 'inline-block',
108
+ },
109
+ }
110
+
111
+ export default setup
112
+ ```
113
+
114
+ Scenario objects can opt out of the setup provider with `providerOptions: false`, or pass options to the provider with `providerOptions`.
115
+
116
+ ## Scenario API
117
+
118
+ A scenario can export:
119
+
120
+ - a React node
121
+ - a function returning a React node
122
+ - a `ComponentShotScenarioObject`
123
+
124
+ ```tsx
125
+ import type { ComponentShotScenarioObject } from '@lioneltay/component-shot'
126
+
127
+ const scenario: ComponentShotScenarioObject = {
128
+ setup: async () => {
129
+ localStorage.clear()
130
+ },
131
+ render: () => <button>Save</button>,
132
+ beforeScreenshot: async () => {
133
+ await new Promise((resolve) => window.setTimeout(resolve, 100))
134
+ },
135
+ rootStyle: {
136
+ display: 'inline-block',
137
+ width: 240,
138
+ },
139
+ }
140
+
141
+ export default scenario
142
+ ```
143
+
144
+ Use deterministic props, mocked data, fixed dates, and stable dimensions. If the gallery clips or scales a component unexpectedly, set an explicit `rootStyle.width`.
145
+
146
+ ## CLI
147
+
148
+ ```bash
149
+ component-shot --scenario <file.tsx> [options]
150
+ ```
151
+
152
+ Common options:
153
+
154
+ | Option | Description |
155
+ | --- | --- |
156
+ | `--scenario <path>` | Scenario module to render. |
157
+ | `--output <path>` | PNG output path. Defaults to a temp PNG. |
158
+ | `--save` | Write `latest.png` and a timestamped history image. |
159
+ | `--save-name <name>` | Screenshot folder name. Defaults to scenario filename. |
160
+ | `--screenshots-dir <path>` | Screenshot root. Defaults to `component-shot/screenshots`. |
161
+ | `--selector <selector>` | Element to capture. Defaults to `[data-component-shot-root]`. |
162
+ | `--full-page` | Capture the whole page instead of the component root. |
163
+ | `--viewport <WxH>` | Browser viewport. Defaults to `1440x900`. |
164
+ | `--wait-for <selector>` | Wait for an additional selector before capture. |
165
+ | `--browser-channel <id>` | Use a system browser channel, for example `chrome`. |
166
+ | `--setup <path>` | Override setup module discovery. |
167
+ | `--build-command <cmd>` | Escape hatch for custom build pipelines. |
168
+ | `--json` | Print machine-readable output. |
169
+
170
+ Write a scenario and capture it in one command:
171
+
172
+ ```bash
173
+ component-shot \
174
+ --source "export default { render: () => <button>Save</button> }" \
175
+ --name save-button \
176
+ --save
177
+ ```
178
+
179
+ ## Gallery
180
+
181
+ Open the live gallery:
182
+
183
+ ```bash
184
+ component-shot gallery
185
+ ```
186
+
187
+ The gallery scans `component-shot/scenarios`, bundles scenarios on demand, and renders each one in a live iframe preview. It watches the active `component-shot` directory and reloads when scenarios, setup, or screenshot history changes.
188
+
189
+ Gallery features:
190
+
191
+ - Search by scenario name or path.
192
+ - Switch between auto layout and fixed two-, three-, or four-column grids.
193
+ - Pin scenarios to keep them at the top.
194
+ - Delete one scenario source file.
195
+ - Clear all discovered scenario source files.
196
+ - Open a scenario detail page with the live render first and screenshot history below it.
197
+
198
+ Use a custom scenario directory:
199
+
200
+ ```bash
201
+ component-shot gallery \
202
+ --scenario-dir packages/client/component-shot/scenarios
203
+ ```
204
+
205
+ Use `--screenshots-dir` when screenshot history is not beside the scenario directory. Use `--no-open` to print the local URL without opening a browser.
206
+
207
+ ## MCP Server
208
+
209
+ Run `component-shot-mcp` from an MCP client to capture scenarios directly from an agent.
210
+
211
+ Example config:
212
+
213
+ ```json
214
+ {
215
+ "mcpServers": {
216
+ "component-shot": {
217
+ "command": "component-shot-mcp",
218
+ "env": {
219
+ "COMPONENT_SHOT_PROJECT_ROOT": "/path/to/app",
220
+ "COMPONENT_SHOT_SCENARIO_DIR": "component-shot/scenarios"
221
+ }
222
+ }
223
+ }
224
+ }
225
+ ```
226
+
227
+ Tools:
228
+
229
+ - `capture_component_shot`: capture an existing scenario file.
230
+ - `capture_component_source`: write scenario source, capture it, and return the image.
231
+
232
+ For worktrees that share dependencies from another checkout, set `COMPONENT_SHOT_DEPENDENCY_ROOT` to that installed checkout.
233
+
234
+ ## Codex Skill
235
+
236
+ Install a repo-local Component Shot skill:
237
+
238
+ ```bash
239
+ component-shot skill
240
+ ```
241
+
242
+ This writes:
243
+
244
+ ```text
245
+ .codex/skills/component-shot/SKILL.md
246
+ ```
247
+
248
+ Use `--output-dir` or `--path` to choose a different skill parent directory, and `--overwrite` to replace an existing generated skill.
249
+
250
+ ## Programmatic API
251
+
252
+ Apps can create thin wrappers when they need project-specific defaults:
253
+
254
+ ```ts
255
+ #!/usr/bin/env node
256
+ import { runComponentShotCli } from '@lioneltay/component-shot'
257
+
258
+ await runComponentShotCli({
259
+ argv: process.argv.slice(2),
260
+ setup: 'component-shot/setup.tsx',
261
+ })
262
+ ```
263
+
264
+ The package also exports the scenario/setup types and `createRspackBuild` for custom wrappers.
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ pnpm install
270
+ pnpm browsers
271
+ pnpm build
272
+ pnpm check
273
+ pnpm --dir demo build
274
+ ```
275
+
276
+ Run the demo gallery:
277
+
278
+ ```bash
279
+ pnpm --dir demo gallery
280
+ ```
281
+
282
+ Run the full verification command:
283
+
284
+ ```bash
285
+ pnpm verify
286
+ ```
287
+
288
+ ## Publishing
289
+
290
+ The intended release path is the npm package with Node.js binaries:
291
+
292
+ ```bash
293
+ pnpm release:dry-run
294
+ pnpm release:publish
295
+ ```
296
+
297
+ Before publishing:
298
+
299
+ - Confirm `CHANGELOG.md` is current.
300
+ - Confirm the version in `package.json`.
301
+ - Inspect the `npm pack --dry-run` output.
302
+ - Confirm the package name and `publishConfig.access` are correct for a public scoped package.
303
+
304
+ ### Bun Binary
305
+
306
+ A Bun-compiled single executable is not the primary release target yet. Component Shot bundles user scenario files at runtime and depends on Playwright browser installation, so the npm binary is the lower-risk first public release. Revisit a Bun binary once the npm package API and gallery workflow stabilize.
307
+
308
+ ## License
309
+
310
+ MIT
@@ -0,0 +1,15 @@
1
+ export type ComponentShotBuildContext = {
2
+ cwd: string;
3
+ debug: boolean;
4
+ publicDir: string;
5
+ scenarioPath: string;
6
+ };
7
+ export type ComponentShotBuildCommand = {
8
+ args?: string[];
9
+ command: string;
10
+ cwd?: string;
11
+ env?: Record<string, string | undefined>;
12
+ shell?: boolean;
13
+ };
14
+ export type ComponentShotBuild = ComponentShotBuildCommand | ((context: ComponentShotBuildContext) => ComponentShotBuildCommand | void | Promise<ComponentShotBuildCommand | void>);
15
+ //# sourceMappingURL=build-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-types.d.ts","sourceRoot":"","sources":["../src/build-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,yBAAyB,GAAG;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACvC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACxC,KAAK,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,kBAAkB,GAC3B,yBAAyB,GACzB,CAAC,CACD,OAAO,EAAE,yBAAyB,KAC7B,yBAAyB,GAAG,IAAI,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runComponentShotCli } from './index.js';
3
+ await runComponentShotCli();
@@ -0,0 +1,38 @@
1
+ import http from 'node:http';
2
+ export type ComponentShotGalleryOptions = {
3
+ cwd?: string;
4
+ host?: string;
5
+ open?: boolean;
6
+ port?: number;
7
+ scenarioDir?: string;
8
+ screenshotsDir?: string;
9
+ };
10
+ export type ComponentShotGalleryScenario = {
11
+ detailUrl: string;
12
+ historyCount: number;
13
+ id: string;
14
+ name: string;
15
+ previewUrl: string;
16
+ relativeScenarioPath: string;
17
+ renderUrl: string;
18
+ scenarioPath: string;
19
+ };
20
+ export type ComponentShotGalleryIndex = {
21
+ cwd: string;
22
+ scenarioDir: string;
23
+ scenarios: ComponentShotGalleryScenario[];
24
+ screenshotsDir: string;
25
+ };
26
+ export type ComponentShotGalleryServer = {
27
+ close: () => Promise<void>;
28
+ index: ComponentShotGalleryIndex;
29
+ server: http.Server;
30
+ url: string;
31
+ };
32
+ export declare const createComponentShotGalleryIndex: (options?: ComponentShotGalleryOptions) => Promise<ComponentShotGalleryIndex>;
33
+ export declare const startComponentShotGallery: (optionsInput?: ComponentShotGalleryOptions) => Promise<ComponentShotGalleryServer>;
34
+ export declare const runComponentShotGalleryCli: ({ argv, usageCommand, }?: {
35
+ argv?: string[];
36
+ usageCommand?: string;
37
+ }) => Promise<void>;
38
+ //# sourceMappingURL=gallery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery.d.ts","sourceRoot":"","sources":["../src/gallery.ts"],"names":[],"mappings":"AAGA,OAAO,IAAI,MAAM,WAAW,CAAA;AAK5B,MAAM,MAAM,2BAA2B,GAAG;IACzC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,4BAA4B,EAAE,CAAA;IACzC,cAAc,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACxC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,KAAK,EAAE,yBAAyB,CAAA;IAChC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;CACX,CAAA;AAuVD,eAAO,MAAM,+BAA+B,GAC3C,UAAS,2BAAgC,KACvC,OAAO,CAAC,yBAAyB,CA8BnC,CAAA;AAiqDD,eAAO,MAAM,yBAAyB,GACrC,eAAc,2BAAgC,KAC5C,OAAO,CAAC,0BAA0B,CA6EpC,CAAA;AA0BD,eAAO,MAAM,0BAA0B,GAAU,0BAG9C;IACF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CAChB,kBAwBL,CAAA"}