frameshot-mcp 0.7.0 → 0.9.7
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 +83 -69
- package/action.yml +114 -16
- package/dist/chunk-MEBQ7ZWA.js +1774 -0
- package/dist/chunk-VUYZHZBH.js +157 -0
- package/dist/cli.js +131 -133
- package/dist/index.js +519 -572
- package/dist/renderer.d.ts +17 -7
- package/dist/renderer.js +4 -6
- package/dist/stubs/gatsby.js +20 -0
- package/dist/stubs/next-font.js +9 -0
- package/dist/stubs/next-headers.js +20 -0
- package/dist/stubs/next-image.js +34 -0
- package/dist/stubs/next-link.js +25 -0
- package/dist/stubs/next-navigation.js +17 -0
- package/dist/stubs/next-router.js +19 -0
- package/dist/stubs/nuxt-app.js +37 -0
- package/dist/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/qwik-city.js +33 -0
- package/dist/stubs/react-router.js +67 -0
- package/dist/stubs/server-only.js +2 -0
- package/dist/stubs/solid-router.js +27 -0
- package/dist/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/gatsby.js +20 -0
- package/dist/stubs/stubs/next-font.js +9 -0
- package/dist/stubs/stubs/next-headers.js +20 -0
- package/dist/stubs/stubs/next-image.js +34 -0
- package/dist/stubs/stubs/next-link.js +25 -0
- package/dist/stubs/stubs/next-navigation.js +17 -0
- package/dist/stubs/stubs/next-router.js +19 -0
- package/dist/stubs/stubs/nuxt-app.js +37 -0
- package/dist/stubs/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/stubs/qwik-city.js +33 -0
- package/dist/stubs/stubs/react-router.js +67 -0
- package/dist/stubs/stubs/server-only.js +2 -0
- package/dist/stubs/stubs/solid-router.js +27 -0
- package/dist/stubs/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/stubs/vike.js +11 -0
- package/dist/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/vike.js +11 -0
- package/package.json +10 -4
- package/scripts/render-changed.mjs +140 -18
- package/dist/chunk-3LVWVDET.js +0 -849
- package/dist/chunk-47YJG5HR.js +0 -690
- package/dist/chunk-67JZQ6OI.js +0 -819
- package/dist/chunk-AZCGKIMU.js +0 -850
- package/dist/chunk-B3CLIGWU.js +0 -786
- package/dist/chunk-C6QSY4WR.js +0 -811
- package/dist/chunk-DX54PJKO.js +0 -603
- package/dist/chunk-EMCJGIMY.js +0 -984
- package/dist/chunk-FQNWGR62.js +0 -849
- package/dist/chunk-FTYTZW6D.js +0 -203
- package/dist/chunk-JGVKYXY2.js +0 -857
- package/dist/chunk-JYPEA4P2.js +0 -846
- package/dist/chunk-KHK35HDD.js +0 -855
- package/dist/chunk-Q7A3DLED.js +0 -848
- package/dist/chunk-SIA6XEHM.js +0 -811
- package/dist/chunk-ST35YDI6.js +0 -834
- package/dist/chunk-T5OBJK35.js +0 -855
- package/dist/chunk-U3GHS7KO.js +0 -837
- package/dist/chunk-WS2ASCD6.js +0 -683
- package/dist/chunk-WZMHVSUA.js +0 -847
- package/dist/chunk-ZZST6K7Y.js +0 -987
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/frameshot-mcp) [](https://www.npmjs.com/package/frameshot-mcp) [](https://github.com/kamegoro/frameshot) [](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
**Give your AI agent eyes.** Render
|
|
5
|
+
**Give your AI agent eyes.** Render components with real Vite imports, Tailwind, and CSS Modules — right in your terminal or AI chat. No story files required.
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="docs/demo.gif" alt="frameshot demo" width="720" />
|
|
@@ -32,70 +32,118 @@ claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## Watch mode — live AI feedback loop
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
The AI edits a file, saves it, and immediately sees how it looks — without you calling anything.
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"Show me all components in src/components/ as screenshots"
|
|
40
|
+
watch_start({ patterns: ["src/components/Button.tsx"] })
|
|
41
|
+
→ renders on every save → call watch_get_latest("watch-1") to see it
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
The agent fixes → saves → sees → fixes again. You just watch.
|
|
46
45
|
|
|
47
46
|
---
|
|
48
47
|
|
|
49
|
-
##
|
|
48
|
+
## GitHub Action — free Chromatic alternative
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
Two lines. Auto-detects changed components. Posts before/after/diff screenshots directly in the PR comment.
|
|
52
51
|
|
|
53
|
-
```
|
|
54
|
-
|
|
52
|
+
```yaml
|
|
53
|
+
- uses: actions/checkout@v7
|
|
54
|
+
with:
|
|
55
|
+
fetch-depth: 0
|
|
56
|
+
- uses: kamegoro/frameshot@v0.8.0
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
<p align="center">
|
|
58
|
-
<img src="docs/
|
|
60
|
+
<img src="docs/diff-before.png" alt="Before" width="210" />
|
|
61
|
+
|
|
62
|
+
<img src="docs/diff-after.png" alt="After" width="210" />
|
|
63
|
+
|
|
64
|
+
<img src="docs/diff-result.png" alt="Diff" width="210" />
|
|
59
65
|
</p>
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
Changed `.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`, `.mdx` files detected automatically. No Storybook. No signup. Free forever.
|
|
68
|
+
|
|
69
|
+
<details>
|
|
70
|
+
<summary>Options</summary>
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- uses: kamegoro/frameshot@v0.8.0
|
|
74
|
+
with:
|
|
75
|
+
paths: "./src/components/*.tsx" # default: auto-detect
|
|
76
|
+
extensions: ".jsx,.tsx,.vue" # default: .jsx,.tsx,.vue,.svelte,.astro,.mdx
|
|
77
|
+
exclude: "*.test.*,*.spec.*" # default: *.test.*,*.spec.*,*.stories.*
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
</details>
|
|
62
81
|
|
|
63
82
|
---
|
|
64
83
|
|
|
65
|
-
##
|
|
84
|
+
## Render any component
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
Point to a file. Get a screenshot. Full Vite pipeline.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
render_file("src/components/Dashboard.tsx")
|
|
90
|
+
```
|
|
68
91
|
|
|
69
92
|
<p align="center">
|
|
70
|
-
<img src="docs/
|
|
71
|
-
|
|
72
|
-
<img src="docs/diff-after.png" alt="After" width="210" />
|
|
73
|
-
|
|
74
|
-
<img src="docs/diff-result.png" alt="Diff" width="210" />
|
|
93
|
+
<img src="docs/example-output.png" alt="Rendered PricingCard with full Tailwind styling" width="280" />
|
|
75
94
|
</p>
|
|
76
95
|
|
|
96
|
+
Resolves your project's real imports, Tailwind config, CSS Modules, and path aliases — not a CDN polyfill.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Using with Claude
|
|
101
|
+
|
|
102
|
+
After installing, just describe what you want:
|
|
103
|
+
|
|
77
104
|
```
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
"Render src/components/PricingCard.tsx — does it look right?"
|
|
106
|
+
"Refactor the layout to CSS grid and show me before/after"
|
|
107
|
+
"Watch Button.tsx while I edit — tell me when something looks off"
|
|
80
108
|
```
|
|
81
109
|
|
|
110
|
+
Claude calls `render_file`, `diff_component`, `watch_start`, or any other tool automatically.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Try it without an AI client
|
|
115
|
+
|
|
116
|
+
Works standalone on any React / Vue / Svelte project:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx frameshot render src/components/Button.tsx
|
|
120
|
+
npx frameshot diff src/components/Header.tsx
|
|
121
|
+
npx frameshot catalog src/components/ --recursive
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
|
|
125
|
+
|
|
126
|
+
> First run installs Playwright's Chromium (~150MB). Subsequent renders are fast.
|
|
127
|
+
|
|
82
128
|
---
|
|
83
129
|
|
|
84
130
|
## Tools
|
|
85
131
|
|
|
86
132
|
| Tool | What it does |
|
|
87
133
|
|------|-------------|
|
|
88
|
-
| `render_file` |
|
|
89
|
-
| `
|
|
134
|
+
| `render_file` | Render a file via Vite — real imports, Tailwind, CSS Modules |
|
|
135
|
+
| `diff_component` | Pixel diff before/after — % changed, highlighted pixels |
|
|
136
|
+
| `watch_start` | Watch files — auto-render on every save |
|
|
137
|
+
| `watch_get_latest` | Get the latest render from a watch session |
|
|
138
|
+
| `render_catalog` | Render every component in a directory at once |
|
|
90
139
|
| `screenshot_url` | Screenshot any URL — localhost, staging, prod |
|
|
91
|
-
| `diff_component` | Pixel diff before/after with % changed |
|
|
92
140
|
| `render_responsive` | Mobile + tablet + desktop in one call |
|
|
93
141
|
| `render_theme` | Light + dark mode side by side |
|
|
94
|
-
| `audit_a11y` | axe-core accessibility audit (WCAG) |
|
|
95
|
-
| `
|
|
142
|
+
| `audit_a11y` | axe-core accessibility audit (WCAG violations) |
|
|
143
|
+
| `render_component` | Render a self-contained code snippet |
|
|
96
144
|
|
|
97
145
|
<details>
|
|
98
|
-
<summary>All
|
|
146
|
+
<summary>All 22 tools</summary>
|
|
99
147
|
|
|
100
148
|
| Tool | What it does |
|
|
101
149
|
|------|-------------|
|
|
@@ -117,52 +165,17 @@ diff_component("src/components/MetricCard.tsx")
|
|
|
117
165
|
| `snapshot_save` | Save a render as named baseline |
|
|
118
166
|
| `snapshot_check` | Compare current render against saved baseline |
|
|
119
167
|
| `snapshot_list` | List all saved snapshots |
|
|
168
|
+
| `watch_start` | Start watching files — renders on every save |
|
|
169
|
+
| `watch_stop` | Stop a watch session |
|
|
170
|
+
| `watch_get_latest` | Get the latest rendered screenshot from a session |
|
|
171
|
+
| `watch_list` | List active watch sessions |
|
|
120
172
|
|
|
121
173
|
</details>
|
|
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
|
-
## GitHub Action
|
|
141
|
-
|
|
142
|
-
Free Chromatic alternative. Auto-posts before/after screenshots with diff on every PR:
|
|
143
|
-
|
|
144
|
-
```yaml
|
|
145
|
-
# .github/workflows/visual-preview.yml
|
|
146
|
-
name: Visual Preview
|
|
147
|
-
on: [pull_request]
|
|
148
|
-
jobs:
|
|
149
|
-
preview:
|
|
150
|
-
runs-on: ubuntu-latest
|
|
151
|
-
steps:
|
|
152
|
-
- uses: actions/checkout@v4
|
|
153
|
-
with:
|
|
154
|
-
fetch-depth: 0
|
|
155
|
-
- uses: kamegoro/frameshot@main
|
|
156
|
-
with:
|
|
157
|
-
paths: "./src/components/*.tsx"
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
177
|
## Performance
|
|
163
178
|
|
|
164
|
-
Typical measured times (varies by machine and project size):
|
|
165
|
-
|
|
166
179
|
| Scenario | Time |
|
|
167
180
|
|----------|------|
|
|
168
181
|
| Warm render (Vite server cached) | **~200–500ms** |
|
|
@@ -181,9 +194,10 @@ Vite server is cached per project root. Browser pool stays warm between MCP call
|
|
|
181
194
|
| Setup | Stories + config | SaaS signup | Browser install | **`npx` — done** |
|
|
182
195
|
| Speed | Dev server startup | Cloud round-trip | 2–5s | **~200ms warm** |
|
|
183
196
|
| Cost | Free (labor cost) | $149–800+/mo | Free | **Free forever** |
|
|
184
|
-
|
|
|
185
|
-
| Real imports | Via Storybook | Via Storybook | No | **Via Vite** |
|
|
197
|
+
| Story files needed | Yes | Yes | No | **No** |
|
|
198
|
+
| Real imports resolved | Via Storybook | Via Storybook | No | **Via Vite** |
|
|
186
199
|
| Works offline | Yes | No | Yes | **Yes** |
|
|
200
|
+
| Watch mode | No | No | No | **Yes** |
|
|
187
201
|
| AI-native (MCP) | No | No | Full-page only | **Component-level** |
|
|
188
202
|
|
|
189
203
|
---
|
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,51 @@ runs:
|
|
|
35
44
|
|
|
36
45
|
- name: Install frameshot
|
|
37
46
|
shell: bash
|
|
38
|
-
run:
|
|
47
|
+
run: |
|
|
48
|
+
cd "${{ github.action_path }}"
|
|
49
|
+
# Install frameshot from the action's own checked-out source (this repo)
|
|
50
|
+
# so users get exactly the version pinned by their `uses:` tag,
|
|
51
|
+
# regardless of npm publish state.
|
|
52
|
+
if [ -f package.json ] && [ -d src ]; then
|
|
53
|
+
npm install --prefer-offline --no-audit --no-fund
|
|
54
|
+
npm run build
|
|
55
|
+
else
|
|
56
|
+
npm install --prefer-offline --no-audit --no-fund frameshot-mcp@latest
|
|
57
|
+
fi
|
|
39
58
|
|
|
40
59
|
- name: Install Playwright Chromium
|
|
41
60
|
shell: bash
|
|
42
|
-
run:
|
|
61
|
+
run: |
|
|
62
|
+
cd "${{ github.action_path }}"
|
|
63
|
+
npx playwright install chromium
|
|
64
|
+
|
|
65
|
+
- name: Install project dependencies (for Vite pipeline)
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
# Install deps in any directory that has a package.json with a build framework
|
|
69
|
+
# Supports Vite, Next.js, Nuxt, SvelteKit, Astro, etc.
|
|
70
|
+
for pkg in $(find "$GITHUB_WORKSPACE" -name "package.json" -not -path "*/node_modules/*" -maxdepth 3); do
|
|
71
|
+
dir=$(dirname "$pkg")
|
|
72
|
+
if grep -qE '"vite"|"next"|"nuxt"|"@sveltejs/kit"|"astro"' "$pkg" 2>/dev/null; then
|
|
73
|
+
echo "Installing deps in $dir"
|
|
74
|
+
cd "$dir" && npm install --legacy-peer-deps 2>/dev/null || true
|
|
75
|
+
cd "$GITHUB_WORKSPACE"
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
43
78
|
|
|
44
79
|
- name: Render changed components
|
|
45
80
|
id: render
|
|
46
81
|
shell: bash
|
|
47
82
|
env:
|
|
48
83
|
INPUT_PATHS: ${{ inputs.paths }}
|
|
84
|
+
INPUT_EXTENSIONS: ${{ inputs.extensions }}
|
|
85
|
+
INPUT_EXCLUDE: ${{ inputs.exclude }}
|
|
49
86
|
INPUT_WIDTH: ${{ inputs.width }}
|
|
50
87
|
INPUT_HEIGHT: ${{ inputs.height }}
|
|
51
88
|
INPUT_BASE_REF: ${{ github.event.pull_request.base.sha }}
|
|
52
89
|
run: |
|
|
53
|
-
|
|
90
|
+
cd "${{ github.action_path }}"
|
|
91
|
+
node scripts/render-changed.mjs
|
|
54
92
|
|
|
55
93
|
- name: Comment on PR
|
|
56
94
|
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
@@ -81,9 +119,60 @@ runs:
|
|
|
81
119
|
return;
|
|
82
120
|
}
|
|
83
121
|
|
|
84
|
-
|
|
122
|
+
// Store image in repo on a dedicated branch and return raw URL.
|
|
123
|
+
// Uses a special orphan branch "frameshot-previews" as image storage.
|
|
124
|
+
async function uploadImageToGitHub(filePath) {
|
|
125
|
+
const bytes = fs.readFileSync(filePath);
|
|
126
|
+
const base64 = bytes.toString('base64');
|
|
127
|
+
const filename = `pr-${context.issue.number}-${path.basename(filePath)}-${Date.now()}.png`;
|
|
128
|
+
const BRANCH = 'frameshot-previews';
|
|
129
|
+
|
|
130
|
+
// Ensure branch exists
|
|
131
|
+
try {
|
|
132
|
+
await github.rest.repos.getBranch({
|
|
133
|
+
owner: context.repo.owner,
|
|
134
|
+
repo: context.repo.repo,
|
|
135
|
+
branch: BRANCH,
|
|
136
|
+
});
|
|
137
|
+
} catch {
|
|
138
|
+
// Branch doesn't exist — create it from main
|
|
139
|
+
try {
|
|
140
|
+
const { data: ref } = await github.rest.git.getRef({
|
|
141
|
+
owner: context.repo.owner,
|
|
142
|
+
repo: context.repo.repo,
|
|
143
|
+
ref: 'heads/main',
|
|
144
|
+
});
|
|
145
|
+
await github.rest.git.createRef({
|
|
146
|
+
owner: context.repo.owner,
|
|
147
|
+
repo: context.repo.repo,
|
|
148
|
+
ref: `refs/heads/${BRANCH}`,
|
|
149
|
+
sha: ref.object.sha,
|
|
150
|
+
});
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.log('Failed to create branch:', e.message);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Commit the image file
|
|
158
|
+
try {
|
|
159
|
+
await github.rest.repos.createOrUpdateFileContents({
|
|
160
|
+
owner: context.repo.owner,
|
|
161
|
+
repo: context.repo.repo,
|
|
162
|
+
path: filename,
|
|
163
|
+
message: `Add PR #${context.issue.number} preview image`,
|
|
164
|
+
content: base64,
|
|
165
|
+
branch: BRANCH,
|
|
166
|
+
});
|
|
167
|
+
return `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${BRANCH}/${filename}`;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log('Failed to commit image:', e.message);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
85
173
|
|
|
86
174
|
const hasBeforeAfter = manifest.some(m => m.hasBefore);
|
|
175
|
+
let body = '## 📸 frameshot — Visual Preview\n\n';
|
|
87
176
|
|
|
88
177
|
if (hasBeforeAfter) {
|
|
89
178
|
body += '| Component | Before | After | Diff |\n|---|---|---|---|\n';
|
|
@@ -95,23 +184,32 @@ runs:
|
|
|
95
184
|
const afterFile = path.join(dir, `${entry.name}_after.png`);
|
|
96
185
|
if (!fs.existsSync(afterFile)) continue;
|
|
97
186
|
|
|
98
|
-
const afterData = fs.readFileSync(afterFile, 'base64');
|
|
99
|
-
|
|
100
187
|
if (hasBeforeAfter) {
|
|
101
188
|
const beforeFile = path.join(dir, `${entry.name}_before.png`);
|
|
102
189
|
const diffFile = path.join(dir, `${entry.name}_diff.png`);
|
|
103
190
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
191
|
+
// Upload sequentially to avoid commit conflicts
|
|
192
|
+
const afterUrl = await uploadImageToGitHub(afterFile);
|
|
193
|
+
const beforeUrl = fs.existsSync(beforeFile) ? await uploadImageToGitHub(beforeFile) : null;
|
|
194
|
+
const diffUrl = fs.existsSync(diffFile) ? await uploadImageToGitHub(diffFile) : null;
|
|
195
|
+
|
|
196
|
+
const beforeCell = beforeUrl
|
|
197
|
+
? `<img src="${beforeUrl}" width="240" />`
|
|
198
|
+
: entry.hasBefore ? '*(before unavailable)*' : '*(new)*';
|
|
199
|
+
const afterCell = afterUrl
|
|
200
|
+
? `<img src="${afterUrl}" width="240" />`
|
|
201
|
+
: '*(after unavailable)*';
|
|
202
|
+
const diffCell = diffUrl && entry.diffPercentage > 0
|
|
203
|
+
? `<img src="${diffUrl}" width="240" /><br/>${entry.diffPercentage.toFixed(1)}% changed`
|
|
110
204
|
: entry.hasBefore ? '✅ No change' : '—';
|
|
111
205
|
|
|
112
|
-
body += `| \`${entry.name}\` | ${
|
|
206
|
+
body += `| \`${entry.label ?? entry.name}\` | ${beforeCell} | ${afterCell} | ${diffCell} |\n`;
|
|
113
207
|
} else {
|
|
114
|
-
|
|
208
|
+
const afterUrl = await uploadImageToGitHub(afterFile);
|
|
209
|
+
const afterCell = afterUrl
|
|
210
|
+
? `<img src="${afterUrl}" width="360" />`
|
|
211
|
+
: '*(unavailable)*';
|
|
212
|
+
body += `| \`${entry.label ?? entry.name}\` | ${afterCell} |\n`;
|
|
115
213
|
}
|
|
116
214
|
}
|
|
117
215
|
|