frameshot-mcp 0.7.0 → 0.8.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/README.md +88 -20
- package/action.yml +105 -16
- package/dist/chunk-3CDSNOX5.js +869 -0
- package/dist/chunk-AUACBLHM.js +191 -0
- package/dist/chunk-GVOEFYEX.js +139 -0
- package/dist/chunk-L2CADTS7.js +191 -0
- package/dist/chunk-MA3FOIQY.js +157 -0
- package/dist/chunk-O7NWWFIU.js +871 -0
- package/dist/chunk-PYWXJZTZ.js +1123 -0
- package/dist/chunk-Q7NQA4ZM.js +1095 -0
- package/dist/cli.js +8 -23
- package/dist/index.js +144 -46
- package/dist/renderer.d.ts +11 -5
- package/dist/renderer.js +4 -6
- package/package.json +3 -2
- package/scripts/render-changed.mjs +83 -16
package/README.md
CHANGED
|
@@ -46,6 +46,18 @@ Claude will automatically call `render_file`, `diff_component`, or `render_catal
|
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
+
## Try it now
|
|
50
|
+
|
|
51
|
+
No AI client required. Works with any React / Vue / Svelte project:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx frameshot render src/components/Button.tsx
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> First run installs Playwright's Chromium (~150MB). Subsequent renders are fast.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
49
61
|
## Render
|
|
50
62
|
|
|
51
63
|
Point to a file. Get a screenshot. All imports resolved.
|
|
@@ -83,6 +95,46 @@ diff_component("src/components/MetricCard.tsx")
|
|
|
83
95
|
|
|
84
96
|
## Tools
|
|
85
97
|
|
|
98
|
+
### `render_file` — Render a component with full dependency resolution
|
|
99
|
+
|
|
100
|
+
<p align="center">
|
|
101
|
+
<img src="docs/example-output.png" alt="Rendered PricingCard" width="280" />
|
|
102
|
+
</p>
|
|
103
|
+
|
|
104
|
+
Real Tailwind. Real CSS Modules. Real imports — resolved by Vite.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### `diff_component` — Visual diff against git HEAD
|
|
109
|
+
|
|
110
|
+
<p align="center">
|
|
111
|
+
<img src="docs/diff-before.png" alt="Before" width="200" />
|
|
112
|
+
|
|
113
|
+
<img src="docs/diff-after.png" alt="After" width="200" />
|
|
114
|
+
|
|
115
|
+
<img src="docs/diff-result.png" alt="Diff" width="200" />
|
|
116
|
+
</p>
|
|
117
|
+
|
|
118
|
+
Pixel-accurate before/after comparison. Returns diff percentage.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### `render_catalog` — Screenshot every component in a directory
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx frameshot catalog src/components/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Renders all components at once and saves PNGs to the output directory.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### `audit_a11y` — Accessibility audit with axe-core
|
|
133
|
+
|
|
134
|
+
Returns WCAG violations with severity and selector. No browser extension needed.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
86
138
|
| Tool | What it does |
|
|
87
139
|
|------|-------------|
|
|
88
140
|
| `render_file` | **Render a file via Vite** — full dep resolution, props support |
|
|
@@ -122,24 +174,11 @@ diff_component("src/components/MetricCard.tsx")
|
|
|
122
174
|
|
|
123
175
|
---
|
|
124
176
|
|
|
125
|
-
## CLI
|
|
126
|
-
|
|
127
|
-
No AI client required. Use frameshot standalone:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
npx frameshot render src/components/Button.tsx
|
|
131
|
-
npx frameshot render src/Card.tsx --props '{"title":"Hello"}'
|
|
132
|
-
npx frameshot catalog src/components/ --recursive
|
|
133
|
-
npx frameshot diff src/components/Header.tsx
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
177
|
## GitHub Action
|
|
141
178
|
|
|
142
|
-
Free Chromatic alternative. Auto-posts before/after screenshots
|
|
179
|
+
Free Chromatic alternative. Auto-detects changed components and posts before/after screenshots on every PR.
|
|
180
|
+
|
|
181
|
+
Add this file to your repo:
|
|
143
182
|
|
|
144
183
|
```yaml
|
|
145
184
|
# .github/workflows/visual-preview.yml
|
|
@@ -149,14 +188,43 @@ jobs:
|
|
|
149
188
|
preview:
|
|
150
189
|
runs-on: ubuntu-latest
|
|
151
190
|
steps:
|
|
152
|
-
- uses: actions/checkout@
|
|
191
|
+
- uses: actions/checkout@v7
|
|
153
192
|
with:
|
|
154
193
|
fetch-depth: 0
|
|
155
|
-
- uses: kamegoro/frameshot@
|
|
156
|
-
with:
|
|
157
|
-
paths: "./src/components/*.tsx"
|
|
194
|
+
- uses: kamegoro/frameshot@v0.8.0
|
|
158
195
|
```
|
|
159
196
|
|
|
197
|
+
That's it. No config needed — changed `.tsx`, `.jsx`, `.vue`, `.svelte` files are detected automatically.
|
|
198
|
+
|
|
199
|
+
Optionally scope to specific paths or customize which files are included:
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
- uses: kamegoro/frameshot@v0.8.0
|
|
203
|
+
with:
|
|
204
|
+
# Render only these files (default: auto-detect changed components)
|
|
205
|
+
paths: "./src/components/*.tsx"
|
|
206
|
+
|
|
207
|
+
# File extensions to treat as components
|
|
208
|
+
# Default: .jsx,.tsx,.vue,.svelte,.astro,.mdx
|
|
209
|
+
extensions: ".jsx,.tsx,.vue"
|
|
210
|
+
|
|
211
|
+
# Patterns to exclude (matched against filename)
|
|
212
|
+
# Default: *.test.*,*.spec.*,*.stories.*,*.story.*
|
|
213
|
+
exclude: "*.test.*,*.spec.*,*.stories.*"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## CLI — advanced usage
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npx frameshot render src/Card.tsx --props '{"title":"Hello"}'
|
|
222
|
+
npx frameshot catalog src/components/ --recursive
|
|
223
|
+
npx frameshot diff src/components/Header.tsx
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
|
|
227
|
+
|
|
160
228
|
---
|
|
161
229
|
|
|
162
230
|
## Performance
|
package/action.yml
CHANGED
|
@@ -6,8 +6,17 @@ branding:
|
|
|
6
6
|
|
|
7
7
|
inputs:
|
|
8
8
|
paths:
|
|
9
|
-
description: "Glob patterns for component files to render
|
|
10
|
-
required:
|
|
9
|
+
description: "Glob patterns for component files to render. Leave empty to auto-detect changed component files."
|
|
10
|
+
required: false
|
|
11
|
+
default: ""
|
|
12
|
+
extensions:
|
|
13
|
+
description: "Comma-separated file extensions to treat as components. Default covers React, Vue, Svelte, Astro, MDX."
|
|
14
|
+
required: false
|
|
15
|
+
default: ".jsx,.tsx,.vue,.svelte,.astro,.mdx"
|
|
16
|
+
exclude:
|
|
17
|
+
description: "Patterns to exclude (comma-separated). Matches against full file path."
|
|
18
|
+
required: false
|
|
19
|
+
default: "*.test.*,*.spec.*,*.stories.*,*.story.*"
|
|
11
20
|
width:
|
|
12
21
|
description: "Viewport width in px. Leave empty for auto-fit (recommended)."
|
|
13
22
|
required: false
|
|
@@ -35,22 +44,42 @@ runs:
|
|
|
35
44
|
|
|
36
45
|
- name: Install frameshot
|
|
37
46
|
shell: bash
|
|
38
|
-
run:
|
|
47
|
+
run: |
|
|
48
|
+
cd "${{ github.action_path }}"
|
|
49
|
+
npm install --prefer-offline frameshot-mcp@latest
|
|
39
50
|
|
|
40
51
|
- name: Install Playwright Chromium
|
|
41
52
|
shell: bash
|
|
42
|
-
run:
|
|
53
|
+
run: |
|
|
54
|
+
cd "${{ github.action_path }}"
|
|
55
|
+
npx playwright install chromium
|
|
56
|
+
|
|
57
|
+
- name: Install project dependencies (for Vite pipeline)
|
|
58
|
+
shell: bash
|
|
59
|
+
run: |
|
|
60
|
+
# Install deps in any subdirectory that has vite as a dependency
|
|
61
|
+
for pkg in $(find "$GITHUB_WORKSPACE" -name "package.json" -not -path "*/node_modules/*" -maxdepth 3); do
|
|
62
|
+
dir=$(dirname "$pkg")
|
|
63
|
+
if grep -q '"vite"' "$pkg" 2>/dev/null; then
|
|
64
|
+
echo "Installing deps in $dir"
|
|
65
|
+
cd "$dir" && npm install --legacy-peer-deps 2>/dev/null || true
|
|
66
|
+
cd "$GITHUB_WORKSPACE"
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
43
69
|
|
|
44
70
|
- name: Render changed components
|
|
45
71
|
id: render
|
|
46
72
|
shell: bash
|
|
47
73
|
env:
|
|
48
74
|
INPUT_PATHS: ${{ inputs.paths }}
|
|
75
|
+
INPUT_EXTENSIONS: ${{ inputs.extensions }}
|
|
76
|
+
INPUT_EXCLUDE: ${{ inputs.exclude }}
|
|
49
77
|
INPUT_WIDTH: ${{ inputs.width }}
|
|
50
78
|
INPUT_HEIGHT: ${{ inputs.height }}
|
|
51
79
|
INPUT_BASE_REF: ${{ github.event.pull_request.base.sha }}
|
|
52
80
|
run: |
|
|
53
|
-
|
|
81
|
+
cd "${{ github.action_path }}"
|
|
82
|
+
node scripts/render-changed.mjs
|
|
54
83
|
|
|
55
84
|
- name: Comment on PR
|
|
56
85
|
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
@@ -81,9 +110,60 @@ runs:
|
|
|
81
110
|
return;
|
|
82
111
|
}
|
|
83
112
|
|
|
84
|
-
|
|
113
|
+
// Store image in repo on a dedicated branch and return raw URL.
|
|
114
|
+
// Uses a special orphan branch "frameshot-previews" as image storage.
|
|
115
|
+
async function uploadImageToGitHub(filePath) {
|
|
116
|
+
const bytes = fs.readFileSync(filePath);
|
|
117
|
+
const base64 = bytes.toString('base64');
|
|
118
|
+
const filename = `pr-${context.issue.number}-${path.basename(filePath)}-${Date.now()}.png`;
|
|
119
|
+
const BRANCH = 'frameshot-previews';
|
|
120
|
+
|
|
121
|
+
// Ensure branch exists
|
|
122
|
+
try {
|
|
123
|
+
await github.rest.repos.getBranch({
|
|
124
|
+
owner: context.repo.owner,
|
|
125
|
+
repo: context.repo.repo,
|
|
126
|
+
branch: BRANCH,
|
|
127
|
+
});
|
|
128
|
+
} catch {
|
|
129
|
+
// Branch doesn't exist — create it from main
|
|
130
|
+
try {
|
|
131
|
+
const { data: ref } = await github.rest.git.getRef({
|
|
132
|
+
owner: context.repo.owner,
|
|
133
|
+
repo: context.repo.repo,
|
|
134
|
+
ref: 'heads/main',
|
|
135
|
+
});
|
|
136
|
+
await github.rest.git.createRef({
|
|
137
|
+
owner: context.repo.owner,
|
|
138
|
+
repo: context.repo.repo,
|
|
139
|
+
ref: `refs/heads/${BRANCH}`,
|
|
140
|
+
sha: ref.object.sha,
|
|
141
|
+
});
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.log('Failed to create branch:', e.message);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Commit the image file
|
|
149
|
+
try {
|
|
150
|
+
await github.rest.repos.createOrUpdateFileContents({
|
|
151
|
+
owner: context.repo.owner,
|
|
152
|
+
repo: context.repo.repo,
|
|
153
|
+
path: filename,
|
|
154
|
+
message: `Add PR #${context.issue.number} preview image`,
|
|
155
|
+
content: base64,
|
|
156
|
+
branch: BRANCH,
|
|
157
|
+
});
|
|
158
|
+
return `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${BRANCH}/${filename}`;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.log('Failed to commit image:', e.message);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
85
164
|
|
|
86
165
|
const hasBeforeAfter = manifest.some(m => m.hasBefore);
|
|
166
|
+
let body = '## 📸 frameshot — Visual Preview\n\n';
|
|
87
167
|
|
|
88
168
|
if (hasBeforeAfter) {
|
|
89
169
|
body += '| Component | Before | After | Diff |\n|---|---|---|---|\n';
|
|
@@ -95,23 +175,32 @@ runs:
|
|
|
95
175
|
const afterFile = path.join(dir, `${entry.name}_after.png`);
|
|
96
176
|
if (!fs.existsSync(afterFile)) continue;
|
|
97
177
|
|
|
98
|
-
const afterData = fs.readFileSync(afterFile, 'base64');
|
|
99
|
-
|
|
100
178
|
if (hasBeforeAfter) {
|
|
101
179
|
const beforeFile = path.join(dir, `${entry.name}_before.png`);
|
|
102
180
|
const diffFile = path.join(dir, `${entry.name}_diff.png`);
|
|
103
181
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
182
|
+
// Upload sequentially to avoid commit conflicts
|
|
183
|
+
const afterUrl = await uploadImageToGitHub(afterFile);
|
|
184
|
+
const beforeUrl = fs.existsSync(beforeFile) ? await uploadImageToGitHub(beforeFile) : null;
|
|
185
|
+
const diffUrl = fs.existsSync(diffFile) ? await uploadImageToGitHub(diffFile) : null;
|
|
186
|
+
|
|
187
|
+
const beforeCell = beforeUrl
|
|
188
|
+
? `<img src="${beforeUrl}" width="240" />`
|
|
189
|
+
: entry.hasBefore ? '*(before unavailable)*' : '*(new)*';
|
|
190
|
+
const afterCell = afterUrl
|
|
191
|
+
? `<img src="${afterUrl}" width="240" />`
|
|
192
|
+
: '*(after unavailable)*';
|
|
193
|
+
const diffCell = diffUrl && entry.diffPercentage > 0
|
|
194
|
+
? `<img src="${diffUrl}" width="240" /><br/>${entry.diffPercentage.toFixed(1)}% changed`
|
|
110
195
|
: entry.hasBefore ? '✅ No change' : '—';
|
|
111
196
|
|
|
112
|
-
body += `| \`${entry.name}\` | ${
|
|
197
|
+
body += `| \`${entry.name}\` | ${beforeCell} | ${afterCell} | ${diffCell} |\n`;
|
|
113
198
|
} else {
|
|
114
|
-
|
|
199
|
+
const afterUrl = await uploadImageToGitHub(afterFile);
|
|
200
|
+
const afterCell = afterUrl
|
|
201
|
+
? `<img src="${afterUrl}" width="360" />`
|
|
202
|
+
: '*(unavailable)*';
|
|
203
|
+
body += `| \`${entry.name}\` | ${afterCell} |\n`;
|
|
115
204
|
}
|
|
116
205
|
}
|
|
117
206
|
|