@juangadm/pre-post 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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +388 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +82 -0
- package/dist/browser.js.map +1 -0
- package/dist/capture.d.ts +9 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +52 -0
- package/dist/capture.js.map +1 -0
- package/dist/clipboard.d.ts +9 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +26 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/filename.d.ts +24 -0
- package/dist/filename.d.ts.map +1 -0
- package/dist/filename.js +76 -0
- package/dist/filename.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/generic.d.ts +11 -0
- package/dist/routes/generic.d.ts.map +1 -0
- package/dist/routes/generic.js +106 -0
- package/dist/routes/generic.js.map +1 -0
- package/dist/routes/nextjs.d.ts +14 -0
- package/dist/routes/nextjs.d.ts.map +1 -0
- package/dist/routes/nextjs.js +196 -0
- package/dist/routes/nextjs.js.map +1 -0
- package/dist/routes.d.ts +21 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +174 -0
- package/dist/routes.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/upload.d.ts +35 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +116 -0
- package/dist/upload.js.map +1 -0
- package/dist/viewport.d.ts +3 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +11 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +66 -0
- package/skill/SKILL.md +246 -0
- package/skill/scripts/adapters/0x0st.sh +36 -0
- package/skill/scripts/adapters/blob.sh +55 -0
- package/skill/scripts/adapters/gist.sh +59 -0
- package/skill/scripts/adapters/git-native.sh +75 -0
- package/skill/scripts/upload-and-copy.sh +183 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Gabriel Delgado
|
|
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,227 @@
|
|
|
1
|
+
# pre-post
|
|
2
|
+
|
|
3
|
+
Visual diff tool that captures pre/post screenshots for PRs. Use it as a Claude Code skill for automatic visual documentation, or run it directly from the CLI.
|
|
4
|
+
|
|
5
|
+
> Forked from [before-and-after](https://github.com/vercel-labs/before-and-after) by [James Clements](https://github.com/jamesclement) / Vercel Labs.
|
|
6
|
+
|
|
7
|
+
## TODO
|
|
8
|
+
|
|
9
|
+
- [ ] Publish to npm so `npm i -g pre-post` and `npx pre-post` work out of the box
|
|
10
|
+
|
|
11
|
+
## What Changed from the Original
|
|
12
|
+
|
|
13
|
+
| Area | before-and-after | pre-post |
|
|
14
|
+
|------|-----------------|----------|
|
|
15
|
+
| Browser engine | `agent-browser` (Vercel-proprietary) | **Playwright** (direct dependency) |
|
|
16
|
+
| Screenshot quality | 1x | **2x retina** (`deviceScaleFactor: 2`) |
|
|
17
|
+
| Route detection | Manual | **Automatic** from `git diff` (Next.js App/Pages Router, Remix, SvelteKit) |
|
|
18
|
+
| Responsive capture | Single viewport | **Desktop + mobile** per route (`--responsive`) |
|
|
19
|
+
| CLI subcommands | URL pairs only | `detect`, `compare`, `run` subcommands |
|
|
20
|
+
| Skill orchestration | Basic capture | Full workflow: route detection, Claude refinement, user approval, PR posting |
|
|
21
|
+
| Font/animation handling | None | Waits for `document.fonts.ready`, disables CSS animations |
|
|
22
|
+
|
|
23
|
+
## How It Works
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
flowchart TD
|
|
27
|
+
A[pre-post] --> B{Mode?}
|
|
28
|
+
|
|
29
|
+
B -->|Manual| C["pre-post url1 url2"]
|
|
30
|
+
B -->|Automatic| D["pre-post run"]
|
|
31
|
+
B -->|Claude Code Skill| E["/pre-post"]
|
|
32
|
+
|
|
33
|
+
D --> F["git diff --name-only"]
|
|
34
|
+
F --> G{Detect framework}
|
|
35
|
+
G -->|Next.js App Router| H[Map files → routes]
|
|
36
|
+
G -->|Next.js Pages Router| H
|
|
37
|
+
G -->|Remix / SvelteKit| H
|
|
38
|
+
G -->|Unknown| I["Default to /"]
|
|
39
|
+
H --> J[Route list with confidence]
|
|
40
|
+
|
|
41
|
+
E --> F
|
|
42
|
+
E --> K[Claude refines routes]
|
|
43
|
+
K --> J
|
|
44
|
+
|
|
45
|
+
C --> L[Playwright]
|
|
46
|
+
J --> L
|
|
47
|
+
|
|
48
|
+
L --> M{System Chrome?}
|
|
49
|
+
M -->|Found| N[Use system Chrome]
|
|
50
|
+
M -->|Not found| O[Use bundled Chromium]
|
|
51
|
+
N --> P[Capture screenshots]
|
|
52
|
+
O --> P
|
|
53
|
+
|
|
54
|
+
P --> Q[Disable animations]
|
|
55
|
+
Q --> R[Wait for fonts]
|
|
56
|
+
R --> S[Take 2x retina screenshot]
|
|
57
|
+
|
|
58
|
+
S --> T{Output}
|
|
59
|
+
T -->|Default| U["Save to ~/Downloads"]
|
|
60
|
+
T -->|--markdown| V[Commit to PR branch]
|
|
61
|
+
V --> W[Markdown table for PR]
|
|
62
|
+
|
|
63
|
+
style A fill:#4f46e5,color:#fff
|
|
64
|
+
style L fill:#2563eb,color:#fff
|
|
65
|
+
style S fill:#059669,color:#fff
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Prerequisites
|
|
69
|
+
|
|
70
|
+
- **Node.js** 18+
|
|
71
|
+
- **Chromium** (auto-installed on first run via Playwright)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Install Chromium browser for Playwright (one-time setup)
|
|
75
|
+
npx playwright install chromium
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm i -g pre-post
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Basic Use
|
|
85
|
+
|
|
86
|
+
### As a Claude Code Skill (recommended)
|
|
87
|
+
|
|
88
|
+
After making visual UI changes, say `/pre-post` or "take pre and post screenshots". Claude will:
|
|
89
|
+
|
|
90
|
+
1. Detect affected routes from your git diff
|
|
91
|
+
2. Propose routes for your approval
|
|
92
|
+
3. Capture desktop + mobile screenshots (production vs localhost)
|
|
93
|
+
4. Show you the screenshots for approval
|
|
94
|
+
5. Upload and append markdown to your PR
|
|
95
|
+
|
|
96
|
+
### From the CLI
|
|
97
|
+
|
|
98
|
+
Capture any two URLs:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pre-post site.com localhost:3000
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use existing images:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pre-post before.png after.png
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## CLI Subcommands
|
|
111
|
+
|
|
112
|
+
### `detect` -- Route Detection
|
|
113
|
+
|
|
114
|
+
Detect affected routes from git changes:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pre-post detect # Auto-detect framework
|
|
118
|
+
pre-post detect --framework nextjs # Force framework
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Outputs JSON with route paths, confidence levels, and source files.
|
|
122
|
+
|
|
123
|
+
### `compare` -- URL Comparison
|
|
124
|
+
|
|
125
|
+
Compare pre/post states across routes:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pre-post compare --before-base https://prod.com --after-base http://localhost:3000
|
|
129
|
+
pre-post compare --before-base URL --after-base URL --routes /dashboard,/settings
|
|
130
|
+
pre-post compare --before-base URL --after-base URL --responsive # Desktop + mobile
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `run` -- Full Auto
|
|
134
|
+
|
|
135
|
+
Combines `detect` + `compare`:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pre-post run --before-base https://prod.com --after-base http://localhost:3000
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Options
|
|
142
|
+
|
|
143
|
+
Capture a specific element using a CSS selector:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pre-post url1 url2 ".hero"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use different selectors for pre and post:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pre-post url1 url2 ".old" ".new"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Capture at mobile (375x812), tablet (768x1024), or custom viewport:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pre-post url1 url2 --mobile
|
|
159
|
+
pre-post url1 url2 --size 1920x1080
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Capture the entire scrollable page:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pre-post url1 url2 --full
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Output a markdown table for PR descriptions:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pre-post url1 url2 --markdown
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Save to a custom location:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pre-post url1 url2 --output ./screenshots
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Override the default upload method:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pre-post url1 url2 --upload-url https://uploads.example.com
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
By default, screenshots are committed directly to the PR branch (under `.pre-post/`) and served via `raw.githubusercontent.com`. Use `--upload-url` to override with a custom storage service. Screenshots auto-append to the PR body, newest on top.
|
|
187
|
+
|
|
188
|
+
## Route Detection
|
|
189
|
+
|
|
190
|
+
Pre-post automatically maps `git diff --name-only` to affected UI routes:
|
|
191
|
+
|
|
192
|
+
| Changed File | Detected Route | Confidence |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `app/page.tsx` | `/` | high |
|
|
195
|
+
| `app/dashboard/page.tsx` | `/dashboard` | high |
|
|
196
|
+
| `app/(marketing)/about/page.tsx` | `/about` (strips route groups) | high |
|
|
197
|
+
| `app/blog/[slug]/page.tsx` | `/blog/[slug]` | high |
|
|
198
|
+
| `app/dashboard/layout.tsx` | `/dashboard` | medium |
|
|
199
|
+
| `app/dashboard/components/Chart.tsx` | `/dashboard` | medium |
|
|
200
|
+
| `globals.css`, `tailwind.config.ts` | `/` | low |
|
|
201
|
+
| `app/api/*`, `middleware.ts` | Skipped (no visual) | -- |
|
|
202
|
+
|
|
203
|
+
Supports:
|
|
204
|
+
- **Next.js App Router** (route groups, dynamic segments, parallel routes, catch-all)
|
|
205
|
+
- **Next.js Pages Router** (`pages/`, `_app.tsx`, `_document.tsx`)
|
|
206
|
+
- **Remix** (`routes/` directory)
|
|
207
|
+
- **SvelteKit** (`src/routes/+page.svelte`)
|
|
208
|
+
- **Generic fallback** (defaults to `/`)
|
|
209
|
+
|
|
210
|
+
## Add Skill
|
|
211
|
+
|
|
212
|
+
Install as a Claude Code skill:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npx skills add juangadm/pre-post
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The skill uses `gh` to detect the associated PR and Playwright for screenshots.
|
|
219
|
+
|
|
220
|
+
## Credits
|
|
221
|
+
|
|
222
|
+
- Original [before-and-after](https://github.com/vercel-labs/before-and-after) by [James Clements](https://github.com/jamesclement) at [Vercel Labs](https://github.com/vercel-labs)
|
|
223
|
+
- Browser automation powered by [Playwright](https://playwright.dev/)
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/bin/cli.ts"],"names":[],"mappings":""}
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { BeforeAndAfter, generateFilename } from '../index.js';
|
|
6
|
+
import { VIEWPORT_PRESETS } from '../types.js';
|
|
7
|
+
import { closeBrowser } from '../browser.js';
|
|
8
|
+
import { captureScreenshot } from '../capture.js';
|
|
9
|
+
import { uploadBeforeAfter } from '../upload.js';
|
|
10
|
+
import { copyToClipboard } from '../clipboard.js';
|
|
11
|
+
import { detectRoutes, getChangedFiles, detectFramework } from '../routes.js';
|
|
12
|
+
// Determine subcommand
|
|
13
|
+
const subcommand = process.argv[2];
|
|
14
|
+
const isSubcommand = ['detect', 'compare', 'run'].includes(subcommand);
|
|
15
|
+
if (isSubcommand) {
|
|
16
|
+
// Remove subcommand from argv for parseArgs
|
|
17
|
+
process.argv.splice(2, 1);
|
|
18
|
+
}
|
|
19
|
+
const { values, positionals } = parseArgs({
|
|
20
|
+
allowPositionals: true,
|
|
21
|
+
options: {
|
|
22
|
+
help: { type: 'boolean', short: 'h' },
|
|
23
|
+
// Viewport presets (default: desktop)
|
|
24
|
+
mobile: { type: 'boolean', short: 'm' },
|
|
25
|
+
tablet: { type: 'boolean', short: 't' },
|
|
26
|
+
size: { type: 'string' }, // WxH custom size
|
|
27
|
+
// Capture options
|
|
28
|
+
full: { type: 'boolean', short: 'f' },
|
|
29
|
+
selector: { type: 'string', short: 's' },
|
|
30
|
+
// Output options
|
|
31
|
+
output: { type: 'string', short: 'o' },
|
|
32
|
+
markdown: { type: 'boolean' },
|
|
33
|
+
'upload-url': { type: 'string' },
|
|
34
|
+
// New options
|
|
35
|
+
responsive: { type: 'boolean', short: 'r' },
|
|
36
|
+
routes: { type: 'string' },
|
|
37
|
+
'max-routes': { type: 'string' },
|
|
38
|
+
framework: { type: 'string' },
|
|
39
|
+
'before-base': { type: 'string' },
|
|
40
|
+
'after-base': { type: 'string' },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
function printHelp() {
|
|
44
|
+
console.log(`
|
|
45
|
+
pre-post — Visual diff tool for PRs
|
|
46
|
+
|
|
47
|
+
USAGE:
|
|
48
|
+
pre-post <before> <after> [selector] [selector2]
|
|
49
|
+
pre-post detect [options]
|
|
50
|
+
pre-post compare --before-base <url> --after-base <url> [options]
|
|
51
|
+
pre-post run --before-base <url> --after-base <url> [options]
|
|
52
|
+
|
|
53
|
+
SUBCOMMANDS:
|
|
54
|
+
detect Detect affected routes from git diff (JSON output)
|
|
55
|
+
compare Compare before/after URLs with screenshots
|
|
56
|
+
run Auto-detect routes + compare (detect + compare)
|
|
57
|
+
|
|
58
|
+
Arguments can be URLs or image files (auto-detected).
|
|
59
|
+
Selectors are optional - use one for both, or two for different selectors.
|
|
60
|
+
|
|
61
|
+
VIEWPORT OPTIONS:
|
|
62
|
+
(default) Desktop viewport (1280x800)
|
|
63
|
+
-m, --mobile Mobile viewport (375x812)
|
|
64
|
+
-t, --tablet Tablet viewport (768x1024)
|
|
65
|
+
--size <WxH> Custom viewport (e.g., 1920x1080)
|
|
66
|
+
|
|
67
|
+
CAPTURE OPTIONS:
|
|
68
|
+
-f, --full Capture full scrollable page
|
|
69
|
+
-s, --selector <css> Scroll element into view before capture
|
|
70
|
+
-r, --responsive Capture at desktop + mobile viewports
|
|
71
|
+
|
|
72
|
+
ROUTE DETECTION OPTIONS:
|
|
73
|
+
--routes <paths> Explicit route list (comma-separated)
|
|
74
|
+
--max-routes <n> Max routes to detect (default: 5)
|
|
75
|
+
--framework <name> Force framework (nextjs-app, nextjs-pages, generic)
|
|
76
|
+
|
|
77
|
+
COMPARE OPTIONS:
|
|
78
|
+
--before-base <url> Base URL for "before" state (production)
|
|
79
|
+
--after-base <url> Base URL for "after" state (localhost)
|
|
80
|
+
|
|
81
|
+
OUTPUT OPTIONS:
|
|
82
|
+
-o, --output <dir> Output directory (default: ~/Downloads)
|
|
83
|
+
--markdown Upload images & output markdown table
|
|
84
|
+
--upload-url <url> Upload endpoint (overrides git-native default)
|
|
85
|
+
Auto-detects: 0x0.st, blob.vercel, generic PUT
|
|
86
|
+
|
|
87
|
+
OTHER OPTIONS:
|
|
88
|
+
-h, --help Show this help
|
|
89
|
+
|
|
90
|
+
EXAMPLES:
|
|
91
|
+
# Compare two URLs (protocol optional)
|
|
92
|
+
pre-post google.com facebook.com
|
|
93
|
+
pre-post https://old.example.com https://new.example.com
|
|
94
|
+
|
|
95
|
+
# Detect routes from git diff
|
|
96
|
+
pre-post detect
|
|
97
|
+
pre-post detect --framework nextjs-app
|
|
98
|
+
|
|
99
|
+
# Compare with auto-detected routes
|
|
100
|
+
pre-post run --before-base https://prod.com --after-base http://localhost:3000
|
|
101
|
+
|
|
102
|
+
# Compare specific routes
|
|
103
|
+
pre-post compare --before-base https://prod.com --after-base http://localhost:3000 --routes /dashboard,/settings
|
|
104
|
+
|
|
105
|
+
# Responsive capture (desktop + mobile)
|
|
106
|
+
pre-post compare --before-base url1 --after-base url2 --responsive
|
|
107
|
+
|
|
108
|
+
# Use existing images (auto-detected)
|
|
109
|
+
pre-post before.png after.png --markdown
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff'];
|
|
113
|
+
function isImageFile(filePath) {
|
|
114
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
115
|
+
return IMAGE_EXTENSIONS.includes(ext) && fs.existsSync(filePath);
|
|
116
|
+
}
|
|
117
|
+
function normalizeUrl(url) {
|
|
118
|
+
if (/^(https?|file):\/\//i.test(url)) {
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
if (/^(localhost|127\.0\.0\.1)(:|\/|$)/i.test(url)) {
|
|
122
|
+
return `http://${url}`;
|
|
123
|
+
}
|
|
124
|
+
return `https://${url}`;
|
|
125
|
+
}
|
|
126
|
+
function resolveViewportFlag() {
|
|
127
|
+
if (values.mobile)
|
|
128
|
+
return 'mobile';
|
|
129
|
+
if (values.tablet)
|
|
130
|
+
return 'tablet';
|
|
131
|
+
if (values.size) {
|
|
132
|
+
const match = values.size.match(/^(\d+)x(\d+)$/);
|
|
133
|
+
if (match) {
|
|
134
|
+
return { width: parseInt(match[1]), height: parseInt(match[2]) };
|
|
135
|
+
}
|
|
136
|
+
console.error(`Invalid size: ${values.size}. Use WxH format (e.g., 1920x1080).`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
return 'desktop';
|
|
140
|
+
}
|
|
141
|
+
// ============================================================
|
|
142
|
+
// Subcommand: detect
|
|
143
|
+
// ============================================================
|
|
144
|
+
async function runDetect() {
|
|
145
|
+
const framework = values.framework;
|
|
146
|
+
const maxRoutes = values['max-routes'] ? parseInt(values['max-routes']) : undefined;
|
|
147
|
+
const changedFiles = getChangedFiles();
|
|
148
|
+
if (changedFiles.length === 0) {
|
|
149
|
+
console.log(JSON.stringify({ routes: [], message: 'No changed files detected' }, null, 2));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const detectedFramework = framework || detectFramework();
|
|
153
|
+
const routes = detectRoutes(changedFiles, { framework, maxRoutes });
|
|
154
|
+
console.log(JSON.stringify({
|
|
155
|
+
framework: detectedFramework,
|
|
156
|
+
changedFiles,
|
|
157
|
+
routes,
|
|
158
|
+
}, null, 2));
|
|
159
|
+
}
|
|
160
|
+
// ============================================================
|
|
161
|
+
// Subcommand: compare
|
|
162
|
+
// ============================================================
|
|
163
|
+
async function runCompare() {
|
|
164
|
+
const beforeBase = values['before-base'];
|
|
165
|
+
const afterBase = values['after-base'];
|
|
166
|
+
if (!beforeBase || !afterBase) {
|
|
167
|
+
console.error('Both --before-base and --after-base are required for compare.');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
const routeList = values.routes
|
|
171
|
+
? values.routes.split(',').map(r => r.trim())
|
|
172
|
+
: ['/'];
|
|
173
|
+
const responsive = values.responsive ?? false;
|
|
174
|
+
const viewport = resolveViewportFlag();
|
|
175
|
+
const outputDir = values.output || path.join(process.env.HOME || '~', 'Downloads');
|
|
176
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
177
|
+
const timestamp = new Date();
|
|
178
|
+
try {
|
|
179
|
+
for (const route of routeList) {
|
|
180
|
+
const beforeUrl = normalizeUrl(beforeBase.replace(/\/$/, '') + route);
|
|
181
|
+
const afterUrl = normalizeUrl(afterBase.replace(/\/$/, '') + route);
|
|
182
|
+
if (responsive) {
|
|
183
|
+
// Capture desktop + mobile for each route
|
|
184
|
+
for (const preset of ['desktop', 'mobile']) {
|
|
185
|
+
const vp = VIEWPORT_PRESETS[preset];
|
|
186
|
+
console.log(`Capturing ${route} @ ${preset} (${vp.width}x${vp.height})...`);
|
|
187
|
+
const beforeResult = await captureScreenshot({
|
|
188
|
+
url: beforeUrl,
|
|
189
|
+
viewport: preset,
|
|
190
|
+
fullPage: values.full,
|
|
191
|
+
selector: values.selector,
|
|
192
|
+
});
|
|
193
|
+
const afterResult = await captureScreenshot({
|
|
194
|
+
url: afterUrl,
|
|
195
|
+
viewport: preset,
|
|
196
|
+
fullPage: values.full,
|
|
197
|
+
selector: values.selector,
|
|
198
|
+
});
|
|
199
|
+
const routeSlug = route === '/' ? 'home' : route.replace(/^\//, '').replace(/\//g, '-');
|
|
200
|
+
const beforeFilename = `${routeSlug}-${preset}-before-${formatTimestamp(timestamp)}.png`;
|
|
201
|
+
const afterFilename = `${routeSlug}-${preset}-after-${formatTimestamp(timestamp)}.png`;
|
|
202
|
+
fs.writeFileSync(path.join(outputDir, beforeFilename), beforeResult.image);
|
|
203
|
+
fs.writeFileSync(path.join(outputDir, afterFilename), afterResult.image);
|
|
204
|
+
console.log(` Saved: ${beforeFilename}, ${afterFilename}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log(`Capturing ${route}...`);
|
|
209
|
+
const beforeResult = await captureScreenshot({ url: beforeUrl, viewport, fullPage: values.full });
|
|
210
|
+
const afterResult = await captureScreenshot({ url: afterUrl, viewport, fullPage: values.full });
|
|
211
|
+
const routeSlug = route === '/' ? 'home' : route.replace(/^\//, '').replace(/\//g, '-');
|
|
212
|
+
const beforeFilename = `${routeSlug}-before-${formatTimestamp(timestamp)}.png`;
|
|
213
|
+
const afterFilename = `${routeSlug}-after-${formatTimestamp(timestamp)}.png`;
|
|
214
|
+
fs.writeFileSync(path.join(outputDir, beforeFilename), beforeResult.image);
|
|
215
|
+
fs.writeFileSync(path.join(outputDir, afterFilename), afterResult.image);
|
|
216
|
+
console.log(` Saved: ${beforeFilename}, ${afterFilename}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
console.log(`\nAll screenshots saved to: ${outputDir}`);
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await closeBrowser();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ============================================================
|
|
226
|
+
// Subcommand: run (detect + compare)
|
|
227
|
+
// ============================================================
|
|
228
|
+
async function runFull() {
|
|
229
|
+
const beforeBase = values['before-base'];
|
|
230
|
+
const afterBase = values['after-base'];
|
|
231
|
+
if (!beforeBase || !afterBase) {
|
|
232
|
+
console.error('Both --before-base and --after-base are required for run.');
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
// Detect routes
|
|
236
|
+
const framework = values.framework;
|
|
237
|
+
const maxRoutes = values['max-routes'] ? parseInt(values['max-routes']) : undefined;
|
|
238
|
+
const changedFiles = getChangedFiles();
|
|
239
|
+
let routeList;
|
|
240
|
+
if (values.routes) {
|
|
241
|
+
routeList = values.routes.split(',').map(r => r.trim());
|
|
242
|
+
}
|
|
243
|
+
else if (changedFiles.length > 0) {
|
|
244
|
+
const routes = detectRoutes(changedFiles, { framework, maxRoutes });
|
|
245
|
+
if (routes.length === 0) {
|
|
246
|
+
console.log('No visual routes detected. Defaulting to /');
|
|
247
|
+
routeList = ['/'];
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
routeList = routes.map(r => r.path);
|
|
251
|
+
console.log(`Detected routes: ${routeList.join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
console.log('No changed files detected. Defaulting to /');
|
|
256
|
+
routeList = ['/'];
|
|
257
|
+
}
|
|
258
|
+
// Override routes in values for compare to pick up
|
|
259
|
+
values.routes = routeList.join(',');
|
|
260
|
+
await runCompare();
|
|
261
|
+
}
|
|
262
|
+
// ============================================================
|
|
263
|
+
// Default mode (original before-and-after behavior)
|
|
264
|
+
// ============================================================
|
|
265
|
+
async function runDefault() {
|
|
266
|
+
if (values.help) {
|
|
267
|
+
printHelp();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (positionals.length < 2) {
|
|
271
|
+
console.error('Two arguments required (URLs or image paths). Run with --help for usage.');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
const [first, second, ...rest] = positionals;
|
|
275
|
+
const viewport = resolveViewportFlag();
|
|
276
|
+
const ba = new BeforeAndAfter({ viewport });
|
|
277
|
+
try {
|
|
278
|
+
// Auto-detect image mode
|
|
279
|
+
if (isImageFile(first) && isImageFile(second)) {
|
|
280
|
+
const result = await ba.fromImages({
|
|
281
|
+
before: first,
|
|
282
|
+
after: second,
|
|
283
|
+
});
|
|
284
|
+
if (values.markdown) {
|
|
285
|
+
console.log(result.markdown);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
console.log(`Before: ${first}`);
|
|
289
|
+
console.log(`After: ${second}`);
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// URL mode
|
|
294
|
+
const beforeUrl = normalizeUrl(first);
|
|
295
|
+
const afterUrl = normalizeUrl(second);
|
|
296
|
+
// Resolve selectors: positional args override -s flag
|
|
297
|
+
let beforeSelector = values.selector;
|
|
298
|
+
let afterSelector = values.selector;
|
|
299
|
+
if (rest.length >= 1) {
|
|
300
|
+
beforeSelector = rest[0];
|
|
301
|
+
afterSelector = rest[0];
|
|
302
|
+
}
|
|
303
|
+
if (rest.length >= 2) {
|
|
304
|
+
afterSelector = rest[1];
|
|
305
|
+
}
|
|
306
|
+
const outputDir = values.output || path.join(process.env.HOME || '~', 'Downloads');
|
|
307
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
308
|
+
console.log(`Capturing before: ${beforeUrl}${beforeSelector ? ` (${beforeSelector})` : ''}`);
|
|
309
|
+
console.log(`Capturing after: ${afterUrl}${afterSelector ? ` (${afterSelector})` : ''}`);
|
|
310
|
+
const result = await ba.captureBeforeAfter({
|
|
311
|
+
before: {
|
|
312
|
+
url: beforeUrl,
|
|
313
|
+
selector: beforeSelector,
|
|
314
|
+
fullPage: values.full,
|
|
315
|
+
},
|
|
316
|
+
after: {
|
|
317
|
+
url: afterUrl,
|
|
318
|
+
selector: afterSelector,
|
|
319
|
+
fullPage: values.full,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
// Generate filenames
|
|
323
|
+
const timestamp = new Date();
|
|
324
|
+
const beforeFilename = generateFilename({
|
|
325
|
+
url: beforeUrl,
|
|
326
|
+
suffix: 'before',
|
|
327
|
+
timestamp,
|
|
328
|
+
});
|
|
329
|
+
const afterFilename = generateFilename({
|
|
330
|
+
url: afterUrl,
|
|
331
|
+
suffix: 'after',
|
|
332
|
+
timestamp,
|
|
333
|
+
});
|
|
334
|
+
const beforePath = path.join(outputDir, beforeFilename);
|
|
335
|
+
const afterPath = path.join(outputDir, afterFilename);
|
|
336
|
+
fs.writeFileSync(beforePath, result.before.image);
|
|
337
|
+
fs.writeFileSync(afterPath, result.after.image);
|
|
338
|
+
console.log(`\nSaved: ${beforePath}`);
|
|
339
|
+
console.log(`Saved: ${afterPath}`);
|
|
340
|
+
// Output markdown if requested
|
|
341
|
+
if (values.markdown) {
|
|
342
|
+
const uploadUrl = values['upload-url'] || process.env.UPLOAD_URL;
|
|
343
|
+
console.log(`\nUploading images${uploadUrl ? ` to ${uploadUrl}` : ''}...`);
|
|
344
|
+
const { beforeUrl: bUrl, afterUrl: aUrl } = await uploadBeforeAfter({ image: result.before.image, filename: beforeFilename }, { image: result.after.image, filename: afterFilename }, uploadUrl);
|
|
345
|
+
console.log(`Before: ${bUrl}`);
|
|
346
|
+
console.log(`After: ${aUrl}`);
|
|
347
|
+
const markdown = `| Pre | Post |
|
|
348
|
+
|:---:|:----:|
|
|
349
|
+
|  |  |`;
|
|
350
|
+
console.log(`\n${markdown}`);
|
|
351
|
+
if (copyToClipboard(markdown)) {
|
|
352
|
+
console.log(`\nMarkdown copied to clipboard`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
await closeBrowser();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function formatTimestamp(date) {
|
|
361
|
+
return date.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
362
|
+
}
|
|
363
|
+
// ============================================================
|
|
364
|
+
// Main dispatch
|
|
365
|
+
// ============================================================
|
|
366
|
+
async function main() {
|
|
367
|
+
if (values.help && !isSubcommand) {
|
|
368
|
+
printHelp();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
switch (subcommand) {
|
|
372
|
+
case 'detect':
|
|
373
|
+
return runDetect();
|
|
374
|
+
case 'compare':
|
|
375
|
+
return runCompare();
|
|
376
|
+
case 'run':
|
|
377
|
+
return runFull();
|
|
378
|
+
default:
|
|
379
|
+
return runDefault();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
main()
|
|
383
|
+
.then(() => process.exit(0))
|
|
384
|
+
.catch((err) => {
|
|
385
|
+
console.error(err.message);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
});
|
|
388
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/bin/cli.ts"],"names":[],"mappings":"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"}
|