@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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/bin/cli.d.ts +3 -0
  4. package/dist/bin/cli.d.ts.map +1 -0
  5. package/dist/bin/cli.js +388 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/browser.d.ts +25 -0
  8. package/dist/browser.d.ts.map +1 -0
  9. package/dist/browser.js +82 -0
  10. package/dist/browser.js.map +1 -0
  11. package/dist/capture.d.ts +9 -0
  12. package/dist/capture.d.ts.map +1 -0
  13. package/dist/capture.js +52 -0
  14. package/dist/capture.js.map +1 -0
  15. package/dist/clipboard.d.ts +9 -0
  16. package/dist/clipboard.d.ts.map +1 -0
  17. package/dist/clipboard.js +26 -0
  18. package/dist/clipboard.js.map +1 -0
  19. package/dist/filename.d.ts +24 -0
  20. package/dist/filename.d.ts.map +1 -0
  21. package/dist/filename.js +76 -0
  22. package/dist/filename.js.map +1 -0
  23. package/dist/index.d.ts +25 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +69 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/routes/generic.d.ts +11 -0
  28. package/dist/routes/generic.d.ts.map +1 -0
  29. package/dist/routes/generic.js +106 -0
  30. package/dist/routes/generic.js.map +1 -0
  31. package/dist/routes/nextjs.d.ts +14 -0
  32. package/dist/routes/nextjs.d.ts.map +1 -0
  33. package/dist/routes/nextjs.js +196 -0
  34. package/dist/routes/nextjs.js.map +1 -0
  35. package/dist/routes.d.ts +21 -0
  36. package/dist/routes.d.ts.map +1 -0
  37. package/dist/routes.js +174 -0
  38. package/dist/routes.js.map +1 -0
  39. package/dist/types.d.ts +98 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +9 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/upload.d.ts +35 -0
  44. package/dist/upload.d.ts.map +1 -0
  45. package/dist/upload.js +116 -0
  46. package/dist/upload.js.map +1 -0
  47. package/dist/viewport.d.ts +3 -0
  48. package/dist/viewport.d.ts.map +1 -0
  49. package/dist/viewport.js +11 -0
  50. package/dist/viewport.js.map +1 -0
  51. package/package.json +66 -0
  52. package/skill/SKILL.md +246 -0
  53. package/skill/scripts/adapters/0x0st.sh +36 -0
  54. package/skill/scripts/adapters/blob.sh +55 -0
  55. package/skill/scripts/adapters/gist.sh +59 -0
  56. package/skill/scripts/adapters/git-native.sh +75 -0
  57. 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,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/bin/cli.ts"],"names":[],"mappings":""}
@@ -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
+ | ![Pre](${bUrl}) | ![Post](${aUrl}) |`;
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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"}