@juangadm/pre-post 0.3.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.
- package/README.md +102 -240
- package/dist/bin/cli.js +200 -454
- package/dist/bin/cli.js.map +1 -1
- package/dist/browser.d.ts +42 -27
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +358 -105
- package/dist/browser.js.map +1 -1
- package/dist/capture.d.ts +1 -6
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +12 -36
- package/dist/capture.js.map +1 -1
- package/dist/commands/compare.d.ts +18 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +64 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/detect.d.ts +20 -0
- package/dist/commands/detect.d.ts.map +1 -0
- package/dist/commands/detect.js +19 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +38 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +34 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/pr.d.ts +28 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +159 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/prune.d.ts +10 -0
- package/dist/commands/prune.d.ts.map +1 -0
- package/dist/commands/prune.js +13 -0
- package/dist/commands/prune.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +60 -0
- package/dist/config.js.map +1 -0
- package/dist/diff-pool.d.ts +17 -0
- package/dist/diff-pool.d.ts.map +1 -0
- package/dist/diff-pool.js +89 -0
- package/dist/diff-pool.js.map +1 -0
- package/dist/diff-worker.d.ts +29 -0
- package/dist/diff-worker.d.ts.map +1 -0
- package/dist/diff-worker.js +38 -0
- package/dist/diff-worker.js.map +1 -0
- package/dist/diff.d.ts +37 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +171 -0
- package/dist/diff.js.map +1 -0
- package/dist/doctor.d.ts +38 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +72 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/git.d.ts +23 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +86 -0
- package/dist/git.js.map +1 -0
- package/dist/github.d.ts +73 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +240 -0
- package/dist/github.js.map +1 -0
- package/dist/index.d.ts +22 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -67
- package/dist/index.js.map +1 -1
- package/dist/pkg.d.ts +8 -0
- package/dist/pkg.d.ts.map +1 -0
- package/dist/pkg.js +35 -0
- package/dist/pkg.js.map +1 -0
- package/dist/report.d.ts +20 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +107 -0
- package/dist/report.js.map +1 -0
- package/dist/routes/imports.d.ts +46 -0
- package/dist/routes/imports.d.ts.map +1 -0
- package/dist/routes/imports.js +208 -0
- package/dist/routes/imports.js.map +1 -0
- package/dist/routes/vite.d.ts +15 -0
- package/dist/routes/vite.d.ts.map +1 -0
- package/dist/routes/vite.js +130 -0
- package/dist/routes/vite.js.map +1 -0
- package/dist/routes.d.ts +41 -14
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +271 -141
- package/dist/routes.js.map +1 -1
- package/dist/run.d.ts +36 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +128 -0
- package/dist/run.js.map +1 -0
- package/dist/sessions.d.ts +50 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +74 -0
- package/dist/sessions.js.map +1 -0
- package/dist/types.d.ts +118 -69
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/viewport.d.ts +6 -1
- package/dist/viewport.d.ts.map +1 -1
- package/dist/viewport.js +13 -7
- package/dist/viewport.js.map +1 -1
- package/package.json +21 -14
- package/skill/SKILL.md +31 -229
- package/dist/clipboard.d.ts +0 -9
- package/dist/clipboard.d.ts.map +0 -1
- package/dist/clipboard.js +0 -26
- package/dist/clipboard.js.map +0 -1
- package/dist/upload.d.ts +0 -39
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -159
- package/dist/upload.js.map +0 -1
- package/dist/video.d.ts +0 -19
- package/dist/video.d.ts.map +0 -1
- package/dist/video.js +0 -263
- package/dist/video.js.map +0 -1
- package/skill/scripts/adapters/0x0st.sh +0 -36
- package/skill/scripts/adapters/blob.sh +0 -55
- package/skill/scripts/adapters/git-native.sh +0 -51
- package/skill/scripts/upload-and-copy.sh +0 -230
package/dist/video.js
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animated GIF capture via sequential Playwright screenshots + gifenc encoding.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline: PNG screenshots → canvas decode → gifenc quantize → animated GIF.
|
|
5
|
-
* Single codepath. No FFmpeg. No external dependencies beyond Playwright + gifenc.
|
|
6
|
-
*/
|
|
7
|
-
import { getBrowser } from './browser.js';
|
|
8
|
-
import { createRequire } from 'module';
|
|
9
|
-
// gifenc ships as CJS — use createRequire for ESM compat
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
let gifencModule = null;
|
|
12
|
-
function getGifenc() {
|
|
13
|
-
if (gifencModule)
|
|
14
|
-
return gifencModule;
|
|
15
|
-
try {
|
|
16
|
-
gifencModule = require('gifenc');
|
|
17
|
-
return gifencModule;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new Error('GIF capture requires the "gifenc" dependency for --video captures. Install dependencies with `npx pnpm install`.');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
// Mobile viewport height cap for GIF mode (above-the-fold, not comically tall)
|
|
24
|
-
const MOBILE_GIF_HEIGHT = 667;
|
|
25
|
-
const DEFAULT_DURATION_SECONDS = 3;
|
|
26
|
-
const DEFAULT_FPS = 5;
|
|
27
|
-
const MAX_DURATION_SECONDS = 10;
|
|
28
|
-
const MAX_FPS = 10;
|
|
29
|
-
const MIN_FPS = 1;
|
|
30
|
-
// ============================================================
|
|
31
|
-
// Canvas decoder page (reused across frames)
|
|
32
|
-
// ============================================================
|
|
33
|
-
/**
|
|
34
|
-
* Decode an image buffer to raw RGBA pixels at target dimensions.
|
|
35
|
-
* Uses a hidden Playwright page with canvas for decoding.
|
|
36
|
-
*/
|
|
37
|
-
async function decodeImageToRgba(decoderPage, imageBuffer, width, height) {
|
|
38
|
-
const pg = decoderPage;
|
|
39
|
-
const base64 = imageBuffer.toString('base64');
|
|
40
|
-
const base64Rgba = await pg.evaluate(async ({ b64, w, h }) => {
|
|
41
|
-
const img = new Image();
|
|
42
|
-
await new Promise((resolve, reject) => {
|
|
43
|
-
img.onload = () => resolve();
|
|
44
|
-
img.onerror = reject;
|
|
45
|
-
img.src = `data:image/png;base64,${b64}`;
|
|
46
|
-
});
|
|
47
|
-
const canvas = document.getElementById('c');
|
|
48
|
-
canvas.width = w;
|
|
49
|
-
canvas.height = h;
|
|
50
|
-
const ctx = canvas.getContext('2d');
|
|
51
|
-
ctx.drawImage(img, 0, 0, w, h);
|
|
52
|
-
const bytes = ctx.getImageData(0, 0, w, h).data;
|
|
53
|
-
// Transfer as base64 instead of JSON array (~5MB vs ~12MB per frame)
|
|
54
|
-
const parts = [];
|
|
55
|
-
for (let i = 0; i < bytes.length; i += 8192) {
|
|
56
|
-
parts.push(String.fromCharCode(...bytes.subarray(i, Math.min(i + 8192, bytes.length))));
|
|
57
|
-
}
|
|
58
|
-
return btoa(parts.join(''));
|
|
59
|
-
}, { b64: base64, w: width, h: height });
|
|
60
|
-
return new Uint8Array(Buffer.from(base64Rgba, 'base64'));
|
|
61
|
-
}
|
|
62
|
-
// ============================================================
|
|
63
|
-
// Core capture function
|
|
64
|
-
// ============================================================
|
|
65
|
-
/**
|
|
66
|
-
* Capture animated GIF frames from an existing Playwright Page.
|
|
67
|
-
* Caller manages page lifecycle.
|
|
68
|
-
*/
|
|
69
|
-
export async function captureGif(page, url, options) {
|
|
70
|
-
const duration = normalizeDuration(options.duration);
|
|
71
|
-
const fps = normalizeFps(options.fps);
|
|
72
|
-
const delay = options.delay ?? 0;
|
|
73
|
-
const totalFrames = Math.ceil(duration * fps);
|
|
74
|
-
const frameInterval = Math.round(1000 / fps);
|
|
75
|
-
// Navigate — do NOT kill animations
|
|
76
|
-
const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
77
|
-
await Promise.race([
|
|
78
|
-
page.waitForLoadState('networkidle'),
|
|
79
|
-
page.waitForTimeout(3000),
|
|
80
|
-
]);
|
|
81
|
-
// Warn on HTTP errors (but continue — page may still have content)
|
|
82
|
-
if (response && response.status() >= 400) {
|
|
83
|
-
console.warn(`Warning: ${url} returned HTTP ${response.status()}`);
|
|
84
|
-
}
|
|
85
|
-
// Wait for fonts (capped at 2s)
|
|
86
|
-
await Promise.race([
|
|
87
|
-
page.evaluate(() => document.fonts.ready),
|
|
88
|
-
page.waitForTimeout(2000),
|
|
89
|
-
]);
|
|
90
|
-
// Optional delay before recording
|
|
91
|
-
if (delay > 0) {
|
|
92
|
-
await page.waitForTimeout(delay);
|
|
93
|
-
}
|
|
94
|
-
// Scroll selector into view if specified
|
|
95
|
-
if (options.selector) {
|
|
96
|
-
const locator = page.locator(options.selector);
|
|
97
|
-
const count = await locator.count();
|
|
98
|
-
if (count === 0) {
|
|
99
|
-
throw new Error(`Element not found: ${options.selector}`);
|
|
100
|
-
}
|
|
101
|
-
await locator.first().scrollIntoViewIfNeeded();
|
|
102
|
-
}
|
|
103
|
-
// Capture PNG frames
|
|
104
|
-
const frames = [];
|
|
105
|
-
let identicalCount = 0;
|
|
106
|
-
for (let i = 0; i < totalFrames; i++) {
|
|
107
|
-
const screenshot = await page.screenshot({ type: 'png' });
|
|
108
|
-
const frame = Buffer.from(screenshot);
|
|
109
|
-
// Early-stop: 3 consecutive identical frames means animation has settled
|
|
110
|
-
if (frames.length > 0 && frame.equals(frames[frames.length - 1])) {
|
|
111
|
-
identicalCount++;
|
|
112
|
-
if (identicalCount >= 3) {
|
|
113
|
-
console.log(`Animation settled after ${i} frames`);
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
identicalCount = 0;
|
|
119
|
-
}
|
|
120
|
-
frames.push(frame);
|
|
121
|
-
if (i < totalFrames - 1) {
|
|
122
|
-
await page.waitForTimeout(frameInterval);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Encode GIF
|
|
126
|
-
const browser = page.context().browser();
|
|
127
|
-
if (!browser) {
|
|
128
|
-
throw new Error('Cannot encode GIF: browser instance is not available on this page context.');
|
|
129
|
-
}
|
|
130
|
-
const gifBuffer = await encodeGif(browser, frames, options.viewport, fps);
|
|
131
|
-
return {
|
|
132
|
-
gif: gifBuffer,
|
|
133
|
-
format: 'gif',
|
|
134
|
-
viewport: options.viewport,
|
|
135
|
-
url,
|
|
136
|
-
duration,
|
|
137
|
-
frameCount: frames.length,
|
|
138
|
-
selector: options.selector,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
function normalizeDuration(duration) {
|
|
142
|
-
if (duration === undefined)
|
|
143
|
-
return DEFAULT_DURATION_SECONDS;
|
|
144
|
-
if (!Number.isFinite(duration)) {
|
|
145
|
-
throw new Error('Duration must be a finite number.');
|
|
146
|
-
}
|
|
147
|
-
return Math.min(MAX_DURATION_SECONDS, Math.max(0.1, duration));
|
|
148
|
-
}
|
|
149
|
-
function normalizeFps(fps) {
|
|
150
|
-
if (fps === undefined)
|
|
151
|
-
return DEFAULT_FPS;
|
|
152
|
-
if (!Number.isFinite(fps)) {
|
|
153
|
-
throw new Error('FPS must be a finite number.');
|
|
154
|
-
}
|
|
155
|
-
return Math.min(MAX_FPS, Math.max(MIN_FPS, Math.round(fps)));
|
|
156
|
-
}
|
|
157
|
-
// ============================================================
|
|
158
|
-
// High-level entry point
|
|
159
|
-
// ============================================================
|
|
160
|
-
/**
|
|
161
|
-
* Capture an animated GIF of a URL.
|
|
162
|
-
* Creates its own page at DPR 1 (no retina), captures, cleans up.
|
|
163
|
-
*/
|
|
164
|
-
export async function captureVideo(url, options) {
|
|
165
|
-
const browser = await getBrowser();
|
|
166
|
-
// For mobile, cap height at 667px for GIF mode
|
|
167
|
-
const viewport = { ...options.viewport };
|
|
168
|
-
if (viewport.width <= 430 && viewport.height > MOBILE_GIF_HEIGHT) {
|
|
169
|
-
viewport.height = MOBILE_GIF_HEIGHT;
|
|
170
|
-
}
|
|
171
|
-
const page = await browser.newPage({
|
|
172
|
-
viewport,
|
|
173
|
-
deviceScaleFactor: 1, // DPR 1 — retina wastes pixels in 256-color GIF
|
|
174
|
-
});
|
|
175
|
-
try {
|
|
176
|
-
return await captureGif(page, url, { ...options, viewport });
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
await page.close();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// ============================================================
|
|
183
|
-
// GIF encoding
|
|
184
|
-
// ============================================================
|
|
185
|
-
/**
|
|
186
|
-
* Encode an array of screenshot Buffers into an animated GIF.
|
|
187
|
-
*/
|
|
188
|
-
async function encodeGif(browser, frames, viewport, fps) {
|
|
189
|
-
const { GIFEncoder, quantize, applyPalette } = getGifenc();
|
|
190
|
-
const width = viewport.width;
|
|
191
|
-
const height = viewport.height;
|
|
192
|
-
const delayCs = Math.round(1000 / fps / 10); // GIF delay is in centiseconds
|
|
193
|
-
const encoder = GIFEncoder();
|
|
194
|
-
const rgbaFrames = [];
|
|
195
|
-
const decoderPage = await browser.newPage();
|
|
196
|
-
await decoderPage.setContent('<canvas id="c"></canvas>');
|
|
197
|
-
try {
|
|
198
|
-
for (let i = 0; i < frames.length; i++) {
|
|
199
|
-
console.log(`Decoding frame ${i + 1}/${frames.length}...`);
|
|
200
|
-
rgbaFrames.push(await decodeImageToRgba(decoderPage, frames[i], width, height));
|
|
201
|
-
}
|
|
202
|
-
const palette = quantize(buildPaletteSample(rgbaFrames), 256, { format: 'rgba4444' });
|
|
203
|
-
let previousIndexed = null;
|
|
204
|
-
let duplicateRunLength = 0;
|
|
205
|
-
for (let i = 0; i < rgbaFrames.length; i++) {
|
|
206
|
-
const indexed = applyPalette(rgbaFrames[i], palette, 'rgba4444');
|
|
207
|
-
if (!previousIndexed) {
|
|
208
|
-
previousIndexed = indexed;
|
|
209
|
-
duplicateRunLength = 1;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (indexedEquals(previousIndexed, indexed)) {
|
|
213
|
-
duplicateRunLength++;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
encoder.writeFrame(previousIndexed, width, height, {
|
|
217
|
-
palette,
|
|
218
|
-
delay: delayCs * duplicateRunLength,
|
|
219
|
-
repeat: 0,
|
|
220
|
-
});
|
|
221
|
-
previousIndexed = indexed;
|
|
222
|
-
duplicateRunLength = 1;
|
|
223
|
-
}
|
|
224
|
-
if (previousIndexed) {
|
|
225
|
-
encoder.writeFrame(previousIndexed, width, height, {
|
|
226
|
-
palette,
|
|
227
|
-
delay: delayCs * duplicateRunLength,
|
|
228
|
-
repeat: 0,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
encoder.finish();
|
|
232
|
-
return Buffer.from(encoder.bytes());
|
|
233
|
-
}
|
|
234
|
-
finally {
|
|
235
|
-
if (!decoderPage.isClosed()) {
|
|
236
|
-
await decoderPage.close();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
function buildPaletteSample(frames) {
|
|
241
|
-
if (frames.length === 0)
|
|
242
|
-
return new Uint8Array(0);
|
|
243
|
-
const stride = 4 * 4;
|
|
244
|
-
const samplesPerFrame = Math.ceil(frames[0].length / stride);
|
|
245
|
-
const sample = new Uint8Array(samplesPerFrame * frames.length * 4);
|
|
246
|
-
let offset = 0;
|
|
247
|
-
for (const frame of frames) {
|
|
248
|
-
for (let i = 0; i < frame.length; i += stride) {
|
|
249
|
-
sample[offset++] = frame[i];
|
|
250
|
-
sample[offset++] = frame[i + 1];
|
|
251
|
-
sample[offset++] = frame[i + 2];
|
|
252
|
-
sample[offset++] = frame[i + 3];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return sample;
|
|
256
|
-
}
|
|
257
|
-
function indexedEquals(a, b) {
|
|
258
|
-
if (a.length !== b.length)
|
|
259
|
-
return false;
|
|
260
|
-
return Buffer.from(a.buffer, a.byteOffset, a.byteLength)
|
|
261
|
-
.equals(Buffer.from(b.buffer, b.byteOffset, b.byteLength));
|
|
262
|
-
}
|
|
263
|
-
//# sourceMappingURL=video.js.map
|
package/dist/video.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.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"
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# upload-and-copy.sh - Upload images via pluggable storage adapters
|
|
3
|
-
# Usage: ./upload-and-copy.sh <before.png> <after.png> [--markdown]
|
|
4
|
-
#
|
|
5
|
-
# Options:
|
|
6
|
-
# --markdown Generate PR markdown table and copy to clipboard
|
|
7
|
-
#
|
|
8
|
-
# Environment:
|
|
9
|
-
# IMAGE_ADAPTER Storage adapter to use (default: git-native)
|
|
10
|
-
# Available: git-native, 0x0st, blob
|
|
11
|
-
#
|
|
12
|
-
# Adapter-specific environment variables:
|
|
13
|
-
# blob: BLOB_UPLOAD_URL - Custom upload endpoint
|
|
14
|
-
#
|
|
15
|
-
# Examples:
|
|
16
|
-
# ./upload-and-copy.sh before.png after.png
|
|
17
|
-
# ./upload-and-copy.sh before.png after.png --markdown
|
|
18
|
-
# IMAGE_ADAPTER=0x0st ./upload-and-copy.sh before.png after.png --markdown
|
|
19
|
-
|
|
20
|
-
set -e
|
|
21
|
-
|
|
22
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
-
|
|
24
|
-
# Resolve owner/repo from env vars or git remote URL (supports proxy URLs)
|
|
25
|
-
resolve_owner_repo() {
|
|
26
|
-
if [[ -n "${GH_REPO:-}" ]]; then echo "$GH_REPO"; return; fi
|
|
27
|
-
if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then echo "$GITHUB_REPOSITORY"; return; fi
|
|
28
|
-
|
|
29
|
-
local url
|
|
30
|
-
url=$(git remote get-url origin 2>/dev/null) || true
|
|
31
|
-
|
|
32
|
-
# Standard GitHub HTTPS/SSH
|
|
33
|
-
local std
|
|
34
|
-
std=$(echo "$url" | sed -nE 's#.*github\.com[/:]([^/]+/[^/]+?)(\.git)?$#\1#p')
|
|
35
|
-
if [[ -n "$std" ]]; then echo "$std"; return; fi
|
|
36
|
-
|
|
37
|
-
# Proxy fallback: last two path segments
|
|
38
|
-
local proxy
|
|
39
|
-
proxy=$(echo "$url" | sed -nE 's#.*/([^/]+/[^/]+?)(\.git)?$#\1#p')
|
|
40
|
-
if [[ -n "$proxy" ]]; then echo "$proxy"; return; fi
|
|
41
|
-
|
|
42
|
-
echo "Error: Cannot parse owner/repo. Set GH_REPO=owner/repo" >&2
|
|
43
|
-
exit 1
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# Check if gh CLI is installed and authenticated
|
|
47
|
-
gh_is_authenticated() {
|
|
48
|
-
command -v gh &>/dev/null && gh auth status &>/dev/null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# Copy text to system clipboard (macOS or Linux)
|
|
52
|
-
copy_to_clipboard() {
|
|
53
|
-
local text="$1"
|
|
54
|
-
local label="${2:-Text}"
|
|
55
|
-
if command -v pbcopy &>/dev/null; then
|
|
56
|
-
echo "$text" | pbcopy
|
|
57
|
-
echo "$label copied to clipboard!"
|
|
58
|
-
elif command -v xclip &>/dev/null; then
|
|
59
|
-
echo "$text" | xclip -selection clipboard
|
|
60
|
-
echo "$label copied to clipboard!"
|
|
61
|
-
else
|
|
62
|
-
echo "(clipboard copy not available - install pbcopy or xclip)"
|
|
63
|
-
fi
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
ADAPTERS_DIR="$SCRIPT_DIR/adapters"
|
|
67
|
-
|
|
68
|
-
# Default adapter
|
|
69
|
-
IMAGE_ADAPTER="${IMAGE_ADAPTER:-git-native}"
|
|
70
|
-
|
|
71
|
-
BEFORE_FILE="$1"
|
|
72
|
-
AFTER_FILE="$2"
|
|
73
|
-
shift 2 2>/dev/null || true
|
|
74
|
-
|
|
75
|
-
MARKDOWN_MODE=false
|
|
76
|
-
|
|
77
|
-
# Parse options
|
|
78
|
-
while [[ $# -gt 0 ]]; do
|
|
79
|
-
case $1 in
|
|
80
|
-
--markdown)
|
|
81
|
-
MARKDOWN_MODE=true
|
|
82
|
-
shift
|
|
83
|
-
;;
|
|
84
|
-
*)
|
|
85
|
-
echo "Unknown option: $1"
|
|
86
|
-
exit 1
|
|
87
|
-
;;
|
|
88
|
-
esac
|
|
89
|
-
done
|
|
90
|
-
|
|
91
|
-
# Validate inputs
|
|
92
|
-
if [[ -z "$BEFORE_FILE" || -z "$AFTER_FILE" ]]; then
|
|
93
|
-
echo "Usage: $0 <before.png> <after.png> [--markdown]"
|
|
94
|
-
echo ""
|
|
95
|
-
echo "Environment:"
|
|
96
|
-
echo " IMAGE_ADAPTER Storage adapter (default: git-native)"
|
|
97
|
-
echo " Available: git-native, 0x0st, blob"
|
|
98
|
-
exit 1
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
if [[ ! -f "$BEFORE_FILE" ]]; then
|
|
102
|
-
echo "Error: Before file not found: $BEFORE_FILE"
|
|
103
|
-
exit 1
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
if [[ ! -f "$AFTER_FILE" ]]; then
|
|
107
|
-
echo "Error: After file not found: $AFTER_FILE"
|
|
108
|
-
exit 1
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# Validate adapter exists
|
|
112
|
-
ADAPTER_SCRIPT="$ADAPTERS_DIR/$IMAGE_ADAPTER.sh"
|
|
113
|
-
if [[ ! -f "$ADAPTER_SCRIPT" ]]; then
|
|
114
|
-
echo "Error: Unknown adapter: $IMAGE_ADAPTER"
|
|
115
|
-
echo ""
|
|
116
|
-
echo "Available adapters:"
|
|
117
|
-
for adapter in "$ADAPTERS_DIR"/*.sh; do
|
|
118
|
-
name=$(basename "$adapter" .sh)
|
|
119
|
-
echo " - $name"
|
|
120
|
-
done
|
|
121
|
-
exit 1
|
|
122
|
-
fi
|
|
123
|
-
|
|
124
|
-
# Upload function using adapter
|
|
125
|
-
# NOTE: git-native adapter returns a filename (not a URL) — the URL is
|
|
126
|
-
# constructed below after commit+push, using the SHA for blob URLs.
|
|
127
|
-
upload_file() {
|
|
128
|
-
local file="$1"
|
|
129
|
-
local filename=$(basename "$file")
|
|
130
|
-
|
|
131
|
-
echo "Uploading: $filename (via $IMAGE_ADAPTER)" >&2
|
|
132
|
-
"$ADAPTER_SCRIPT" "$file"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# Upload both files (adapter returns filename for git-native, URL for others)
|
|
136
|
-
echo "=== Uploading Screenshots ==="
|
|
137
|
-
BEFORE_RESULT=$(upload_file "$BEFORE_FILE")
|
|
138
|
-
AFTER_RESULT=$(upload_file "$AFTER_FILE")
|
|
139
|
-
|
|
140
|
-
# For git-native: commit, push, then construct blob+SHA URLs.
|
|
141
|
-
# Blob URLs work for both public and private repos (same-origin on GitHub).
|
|
142
|
-
if [[ "$IMAGE_ADAPTER" == "git-native" ]]; then
|
|
143
|
-
echo "Committing and pushing screenshots..." >&2
|
|
144
|
-
git commit -m "chore: add pre/post screenshots"
|
|
145
|
-
git push origin HEAD
|
|
146
|
-
|
|
147
|
-
SHA=$(git rev-parse HEAD)
|
|
148
|
-
if [[ ! "$SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
|
149
|
-
echo "Error: Could not determine commit SHA after push (got: '$SHA')" >&2
|
|
150
|
-
exit 1
|
|
151
|
-
fi
|
|
152
|
-
|
|
153
|
-
OWNER_REPO=$(resolve_owner_repo)
|
|
154
|
-
|
|
155
|
-
BEFORE_URL="https://github.com/$OWNER_REPO/blob/$SHA/.pre-post/$BEFORE_RESULT?raw=true"
|
|
156
|
-
AFTER_URL="https://github.com/$OWNER_REPO/blob/$SHA/.pre-post/$AFTER_RESULT?raw=true"
|
|
157
|
-
else
|
|
158
|
-
# Other adapters (0x0st, blob) return full URLs directly
|
|
159
|
-
BEFORE_URL="$BEFORE_RESULT"
|
|
160
|
-
AFTER_URL="$AFTER_RESULT"
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
echo ""
|
|
164
|
-
echo "Before URL: $BEFORE_URL"
|
|
165
|
-
echo "After URL: $AFTER_URL"
|
|
166
|
-
echo ""
|
|
167
|
-
|
|
168
|
-
if [[ "$MARKDOWN_MODE" != "true" ]]; then
|
|
169
|
-
copy_to_clipboard "Before: $BEFORE_URL
|
|
170
|
-
After: $AFTER_URL" "URLs"
|
|
171
|
-
exit 0
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
# Build section with commit reference
|
|
175
|
-
SHORT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
176
|
-
TIMESTAMP=$(date "+%Y-%m-%d %H:%M")
|
|
177
|
-
|
|
178
|
-
SECTION="### Screenshots ($SHORT_SHA — $TIMESTAMP)
|
|
179
|
-
|
|
180
|
-
| Pre | Post |
|
|
181
|
-
|:---:|:----:|
|
|
182
|
-
|  |  |"
|
|
183
|
-
|
|
184
|
-
echo "=== PR Markdown ==="
|
|
185
|
-
echo "$SECTION"
|
|
186
|
-
echo ""
|
|
187
|
-
|
|
188
|
-
# Auto-append to PR body if gh CLI is available and authenticated
|
|
189
|
-
GH_REPO=$(resolve_owner_repo)
|
|
190
|
-
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
|
191
|
-
|
|
192
|
-
if ! gh_is_authenticated || [[ -z "$GH_REPO" ]] || [[ -z "$CURRENT_BRANCH" ]]; then
|
|
193
|
-
if command -v gh &>/dev/null && ! gh_is_authenticated; then
|
|
194
|
-
echo "(gh CLI found but not authenticated — run 'gh auth login' to enable auto PR updates)"
|
|
195
|
-
fi
|
|
196
|
-
echo "Paste the markdown above into your PR description."
|
|
197
|
-
copy_to_clipboard "$SECTION" "Markdown"
|
|
198
|
-
exit 0
|
|
199
|
-
fi
|
|
200
|
-
|
|
201
|
-
PR_JSON=$(gh pr view "$CURRENT_BRANCH" --repo "$GH_REPO" --json number,body 2>/dev/null || true)
|
|
202
|
-
|
|
203
|
-
if [[ -z "$PR_JSON" ]]; then
|
|
204
|
-
echo "(no PR found for branch '$CURRENT_BRANCH' — create a PR first, then paste the markdown above into the description)"
|
|
205
|
-
copy_to_clipboard "$SECTION" "Markdown"
|
|
206
|
-
exit 0
|
|
207
|
-
fi
|
|
208
|
-
|
|
209
|
-
PR_NUMBER=$(echo "$PR_JSON" | grep -o '"number":[0-9]*' | grep -o '[0-9]*')
|
|
210
|
-
PR_BODY=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))" 2>/dev/null || echo "")
|
|
211
|
-
|
|
212
|
-
# Stack: newest on top, right after ## Visual Changes header
|
|
213
|
-
NEW_BODY=$(python3 -c "
|
|
214
|
-
import sys
|
|
215
|
-
body = sys.argv[1]
|
|
216
|
-
section = sys.argv[2]
|
|
217
|
-
marker = '## Visual Changes'
|
|
218
|
-
if marker in body:
|
|
219
|
-
# Insert new section right after header, push older ones down
|
|
220
|
-
parts = body.split(marker, 1)
|
|
221
|
-
print(parts[0] + marker + '\n\n' + section + '\n\n---' + parts[1])
|
|
222
|
-
else:
|
|
223
|
-
# First time
|
|
224
|
-
print(body + '\n\n' + marker + '\n\n' + section)
|
|
225
|
-
" "$PR_BODY" "$SECTION")
|
|
226
|
-
|
|
227
|
-
gh pr edit "$PR_NUMBER" --repo "$GH_REPO" --body "$NEW_BODY"
|
|
228
|
-
echo "Screenshots appended to PR #$PR_NUMBER body"
|
|
229
|
-
|
|
230
|
-
copy_to_clipboard "$SECTION" "Markdown"
|