@juangadm/pre-post 0.2.0 → 1.0.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 (125) hide show
  1. package/README.md +102 -222
  2. package/dist/bin/cli.js +199 -347
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/browser.d.ts +42 -11
  5. package/dist/browser.d.ts.map +1 -1
  6. package/dist/browser.js +371 -94
  7. package/dist/browser.js.map +1 -1
  8. package/dist/capture.d.ts +1 -6
  9. package/dist/capture.d.ts.map +1 -1
  10. package/dist/capture.js +12 -35
  11. package/dist/capture.js.map +1 -1
  12. package/dist/commands/compare.d.ts +18 -0
  13. package/dist/commands/compare.d.ts.map +1 -0
  14. package/dist/commands/compare.js +64 -0
  15. package/dist/commands/compare.js.map +1 -0
  16. package/dist/commands/detect.d.ts +20 -0
  17. package/dist/commands/detect.d.ts.map +1 -0
  18. package/dist/commands/detect.js +19 -0
  19. package/dist/commands/detect.js.map +1 -0
  20. package/dist/commands/doctor.d.ts +10 -0
  21. package/dist/commands/doctor.d.ts.map +1 -0
  22. package/dist/commands/doctor.js +38 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/login.d.ts +6 -0
  25. package/dist/commands/login.d.ts.map +1 -0
  26. package/dist/commands/login.js +34 -0
  27. package/dist/commands/login.js.map +1 -0
  28. package/dist/commands/pr.d.ts +28 -0
  29. package/dist/commands/pr.d.ts.map +1 -0
  30. package/dist/commands/pr.js +159 -0
  31. package/dist/commands/pr.js.map +1 -0
  32. package/dist/commands/prune.d.ts +10 -0
  33. package/dist/commands/prune.d.ts.map +1 -0
  34. package/dist/commands/prune.js +13 -0
  35. package/dist/commands/prune.js.map +1 -0
  36. package/dist/config.d.ts +36 -0
  37. package/dist/config.d.ts.map +1 -0
  38. package/dist/config.js +60 -0
  39. package/dist/config.js.map +1 -0
  40. package/dist/diff-pool.d.ts +17 -0
  41. package/dist/diff-pool.d.ts.map +1 -0
  42. package/dist/diff-pool.js +89 -0
  43. package/dist/diff-pool.js.map +1 -0
  44. package/dist/diff-worker.d.ts +29 -0
  45. package/dist/diff-worker.d.ts.map +1 -0
  46. package/dist/diff-worker.js +38 -0
  47. package/dist/diff-worker.js.map +1 -0
  48. package/dist/diff.d.ts +37 -0
  49. package/dist/diff.d.ts.map +1 -0
  50. package/dist/diff.js +171 -0
  51. package/dist/diff.js.map +1 -0
  52. package/dist/doctor.d.ts +38 -0
  53. package/dist/doctor.d.ts.map +1 -0
  54. package/dist/doctor.js +72 -0
  55. package/dist/doctor.js.map +1 -0
  56. package/dist/errors.d.ts +38 -0
  57. package/dist/errors.d.ts.map +1 -0
  58. package/dist/errors.js +59 -0
  59. package/dist/errors.js.map +1 -0
  60. package/dist/filename.d.ts +2 -0
  61. package/dist/filename.d.ts.map +1 -1
  62. package/dist/filename.js +2 -1
  63. package/dist/filename.js.map +1 -1
  64. package/dist/git.d.ts +23 -0
  65. package/dist/git.d.ts.map +1 -0
  66. package/dist/git.js +86 -0
  67. package/dist/git.js.map +1 -0
  68. package/dist/github.d.ts +73 -0
  69. package/dist/github.d.ts.map +1 -0
  70. package/dist/github.js +240 -0
  71. package/dist/github.js.map +1 -0
  72. package/dist/index.d.ts +22 -22
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +17 -66
  75. package/dist/index.js.map +1 -1
  76. package/dist/pkg.d.ts +8 -0
  77. package/dist/pkg.d.ts.map +1 -0
  78. package/dist/pkg.js +35 -0
  79. package/dist/pkg.js.map +1 -0
  80. package/dist/report.d.ts +20 -0
  81. package/dist/report.d.ts.map +1 -0
  82. package/dist/report.js +107 -0
  83. package/dist/report.js.map +1 -0
  84. package/dist/routes/imports.d.ts +46 -0
  85. package/dist/routes/imports.d.ts.map +1 -0
  86. package/dist/routes/imports.js +208 -0
  87. package/dist/routes/imports.js.map +1 -0
  88. package/dist/routes/vite.d.ts +15 -0
  89. package/dist/routes/vite.d.ts.map +1 -0
  90. package/dist/routes/vite.js +130 -0
  91. package/dist/routes/vite.js.map +1 -0
  92. package/dist/routes.d.ts +41 -14
  93. package/dist/routes.d.ts.map +1 -1
  94. package/dist/routes.js +271 -141
  95. package/dist/routes.js.map +1 -1
  96. package/dist/run.d.ts +36 -0
  97. package/dist/run.d.ts.map +1 -0
  98. package/dist/run.js +128 -0
  99. package/dist/run.js.map +1 -0
  100. package/dist/sessions.d.ts +50 -0
  101. package/dist/sessions.d.ts.map +1 -0
  102. package/dist/sessions.js +74 -0
  103. package/dist/sessions.js.map +1 -0
  104. package/dist/types.d.ts +118 -41
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/types.js +1 -0
  107. package/dist/types.js.map +1 -1
  108. package/dist/viewport.d.ts +6 -1
  109. package/dist/viewport.d.ts.map +1 -1
  110. package/dist/viewport.js +13 -7
  111. package/dist/viewport.js.map +1 -1
  112. package/package.json +21 -13
  113. package/skill/SKILL.md +31 -229
  114. package/dist/clipboard.d.ts +0 -9
  115. package/dist/clipboard.d.ts.map +0 -1
  116. package/dist/clipboard.js +0 -26
  117. package/dist/clipboard.js.map +0 -1
  118. package/dist/upload.d.ts +0 -39
  119. package/dist/upload.d.ts.map +0 -1
  120. package/dist/upload.js +0 -149
  121. package/dist/upload.js.map +0 -1
  122. package/skill/scripts/adapters/0x0st.sh +0 -36
  123. package/skill/scripts/adapters/blob.sh +0 -55
  124. package/skill/scripts/adapters/git-native.sh +0 -51
  125. package/skill/scripts/upload-and-copy.sh +0 -230
package/skill/SKILL.md CHANGED
@@ -1,247 +1,49 @@
1
1
  ---
2
2
  name: pre-post
3
- description: Captures before/after screenshots of web pages for visual comparison in PRs. Use when user says "take before and after", "screenshot comparison", "visual diff", "PR screenshots", or after making visual UI changes.
3
+ description: Before/after screenshots for the current PR. Use when the user says "take before and after", "pre-post", "screenshot comparison", "visual diff", "PR screenshots", or after making visual UI changes.
4
4
  allowed-tools:
5
+ - Bash(npx -y @juangadm/pre-post@latest *)
5
6
  - Bash(npx pre-post *)
6
7
  - Bash(pre-post *)
7
- - Bash(*/upload-and-copy.sh *)
8
- - Bash(git add *)
9
- - Bash(git commit -m *)
10
- - Bash(git push origin *)
11
- - Bash(curl -s -o /dev/null -w *)
12
- - Bash(gh pr view *)
13
- - Bash(gh pr edit *)
14
- - Bash(lsof -i *)
15
- - Bash(mkdir -p /tmp/pre-post)
16
- - Bash(git diff *)
17
8
  ---
18
9
 
19
- # Pre-Post Screenshot Skill
10
+ # pre-post
20
11
 
21
- > **Package:** `pre-post`
22
- > Visual diff tool for PRs fastest path from code change to visual documentation.
12
+ One command does everything: detects the routes this branch changed, screenshots them on
13
+ production ("Pre") and on the local dev server ("Post") at desktop and mobile, pixel-diffs
14
+ them, uploads the images to a `pre-post-assets` branch, and posts or updates a single
15
+ comment on the open PR. The human reviews on GitHub.
23
16
 
24
- ## Agent Behavior Rules
25
-
26
- **DO NOT:**
27
- - Switch git branches, stash changes, start dev servers, or assume what "before" is
28
- - Use `--full` unless user explicitly asks for full page / full scroll capture
29
- - Post screenshots to PR without user approval
30
-
31
- **DO:**
32
- - Use `--markdown` when user wants PR integration or markdown output
33
- - Use `--responsive` to capture both desktop and mobile viewports
34
- - Use `--mobile` / `--tablet` if user mentions phone, mobile, tablet, responsive
35
- - Assume current state is **After** (localhost = after, production = before)
36
- - Show screenshots to user before posting to PR
37
- - If user provides only one URL, **ASK**: "What URL should I use for the 'before' state? (production URL, preview deployment, or another local port)"
38
-
39
- ## Execution Order
40
-
41
- ### 1. Pre-flight Checks
42
-
43
- ```bash
44
- # Detect running dev server
45
- lsof -i :3000 2>/dev/null || lsof -i :3001 2>/dev/null || lsof -i :5173 2>/dev/null || lsof -i :8080 2>/dev/null
46
- ```
47
-
48
- If no dev server is running, tell the user to start one.
49
-
50
- ```bash
51
- # Check production URL is accessible
52
- curl -s -o /dev/null -w "%{http_code}" "<production-url>"
53
- ```
54
-
55
- - **200** → proceed
56
- - **401/403** → warn user: "Production URL requires authentication. Options: (1) provide a public URL, (2) skip 'before' and capture after-only, (3) provide auth cookies"
57
- - **No production URL** → "after-only" mode: screenshot localhost only, label as current state
58
-
59
- ### 2. Route Detection + Refinement
17
+ ## Run
60
18
 
61
19
  ```bash
62
- # Detect affected routes from git diff
63
- npx pre-post detect
20
+ npx -y @juangadm/pre-post@latest pr
64
21
  ```
65
22
 
66
- This outputs JSON with detected routes, confidence levels, and source files.
67
-
68
- **Claude's role:** Review the JSON output using conversation context:
69
- - Add routes you know are affected from the work done in this session
70
- - Remove false positives (e.g., API-only changes)
71
- - For dynamic routes (e.g., `/blog/[slug]`), ask user for a sample value
72
- - Present to user: "I'll screenshot these routes: `/dashboard`, `/settings`. Want to add or change any?"
23
+ Add `--before https://production-url` the first time in a repo (it is saved to
24
+ `.pre-post.json`). Add `--routes /a,/b` when the user names pages explicitly.
73
25
 
74
- ### 3. Screenshot Capture
26
+ ## Rules
75
27
 
76
- **Option A: CLI (preferred deterministic)**
77
-
78
- ```bash
79
- # Single route, desktop only
80
- npx pre-post compare \
81
- --before-base https://prod.com \
82
- --after-base http://localhost:3000 \
83
- --routes /dashboard \
84
- --output /tmp/pre-post
85
-
86
- # Multiple routes, responsive (desktop + mobile)
87
- npx pre-post compare \
88
- --before-base https://prod.com \
89
- --after-base http://localhost:3000 \
90
- --routes /dashboard,/settings,/ \
91
- --responsive \
92
- --output /tmp/pre-post
93
- ```
28
+ - Run the command once. Do not open, read, or describe the screenshot files; the PR
29
+ comment is the deliverable. Report the summary the command prints, plus the comment link.
30
+ - Do not start dev servers, switch branches, or use a browser tool yourself.
31
+ - Exit code 3 means a human must do one thing (log in, start the dev server, pass
32
+ `--before`). Relay that one sentence verbatim and stop.
33
+ - Do not use `--dry-run` unless the user asks to preview without posting.
34
+ - If the summary lists routes that "need a sample URL", ask the user for one example URL per
35
+ dynamic route and add it under `"samples"` in `.pre-post.json`, then re-run.
94
36
 
95
- **Option B: Playwright MCP (for more control)**
96
-
97
- Use when you need custom waits, interactions, or complex page states:
98
-
99
- ```
100
- browser_resize(1280, 800)
101
- browser_navigate("https://prod.com/dashboard")
102
- browser_wait_for(time: 3)
103
- browser_take_screenshot(filename: "/tmp/pre-post/dashboard-desktop-before.png")
104
-
105
- browser_navigate("http://localhost:3000/dashboard")
106
- browser_wait_for(time: 3)
107
- browser_take_screenshot(filename: "/tmp/pre-post/dashboard-desktop-after.png")
108
-
109
- # Mobile
110
- browser_resize(375, 812)
111
- browser_navigate("https://prod.com/dashboard")
112
- browser_wait_for(time: 3)
113
- browser_take_screenshot(filename: "/tmp/pre-post/dashboard-mobile-before.png")
114
-
115
- browser_navigate("http://localhost:3000/dashboard")
116
- browser_wait_for(time: 3)
117
- browser_take_screenshot(filename: "/tmp/pre-post/dashboard-mobile-after.png")
118
- ```
119
-
120
- ### 4. User Approval
121
-
122
- Show screenshots in conversation. Ask: "Here are the before/after screenshots. Should I post to PR, retake any, or add more pages?"
123
-
124
- ### 5. Upload + PR Markdown
125
-
126
- ```bash
127
- # Upload and generate markdown
128
- mkdir -p /tmp/pre-post
129
- ./scripts/upload-and-copy.sh /tmp/pre-post/before.png /tmp/pre-post/after.png --markdown
130
- ```
131
-
132
- Or use the CLI's built-in upload:
133
-
134
- ```bash
135
- npx pre-post <before.png> <after.png> --markdown
136
- ```
137
-
138
- For multi-route PRs, generate this format:
139
-
140
- ```markdown
141
- ## Visual Changes
142
-
143
- ### `/dashboard`
144
-
145
- <details open>
146
- <summary>Desktop (1280x800)</summary>
147
-
148
- | Pre | Post |
149
- |:---:|:----:|
150
- | ![Pre](url) | ![Post](url) |
151
- </details>
152
-
153
- <details>
154
- <summary>Mobile (375x812)</summary>
155
-
156
- | Pre | Post |
157
- |:---:|:----:|
158
- | ![Pre](url) | ![Post](url) |
159
- </details>
160
-
161
- ---
162
- *Captured by [pre-post](https://github.com/juangadm/pre-post)*
163
- ```
164
-
165
- ### 6. PR Integration
166
-
167
- ```bash
168
- # Get current PR
169
- gh pr view --json number,body
170
-
171
- # Append screenshots to PR body
172
- gh pr edit <number> --body "<existing-body>
173
-
174
- <generated-markdown>"
175
- ```
176
-
177
- If no `gh` CLI: output markdown and tell user to paste manually.
178
-
179
- ## Quick Reference
180
-
181
- ```bash
182
- # Basic usage (two URLs)
183
- pre-post site.com localhost:3000
184
-
185
- # Detect routes from git diff
186
- pre-post detect
187
- pre-post detect --framework nextjs-app
188
-
189
- # Compare with auto-detected routes
190
- pre-post run --before-base https://prod.com --after-base http://localhost:3000
191
-
192
- # Compare specific routes
193
- pre-post compare --before-base URL --after-base URL --routes /dashboard,/settings
194
-
195
- # Responsive (desktop + mobile)
196
- pre-post compare --before-base URL --after-base URL --responsive
197
-
198
- # From existing images
199
- pre-post before.png after.png --markdown
200
-
201
- # Via npx
202
- npx pre-post detect
203
- npx pre-post compare --before-base URL --after-base URL
204
- ```
205
-
206
- | Flag | Description |
207
- |------|-------------|
208
- | `-m, --mobile` | Mobile viewport (375x812) |
209
- | `-t, --tablet` | Tablet viewport (768x1024) |
210
- | `--size <WxH>` | Custom viewport |
211
- | `-f, --full` | Full scrollable page |
212
- | `-s, --selector` | CSS selector to capture |
213
- | `-r, --responsive` | Desktop + mobile capture |
214
- | `--routes <paths>` | Explicit route list (comma-separated) |
215
- | `--max-routes <n>` | Max detected routes (default: 5) |
216
- | `--framework <name>` | Force framework detection |
217
- | `--before-base <url>` | Production URL |
218
- | `--after-base <url>` | Localhost URL |
219
- | `-o, --output` | Output directory (default: ~/Downloads) |
220
- | `--markdown` | Upload images & output markdown |
221
- | `--upload-url <url>` | Upload endpoint (overrides git-native default) |
222
-
223
- ## Image Upload
224
-
225
- Screenshots are committed to `.pre-post/` on the current PR branch and served via
226
- GitHub blob URLs pinned to the commit SHA. This works for **both public and private
227
- repos** — blob URLs are same-origin on GitHub, so the markdown renderer resolves them
228
- with the viewer's authentication.
229
-
230
- ```bash
231
- # Default (git-native — commits to PR branch, works on any repo)
232
- ./scripts/upload-and-copy.sh before.png after.png --markdown
233
-
234
- # Explicit override for external storage:
235
- IMAGE_ADAPTER=0x0st ./scripts/upload-and-copy.sh before.png after.png --markdown
236
- ```
37
+ ## Options worth knowing
237
38
 
238
- ## Error Reference
39
+ | Flag | Use when |
40
+ |------|----------|
41
+ | `--routes /a,/b` | The user names the pages |
42
+ | `--viewports desktop` | Desktop only (default is desktop + mobile) |
43
+ | `--viewport-only` | First screen only instead of full page |
44
+ | `--pr <n>` | The branch has several PRs or the lookup fails |
45
+ | `--dry-run` | Preview locally, post nothing |
46
+ | `--header k=v` / `--cookie k=v` | The site needs auth headers or cookies |
239
47
 
240
- | Error | Fix |
241
- |-------|-----|
242
- | `command not found` | `npm install -g @juangadm/pre-post` |
243
- | `browserType.launch: Executable doesn't exist` | `npx playwright install chromium` |
244
- | 401/403 on production URL | See pre-flight section above |
245
- | Element not found | Verify selector exists on page |
246
- | No changed files detected | Specify routes manually with `--routes` |
247
- | Could not determine commit SHA | Ensure `git push` succeeded and HEAD is valid |
48
+ Login-protected sites: `npx -y @juangadm/pre-post@latest login https://site` opens a browser
49
+ once; the saved session is reused automatically.
@@ -1,9 +0,0 @@
1
- /**
2
- * Cross-platform clipboard utilities
3
- */
4
- /**
5
- * Copy text to system clipboard
6
- * Supports macOS (pbcopy) and Linux (xclip)
7
- */
8
- export declare function copyToClipboard(text: string): boolean;
9
- //# sourceMappingURL=clipboard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcrD"}
package/dist/clipboard.js DELETED
@@ -1,26 +0,0 @@
1
- /**
2
- * Cross-platform clipboard utilities
3
- */
4
- import { execSync } from 'child_process';
5
- /**
6
- * Copy text to system clipboard
7
- * Supports macOS (pbcopy) and Linux (xclip)
8
- */
9
- export function copyToClipboard(text) {
10
- try {
11
- if (process.platform === 'darwin') {
12
- execSync('pbcopy', { input: text });
13
- return true;
14
- }
15
- else if (process.platform === 'linux') {
16
- execSync('xclip -selection clipboard', { input: text });
17
- return true;
18
- }
19
- // Windows or unsupported platform
20
- return false;
21
- }
22
- catch {
23
- return false;
24
- }
25
- }
26
- //# sourceMappingURL=clipboard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,QAAQ,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACxC,QAAQ,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,kCAAkC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/dist/upload.d.ts DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Image upload for generating shareable URLs.
3
- * Default: git-native (commits to .pre-post/, serves via blob+SHA URLs).
4
- * Opt-in: 0x0.st, Vercel Blob, generic PUT via --upload-url.
5
- */
6
- /**
7
- * Upload an image and return a public URL.
8
- * Auto-detects upload method from URL pattern.
9
- */
10
- export declare function uploadImage(image: Buffer, filename: string, uploadUrl?: string): Promise<string>;
11
- /**
12
- * Write an image to .pre-post/ in the repo root and stage it.
13
- * Returns filename + ownerRepo — caller constructs blob+SHA URL after commit.
14
- */
15
- export declare function uploadGitNative(image: Buffer, filename: string): {
16
- filename: string;
17
- ownerRepo: string;
18
- };
19
- /**
20
- * Commit and push all staged .pre-post/ screenshots in one batch.
21
- * Returns the full 40-char commit SHA.
22
- */
23
- export declare function commitAndPushScreenshots(): string;
24
- /**
25
- * Upload before/after images and return URLs.
26
- * When uploadUrl is provided, uses the HTTP-based upload path.
27
- * Otherwise, uses git-native (commit to .pre-post/) with blob+SHA URLs.
28
- */
29
- export declare function uploadBeforeAfter(before: {
30
- image: Buffer;
31
- filename: string;
32
- }, after: {
33
- image: Buffer;
34
- filename: string;
35
- }, uploadUrl?: string): Promise<{
36
- beforeUrl: string;
37
- afterUrl: string;
38
- }>;
39
- //# sourceMappingURL=upload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,MAAM,CAAC,CAajB;AA+ED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAYzC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AAWD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC3C,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC1C,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBlD"}
package/dist/upload.js DELETED
@@ -1,149 +0,0 @@
1
- /**
2
- * Image upload for generating shareable URLs.
3
- * Default: git-native (commits to .pre-post/, serves via blob+SHA URLs).
4
- * Opt-in: 0x0.st, Vercel Blob, generic PUT via --upload-url.
5
- */
6
- import { execSync } from 'child_process';
7
- import fs from 'fs';
8
- import path from 'path';
9
- const DEFAULT_UPLOAD_URL = 'https://0x0.st';
10
- /**
11
- * Upload an image and return a public URL.
12
- * Auto-detects upload method from URL pattern.
13
- */
14
- export async function uploadImage(image, filename, uploadUrl = DEFAULT_UPLOAD_URL) {
15
- // 0x0.st uses multipart form upload
16
- if (uploadUrl.includes('0x0.st')) {
17
- return upload0x0st(image, filename, uploadUrl);
18
- }
19
- // Vercel Blob uses PUT with specific headers
20
- if (uploadUrl.includes('blob.vercel')) {
21
- return uploadVercelBlob(image, filename, uploadUrl);
22
- }
23
- // Generic PUT upload (common for S3-compatible services)
24
- return uploadGenericPut(image, filename, uploadUrl);
25
- }
26
- async function upload0x0st(image, filename, url) {
27
- const formData = new FormData();
28
- formData.append('file', new Blob([image]), filename);
29
- const response = await fetch(url, {
30
- method: 'POST',
31
- headers: { 'User-Agent': 'before-after-cli/1.0' },
32
- body: formData,
33
- });
34
- const result = (await response.text()).trim();
35
- if (!result.startsWith('http')) {
36
- throw new Error(`Upload failed: ${result}`);
37
- }
38
- return result;
39
- }
40
- async function uploadVercelBlob(image, filename, url) {
41
- const response = await fetch(`${url}/${filename}`, {
42
- method: 'PUT',
43
- headers: { 'Content-Type': 'image/png' },
44
- body: image,
45
- });
46
- if (!response.ok) {
47
- throw new Error(`Upload failed: ${response.statusText}`);
48
- }
49
- const result = await response.json();
50
- return result.url;
51
- }
52
- async function uploadGenericPut(image, filename, url) {
53
- const response = await fetch(`${url}/${filename}`, {
54
- method: 'PUT',
55
- headers: { 'Content-Type': 'image/png' },
56
- body: image,
57
- });
58
- if (!response.ok) {
59
- throw new Error(`Upload failed: ${response.statusText}`);
60
- }
61
- // Try to parse JSON response, fall back to URL from location header or constructed URL
62
- const contentType = response.headers.get('content-type') || '';
63
- if (contentType.includes('application/json')) {
64
- const result = await response.json();
65
- if (result.url)
66
- return result.url;
67
- }
68
- return response.headers.get('location') || `${url}/${filename}`;
69
- }
70
- /**
71
- * Resolve owner/repo from environment variables or git remote URL.
72
- * Supports standard GitHub URLs, SSH URLs, and proxy/non-standard URLs.
73
- */
74
- function resolveOwnerRepo() {
75
- const envOwnerRepo = process.env.GH_REPO || process.env.GITHUB_REPOSITORY;
76
- if (envOwnerRepo)
77
- return envOwnerRepo;
78
- const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
79
- // Standard GitHub: https://github.com/owner/repo.git or git@github.com:owner/repo.git
80
- const githubMatch = remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
81
- if (githubMatch)
82
- return githubMatch[1];
83
- // Fallback: extract last two path segments from any URL (covers proxies, mirrors, etc.)
84
- const fallbackMatch = remoteUrl.match(/\/([^/]+\/[^/]+?)(?:\.git)?$/);
85
- if (fallbackMatch)
86
- return fallbackMatch[1];
87
- throw new Error(`Cannot parse owner/repo from: ${remoteUrl}\n` +
88
- 'Fix: set GH_REPO=owner/repo');
89
- }
90
- /**
91
- * Write an image to .pre-post/ in the repo root and stage it.
92
- * Returns filename + ownerRepo — caller constructs blob+SHA URL after commit.
93
- */
94
- export function uploadGitNative(image, filename) {
95
- const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
96
- const ownerRepo = resolveOwnerRepo();
97
- const destDir = path.join(repoRoot, '.pre-post');
98
- fs.mkdirSync(destDir, { recursive: true });
99
- const dest = path.join(destDir, filename);
100
- fs.writeFileSync(dest, image);
101
- execSync(`git add -f "${dest}"`);
102
- return { filename, ownerRepo };
103
- }
104
- /**
105
- * Commit and push all staged .pre-post/ screenshots in one batch.
106
- * Returns the full 40-char commit SHA.
107
- */
108
- export function commitAndPushScreenshots() {
109
- execSync('git commit -m "chore: add pre/post screenshots"');
110
- execSync('git push origin HEAD');
111
- const sha = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
112
- if (!/^[0-9a-f]{40}$/.test(sha)) {
113
- throw new Error(`Failed to get commit SHA after push (got: "${sha}")`);
114
- }
115
- return sha;
116
- }
117
- /**
118
- * Construct a GitHub blob URL with full SHA for an image in .pre-post/.
119
- * Blob URLs are same-origin on GitHub — the markdown renderer resolves them
120
- * with the viewer's auth, so they work for both public and private repos.
121
- */
122
- function buildBlobUrl(ownerRepo, sha, filename) {
123
- return `https://github.com/${ownerRepo}/blob/${sha}/.pre-post/${filename}?raw=true`;
124
- }
125
- /**
126
- * Upload before/after images and return URLs.
127
- * When uploadUrl is provided, uses the HTTP-based upload path.
128
- * Otherwise, uses git-native (commit to .pre-post/) with blob+SHA URLs.
129
- */
130
- export async function uploadBeforeAfter(before, after, uploadUrl) {
131
- // If an explicit upload URL is provided, use HTTP upload
132
- if (uploadUrl) {
133
- const [beforeUrl, afterUrl] = await Promise.all([
134
- uploadImage(before.image, before.filename, uploadUrl),
135
- uploadImage(after.image, after.filename, uploadUrl),
136
- ]);
137
- return { beforeUrl, afterUrl };
138
- }
139
- // Default: git-native with blob+SHA URLs.
140
- // Works for both public and private repos — blob URLs are same-origin on GitHub,
141
- // so the markdown renderer resolves them with the viewer's authentication context.
142
- const beforeResult = uploadGitNative(before.image, before.filename);
143
- const afterResult = uploadGitNative(after.image, after.filename);
144
- const sha = commitAndPushScreenshots();
145
- const beforeUrl = buildBlobUrl(beforeResult.ownerRepo, sha, beforeResult.filename);
146
- const afterUrl = buildBlobUrl(afterResult.ownerRepo, sha, afterResult.filename);
147
- return { beforeUrl, afterUrl };
148
- }
149
- //# sourceMappingURL=upload.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
@@ -1,36 +0,0 @@
1
- #!/bin/bash
2
- # 0x0.st adapter - Free, no-signup file hosting
3
- # https://0x0.st
4
- #
5
- # Usage: ./0x0st.sh <file>
6
- # Output: URL to uploaded file (stdout)
7
- #
8
- # Notes:
9
- # - Files expire after 365 days (or sooner for larger files)
10
- # - Max file size: 512 MiB
11
- # - No authentication required
12
-
13
- set -e
14
-
15
- FILE="$1"
16
-
17
- if [[ -z "$FILE" ]]; then
18
- echo "Usage: $0 <file>" >&2
19
- exit 1
20
- fi
21
-
22
- if [[ ! -f "$FILE" ]]; then
23
- echo "Error: File not found: $FILE" >&2
24
- exit 1
25
- fi
26
-
27
- # Upload to 0x0.st - returns the URL directly
28
- URL=$(curl -s -A "before-after-cli/1.0" -F "file=@$FILE" https://0x0.st)
29
-
30
- # Validate we got a URL back
31
- if [[ ! "$URL" =~ ^https?:// ]]; then
32
- echo "Error: Upload failed. Response: $URL" >&2
33
- exit 1
34
- fi
35
-
36
- echo "$URL"
@@ -1,55 +0,0 @@
1
- #!/bin/bash
2
- # Custom blob storage adapter - For self-hosted or custom endpoints
3
- #
4
- # Usage: ./blob.sh <file>
5
- # Output: URL to uploaded file (stdout)
6
- #
7
- # Environment:
8
- # BLOB_UPLOAD_URL Required. URL endpoint for uploading images
9
- #
10
- # Notes:
11
- # - Expects endpoint to accept multipart form upload with "file" field
12
- # - Expects response to be either:
13
- # - Plain text URL
14
- # - JSON with "url" field
15
-
16
- set -e
17
-
18
- FILE="$1"
19
-
20
- if [[ -z "$FILE" ]]; then
21
- echo "Usage: $0 <file>" >&2
22
- exit 1
23
- fi
24
-
25
- if [[ ! -f "$FILE" ]]; then
26
- echo "Error: File not found: $FILE" >&2
27
- exit 1
28
- fi
29
-
30
- if [[ -z "$BLOB_UPLOAD_URL" ]]; then
31
- echo "Error: BLOB_UPLOAD_URL environment variable not set" >&2
32
- echo "" >&2
33
- echo "Set it with:" >&2
34
- echo " export BLOB_UPLOAD_URL='https://your-blob-service.com/upload'" >&2
35
- exit 1
36
- fi
37
-
38
- # Upload file
39
- RESPONSE=$(curl -s -X POST -F "file=@$FILE" "$BLOB_UPLOAD_URL")
40
-
41
- # Try to extract URL from JSON response
42
- URL=$(echo "$RESPONSE" | grep -o '"url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"url"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//' || true)
43
-
44
- # Fallback: maybe the response is just the URL
45
- if [[ -z "$URL" ]]; then
46
- URL="$RESPONSE"
47
- fi
48
-
49
- # Validate we got a URL back
50
- if [[ ! "$URL" =~ ^https?:// ]]; then
51
- echo "Error: Upload failed. Response: $RESPONSE" >&2
52
- exit 1
53
- fi
54
-
55
- echo "$URL"
@@ -1,51 +0,0 @@
1
- #!/bin/bash
2
- # Git-native adapter - Stage a screenshot in .pre-post/ on the PR branch
3
- #
4
- # Usage: ./git-native.sh <file>
5
- # Output: filename (stdout)
6
- #
7
- # Requirements:
8
- # - Inside a git repo
9
- #
10
- # Notes:
11
- # - Stages only the specific file, never `git add .`
12
- # - Does NOT commit or push — the caller (upload-and-copy.sh) batches that
13
- # - Returns filename only — URL is constructed by upload-and-copy.sh after
14
- # commit, using the SHA for blob URLs that work on public + private repos
15
-
16
- set -e
17
-
18
- FILE="$1"
19
-
20
- if [[ -z "$FILE" ]]; then
21
- echo "Usage: $0 <file>" >&2
22
- exit 1
23
- fi
24
-
25
- if [[ ! -f "$FILE" ]]; then
26
- echo "Error: File not found: $FILE" >&2
27
- exit 1
28
- fi
29
-
30
- # Must be inside a git repo
31
- if ! git rev-parse --is-inside-work-tree &>/dev/null; then
32
- echo "Error: Not inside a git repository" >&2
33
- exit 1
34
- fi
35
-
36
- # Copy file into .pre-post/ at the repo root
37
- REPO_ROOT=$(git rev-parse --show-toplevel)
38
- DEST_DIR="$REPO_ROOT/.pre-post"
39
- mkdir -p "$DEST_DIR"
40
-
41
- FILENAME=$(basename "$FILE")
42
- DEST="$DEST_DIR/$FILENAME"
43
- cp "$FILE" "$DEST"
44
-
45
- # Stage only this specific file (-f to override .gitignore)
46
- git add -f "$DEST"
47
-
48
- echo "Staged: .pre-post/$FILENAME" >&2
49
-
50
- # Return just the filename — URL built by caller after commit
51
- echo "$FILENAME"