afterbefore 0.2.9 → 0.2.11
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/dist/{chunk-A73KCX5G.js → chunk-BFC2HVPX.js} +111 -43
- package/dist/chunk-BFC2HVPX.js.map +1 -0
- package/dist/overlay/index.js +285 -125
- package/dist/overlay/index.js.map +1 -1
- package/dist/server/middleware.js +14 -2
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/route.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-A73KCX5G.js.map +0 -1
|
@@ -1,13 +1,80 @@
|
|
|
1
|
-
// src/server/
|
|
2
|
-
import {
|
|
3
|
-
import { writeFile } from "fs/promises";
|
|
1
|
+
// src/server/config.ts
|
|
2
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4
3
|
import { join } from "path";
|
|
5
4
|
import { homedir } from "os";
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
6
|
import { execFile } from "child_process";
|
|
7
7
|
import { promisify } from "util";
|
|
8
8
|
var execFileAsync = promisify(execFile);
|
|
9
|
+
var CONFIG_DIR = join(process.cwd(), ".afterbefore");
|
|
10
|
+
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
11
|
+
var DEFAULT_SAVE_DIR = join(homedir(), "Desktop");
|
|
12
|
+
async function readConfig() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await readFile(CONFIG_PATH, "utf-8");
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
return { saveDir: parsed.saveDir || DEFAULT_SAVE_DIR };
|
|
17
|
+
} catch {
|
|
18
|
+
return { saveDir: DEFAULT_SAVE_DIR };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function writeConfig(config) {
|
|
22
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
23
|
+
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
24
|
+
}
|
|
25
|
+
async function getSaveDir() {
|
|
26
|
+
const config = await readConfig();
|
|
27
|
+
return config.saveDir;
|
|
28
|
+
}
|
|
29
|
+
async function handleGetConfig() {
|
|
30
|
+
const config = await readConfig();
|
|
31
|
+
return NextResponse.json(config);
|
|
32
|
+
}
|
|
33
|
+
async function handleSetConfig(req) {
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = await req.json();
|
|
37
|
+
} catch {
|
|
38
|
+
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
if (typeof body.saveDir !== "string" || body.saveDir.trim() === "") {
|
|
41
|
+
return NextResponse.json({ error: "Invalid saveDir" }, { status: 400 });
|
|
42
|
+
}
|
|
43
|
+
const config = { saveDir: body.saveDir.trim() };
|
|
44
|
+
await writeConfig(config);
|
|
45
|
+
return NextResponse.json(config);
|
|
46
|
+
}
|
|
47
|
+
async function handlePickFolder() {
|
|
48
|
+
if (process.platform !== "darwin") {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: "Folder picker is only supported on macOS" },
|
|
51
|
+
{ status: 501 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const { stdout } = await execFileAsync("osascript", [
|
|
56
|
+
"-e",
|
|
57
|
+
'POSIX path of (choose folder with prompt "Select screenshot save location")'
|
|
58
|
+
]);
|
|
59
|
+
const folder = stdout.trim().replace(/\/$/, "");
|
|
60
|
+
if (!folder) {
|
|
61
|
+
return NextResponse.json({ cancelled: true });
|
|
62
|
+
}
|
|
63
|
+
return NextResponse.json({ folder });
|
|
64
|
+
} catch {
|
|
65
|
+
return NextResponse.json({ cancelled: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/server/save.ts
|
|
70
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
71
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
72
|
+
import { join as join2 } from "path";
|
|
73
|
+
import { execFile as execFile2 } from "child_process";
|
|
74
|
+
import { promisify as promisify2 } from "util";
|
|
75
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
9
76
|
async function getBranch() {
|
|
10
|
-
const { stdout } = await
|
|
77
|
+
const { stdout } = await execFileAsync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
11
78
|
cwd: process.cwd()
|
|
12
79
|
});
|
|
13
80
|
return stdout.trim();
|
|
@@ -20,26 +87,26 @@ async function handleSave(req) {
|
|
|
20
87
|
try {
|
|
21
88
|
body = await req.json();
|
|
22
89
|
} catch {
|
|
23
|
-
return
|
|
90
|
+
return NextResponse2.json(
|
|
24
91
|
{ error: "Invalid JSON body" },
|
|
25
92
|
{ status: 400 }
|
|
26
93
|
);
|
|
27
94
|
}
|
|
28
95
|
const { type, mode, image } = body;
|
|
29
96
|
if (!VALID_TYPES.includes(type)) {
|
|
30
|
-
return
|
|
97
|
+
return NextResponse2.json(
|
|
31
98
|
{ error: `Invalid type: must be "before" or "after"` },
|
|
32
99
|
{ status: 400 }
|
|
33
100
|
);
|
|
34
101
|
}
|
|
35
102
|
if (!VALID_MODES.includes(mode)) {
|
|
36
|
-
return
|
|
103
|
+
return NextResponse2.json(
|
|
37
104
|
{ error: `Invalid mode: must be "fullpage", "viewport", or "area"` },
|
|
38
105
|
{ status: 400 }
|
|
39
106
|
);
|
|
40
107
|
}
|
|
41
108
|
if (typeof image !== "string" || !image.startsWith(DATA_URL_PREFIX)) {
|
|
42
|
-
return
|
|
109
|
+
return NextResponse2.json(
|
|
43
110
|
{ error: "Invalid image: must be a data:image/png;base64 data URL" },
|
|
44
111
|
{ status: 400 }
|
|
45
112
|
);
|
|
@@ -47,29 +114,29 @@ async function handleSave(req) {
|
|
|
47
114
|
const base64 = image.slice(DATA_URL_PREFIX.length);
|
|
48
115
|
const buffer = Buffer.from(base64, "base64");
|
|
49
116
|
const branch = await getBranch();
|
|
117
|
+
const saveDir = await getSaveDir();
|
|
50
118
|
const filename = `${branch}-${type}.png`;
|
|
51
|
-
const filepath =
|
|
119
|
+
const filepath = join2(saveDir, filename);
|
|
52
120
|
try {
|
|
53
|
-
await
|
|
121
|
+
await writeFile2(filepath, buffer);
|
|
54
122
|
} catch (err) {
|
|
55
|
-
return
|
|
123
|
+
return NextResponse2.json(
|
|
56
124
|
{ error: "Failed to write screenshot", detail: String(err) },
|
|
57
125
|
{ status: 500 }
|
|
58
126
|
);
|
|
59
127
|
}
|
|
60
|
-
return
|
|
128
|
+
return NextResponse2.json({ success: true, path: filepath });
|
|
61
129
|
}
|
|
62
130
|
|
|
63
131
|
// src/server/push.ts
|
|
64
|
-
import { NextResponse as
|
|
65
|
-
import { execFile as
|
|
66
|
-
import { access, mkdir, copyFile } from "fs/promises";
|
|
67
|
-
import { join as
|
|
68
|
-
import {
|
|
69
|
-
|
|
70
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
132
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
133
|
+
import { execFile as execFile3 } from "child_process";
|
|
134
|
+
import { access, mkdir as mkdir2, copyFile } from "fs/promises";
|
|
135
|
+
import { join as join3 } from "path";
|
|
136
|
+
import { promisify as promisify3 } from "util";
|
|
137
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
71
138
|
async function run(cmd, args) {
|
|
72
|
-
return
|
|
139
|
+
return execFileAsync3(cmd, args, { cwd: process.cwd() });
|
|
73
140
|
}
|
|
74
141
|
async function ghAvailable() {
|
|
75
142
|
try {
|
|
@@ -102,22 +169,22 @@ async function fileExists(path) {
|
|
|
102
169
|
}
|
|
103
170
|
async function handlePush(req) {
|
|
104
171
|
if (!await ghAvailable()) {
|
|
105
|
-
return
|
|
172
|
+
return NextResponse3.json(
|
|
106
173
|
{ success: false, error: "GitHub CLI (gh) is not installed or not in PATH" },
|
|
107
174
|
{ status: 500 }
|
|
108
175
|
);
|
|
109
176
|
}
|
|
110
177
|
const pr = await getPrInfo();
|
|
111
178
|
if (!pr) {
|
|
112
|
-
return
|
|
179
|
+
return NextResponse3.json(
|
|
113
180
|
{ success: false, error: "No PR found for current branch" },
|
|
114
181
|
{ status: 404 }
|
|
115
182
|
);
|
|
116
183
|
}
|
|
117
|
-
const desktop =
|
|
184
|
+
const desktop = await getSaveDir();
|
|
118
185
|
const branch = pr.headRefName;
|
|
119
|
-
const beforePath =
|
|
120
|
-
const afterPath =
|
|
186
|
+
const beforePath = join3(desktop, `${branch}-before.png`);
|
|
187
|
+
const afterPath = join3(desktop, `${branch}-after.png`);
|
|
121
188
|
const [hasBefore, hasAfter] = await Promise.all([
|
|
122
189
|
fileExists(beforePath),
|
|
123
190
|
fileExists(afterPath)
|
|
@@ -127,20 +194,20 @@ async function handlePush(req) {
|
|
|
127
194
|
!hasBefore && `${branch}-before.png`,
|
|
128
195
|
!hasAfter && `${branch}-after.png`
|
|
129
196
|
].filter(Boolean);
|
|
130
|
-
return
|
|
197
|
+
return NextResponse3.json(
|
|
131
198
|
{ success: false, error: `Missing screenshots: ${missing.join(", ")}` },
|
|
132
199
|
{ status: 400 }
|
|
133
200
|
);
|
|
134
201
|
}
|
|
135
|
-
const repoDir =
|
|
136
|
-
const repoBefore =
|
|
137
|
-
const repoAfter =
|
|
202
|
+
const repoDir = join3(process.cwd(), ".afterbefore");
|
|
203
|
+
const repoBefore = join3(repoDir, `${branch}-before.png`);
|
|
204
|
+
const repoAfter = join3(repoDir, `${branch}-after.png`);
|
|
138
205
|
try {
|
|
139
|
-
await
|
|
206
|
+
await mkdir2(repoDir, { recursive: true });
|
|
140
207
|
await copyFile(beforePath, repoBefore);
|
|
141
208
|
await copyFile(afterPath, repoAfter);
|
|
142
209
|
} catch (err) {
|
|
143
|
-
return
|
|
210
|
+
return NextResponse3.json(
|
|
144
211
|
{ success: false, error: "Failed to copy screenshots into repo", detail: String(err) },
|
|
145
212
|
{ status: 500 }
|
|
146
213
|
);
|
|
@@ -155,7 +222,7 @@ async function handlePush(req) {
|
|
|
155
222
|
} catch (err) {
|
|
156
223
|
const msg = String(err);
|
|
157
224
|
if (!msg.includes("nothing to commit")) {
|
|
158
|
-
return
|
|
225
|
+
return NextResponse3.json(
|
|
159
226
|
{ success: false, error: "Failed to commit screenshots", detail: msg },
|
|
160
227
|
{ status: 500 }
|
|
161
228
|
);
|
|
@@ -164,7 +231,7 @@ async function handlePush(req) {
|
|
|
164
231
|
try {
|
|
165
232
|
await run("git", ["push"]);
|
|
166
233
|
} catch (err) {
|
|
167
|
-
return
|
|
234
|
+
return NextResponse3.json(
|
|
168
235
|
{ success: false, error: "Failed to push to remote", detail: String(err) },
|
|
169
236
|
{ status: 500 }
|
|
170
237
|
);
|
|
@@ -193,12 +260,12 @@ async function handlePush(req) {
|
|
|
193
260
|
]);
|
|
194
261
|
commentUrl = stdout.trim() || void 0;
|
|
195
262
|
} catch (err) {
|
|
196
|
-
return
|
|
263
|
+
return NextResponse3.json(
|
|
197
264
|
{ success: false, error: "Failed to post PR comment", detail: String(err) },
|
|
198
265
|
{ status: 500 }
|
|
199
266
|
);
|
|
200
267
|
}
|
|
201
|
-
return
|
|
268
|
+
return NextResponse3.json({
|
|
202
269
|
success: true,
|
|
203
270
|
prNumber: pr.number,
|
|
204
271
|
prUrl: pr.url,
|
|
@@ -207,20 +274,21 @@ async function handlePush(req) {
|
|
|
207
274
|
}
|
|
208
275
|
|
|
209
276
|
// src/server/open.ts
|
|
210
|
-
import { NextResponse as
|
|
211
|
-
import { execFile as
|
|
212
|
-
import { join as join3 } from "path";
|
|
213
|
-
import { homedir as homedir3 } from "os";
|
|
277
|
+
import { NextResponse as NextResponse4 } from "next/server";
|
|
278
|
+
import { execFile as execFile4 } from "child_process";
|
|
214
279
|
async function handleOpen(_req) {
|
|
215
|
-
const desktop =
|
|
280
|
+
const desktop = await getSaveDir();
|
|
216
281
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
|
|
217
|
-
|
|
218
|
-
return
|
|
282
|
+
execFile4(cmd, [desktop]);
|
|
283
|
+
return NextResponse4.json({ success: true });
|
|
219
284
|
}
|
|
220
285
|
|
|
221
286
|
export {
|
|
287
|
+
handleGetConfig,
|
|
288
|
+
handleSetConfig,
|
|
289
|
+
handlePickFolder,
|
|
222
290
|
handleSave,
|
|
223
291
|
handlePush,
|
|
224
292
|
handleOpen
|
|
225
293
|
};
|
|
226
|
-
//# sourceMappingURL=chunk-
|
|
294
|
+
//# sourceMappingURL=chunk-BFC2HVPX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/config.ts","../src/server/save.ts","../src/server/push.ts","../src/server/open.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface Config {\n saveDir: string;\n}\n\nconst CONFIG_DIR = join(process.cwd(), \".afterbefore\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.json\");\nconst DEFAULT_SAVE_DIR = join(homedir(), \"Desktop\");\n\nasync function readConfig(): Promise<Config> {\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw);\n return { saveDir: parsed.saveDir || DEFAULT_SAVE_DIR };\n } catch {\n return { saveDir: DEFAULT_SAVE_DIR };\n }\n}\n\nasync function writeConfig(config: Config): Promise<void> {\n await mkdir(CONFIG_DIR, { recursive: true });\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\nexport async function getSaveDir(): Promise<string> {\n const config = await readConfig();\n return config.saveDir;\n}\n\nexport async function handleGetConfig(): Promise<NextResponse> {\n const config = await readConfig();\n return NextResponse.json(config);\n}\n\nexport async function handleSetConfig(req: NextRequest): Promise<NextResponse> {\n let body: Partial<Config>;\n try {\n body = await req.json();\n } catch {\n return NextResponse.json({ error: \"Invalid JSON\" }, { status: 400 });\n }\n\n if (typeof body.saveDir !== \"string\" || body.saveDir.trim() === \"\") {\n return NextResponse.json({ error: \"Invalid saveDir\" }, { status: 400 });\n }\n\n const config: Config = { saveDir: body.saveDir.trim() };\n await writeConfig(config);\n return NextResponse.json(config);\n}\n\nexport async function handlePickFolder(): Promise<NextResponse> {\n if (process.platform !== \"darwin\") {\n return NextResponse.json(\n { error: \"Folder picker is only supported on macOS\" },\n { status: 501 },\n );\n }\n\n try {\n const { stdout } = await execFileAsync(\"osascript\", [\n \"-e\",\n 'POSIX path of (choose folder with prompt \"Select screenshot save location\")',\n ]);\n const folder = stdout.trim().replace(/\\/$/, \"\");\n if (!folder) {\n return NextResponse.json({ cancelled: true });\n }\n return NextResponse.json({ folder });\n } catch {\n // User cancelled the dialog\n return NextResponse.json({ cancelled: true });\n }\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { getSaveDir } from \"./config\";\n\nconst execFileAsync = promisify(execFile);\n\nasync function getBranch(): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], {\n cwd: process.cwd(),\n });\n return stdout.trim();\n}\n\nconst VALID_TYPES = [\"before\", \"after\"] as const;\ntype ScreenshotType = (typeof VALID_TYPES)[number];\n\nconst VALID_MODES = [\"fullpage\", \"viewport\", \"area\", \"component\"] as const;\n\nconst DATA_URL_PREFIX = \"data:image/png;base64,\";\n\ninterface SaveRequestBody {\n type: ScreenshotType;\n mode: string;\n image: string;\n}\n\nexport async function handleSave(req: NextRequest): Promise<NextResponse> {\n let body: SaveRequestBody;\n try {\n body = await req.json();\n } catch {\n return NextResponse.json(\n { error: \"Invalid JSON body\" },\n { status: 400 },\n );\n }\n\n const { type, mode, image } = body;\n\n if (!VALID_TYPES.includes(type as ScreenshotType)) {\n return NextResponse.json(\n { error: `Invalid type: must be \"before\" or \"after\"` },\n { status: 400 },\n );\n }\n\n if (!VALID_MODES.includes(mode as (typeof VALID_MODES)[number])) {\n return NextResponse.json(\n { error: `Invalid mode: must be \"fullpage\", \"viewport\", or \"area\"` },\n { status: 400 },\n );\n }\n\n if (typeof image !== \"string\" || !image.startsWith(DATA_URL_PREFIX)) {\n return NextResponse.json(\n { error: \"Invalid image: must be a data:image/png;base64 data URL\" },\n { status: 400 },\n );\n }\n\n const base64 = image.slice(DATA_URL_PREFIX.length);\n const buffer = Buffer.from(base64, \"base64\");\n\n const branch = await getBranch();\n const saveDir = await getSaveDir();\n const filename = `${branch}-${type}.png`;\n const filepath = join(saveDir, filename);\n\n try {\n await writeFile(filepath, buffer);\n } catch (err) {\n return NextResponse.json(\n { error: \"Failed to write screenshot\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({ success: true, path: filepath });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { access, mkdir, copyFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { getSaveDir } from \"./config\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface PrInfo {\n number: number;\n url: string;\n headRepository: { owner: { login: string }; name: string };\n headRefName: string;\n}\n\nasync function run(\n cmd: string,\n args: string[],\n): Promise<{ stdout: string; stderr: string }> {\n return execFileAsync(cmd, args, { cwd: process.cwd() });\n}\n\nasync function ghAvailable(): Promise<boolean> {\n try {\n await run(\"gh\", [\"--version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function getPrInfo(): Promise<PrInfo | null> {\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"view\",\n \"--json\",\n \"number,url,headRepository,headRefName\",\n ]);\n return JSON.parse(stdout) as PrInfo;\n } catch {\n return null;\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function handlePush(req: NextRequest): Promise<NextResponse> {\n // 1. Check gh CLI availability\n if (!(await ghAvailable())) {\n return NextResponse.json(\n { success: false, error: \"GitHub CLI (gh) is not installed or not in PATH\" },\n { status: 500 },\n );\n }\n\n // 2. Check for active PR\n const pr = await getPrInfo();\n if (!pr) {\n return NextResponse.json(\n { success: false, error: \"No PR found for current branch\" },\n { status: 404 },\n );\n }\n\n // 3. Check that screenshot files exist in save directory\n const desktop = await getSaveDir();\n const branch = pr.headRefName;\n const beforePath = join(desktop, `${branch}-before.png`);\n const afterPath = join(desktop, `${branch}-after.png`);\n\n const [hasBefore, hasAfter] = await Promise.all([\n fileExists(beforePath),\n fileExists(afterPath),\n ]);\n\n if (!hasBefore || !hasAfter) {\n const missing = [\n !hasBefore && `${branch}-before.png`,\n !hasAfter && `${branch}-after.png`,\n ].filter(Boolean);\n return NextResponse.json(\n { success: false, error: `Missing screenshots: ${missing.join(\", \")}` },\n { status: 400 },\n );\n }\n\n // 4. Copy from Desktop into repo, stage, commit, and push\n const repoDir = join(process.cwd(), \".afterbefore\");\n const repoBefore = join(repoDir, `${branch}-before.png`);\n const repoAfter = join(repoDir, `${branch}-after.png`);\n\n try {\n await mkdir(repoDir, { recursive: true });\n await copyFile(beforePath, repoBefore);\n await copyFile(afterPath, repoAfter);\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to copy screenshots into repo\", detail: String(err) },\n { status: 500 },\n );\n }\n\n try {\n await run(\"git\", [\"add\", repoBefore, repoAfter]);\n await run(\"git\", [\n \"commit\",\n \"-m\",\n \"chore: add before/after screenshots\",\n ]);\n } catch (err) {\n // Commit may fail if files are already committed with no changes.\n // That's fine -- we still want to push and comment.\n const msg = String(err);\n if (!msg.includes(\"nothing to commit\")) {\n return NextResponse.json(\n { success: false, error: \"Failed to commit screenshots\", detail: msg },\n { status: 500 },\n );\n }\n }\n\n try {\n await run(\"git\", [\"push\"]);\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to push to remote\", detail: String(err) },\n { status: 500 },\n );\n }\n\n // 5. Build raw GitHub URLs for the images\n const owner = pr.headRepository.owner.login;\n const repo = pr.headRepository.name;\n const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;\n\n const beforeUrl = `${rawBase}/.afterbefore/${branch}-before.png`;\n const afterUrl = `${rawBase}/.afterbefore/${branch}-after.png`;\n\n // Cache-bust with timestamp so GitHub doesn't serve stale images\n const ts = Date.now();\n const commentBody = [\n \"## Before / After\",\n \"\",\n \"| Before | After |\",\n \"|--------|-------|\",\n `|  |  |`,\n ].join(\"\\n\");\n\n // 6. Post PR comment\n let commentUrl: string | undefined;\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"comment\",\n String(pr.number),\n \"--body\",\n commentBody,\n ]);\n // gh pr comment prints the comment URL to stdout\n commentUrl = stdout.trim() || undefined;\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to post PR comment\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({\n success: true,\n prNumber: pr.number,\n prUrl: pr.url,\n commentUrl,\n });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { getSaveDir } from \"./config\";\n\nexport async function handleOpen(_req: NextRequest): Promise<NextResponse> {\n const desktop = await getSaveDir();\n\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"explorer\"\n : \"xdg-open\";\n\n execFile(cmd, [desktop]);\n\n return NextResponse.json({ success: true });\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAsB,oBAAoB;AAC1C,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAE1B,IAAM,gBAAgB,UAAU,QAAQ;AAMxC,IAAM,aAAa,KAAK,QAAQ,IAAI,GAAG,cAAc;AACrD,IAAM,cAAc,KAAK,YAAY,aAAa;AAClD,IAAM,mBAAmB,KAAK,QAAQ,GAAG,SAAS;AAElD,eAAe,aAA8B;AAC3C,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,aAAa,OAAO;AAC/C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,EAAE,SAAS,OAAO,WAAW,iBAAiB;AAAA,EACvD,QAAQ;AACN,WAAO,EAAE,SAAS,iBAAiB;AAAA,EACrC;AACF;AAEA,eAAe,YAAY,QAA+B;AACxD,QAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AACrE;AAEA,eAAsB,aAA8B;AAClD,QAAM,SAAS,MAAM,WAAW;AAChC,SAAO,OAAO;AAChB;AAEA,eAAsB,kBAAyC;AAC7D,QAAM,SAAS,MAAM,WAAW;AAChC,SAAO,aAAa,KAAK,MAAM;AACjC;AAEA,eAAsB,gBAAgB,KAAyC;AAC7E,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,KAAK,MAAM,IAAI;AAClE,WAAO,aAAa,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AAEA,QAAM,SAAiB,EAAE,SAAS,KAAK,QAAQ,KAAK,EAAE;AACtD,QAAM,YAAY,MAAM;AACxB,SAAO,aAAa,KAAK,MAAM;AACjC;AAEA,eAAsB,mBAA0C;AAC9D,MAAI,QAAQ,aAAa,UAAU;AACjC,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,2CAA2C;AAAA,MACpD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc,aAAa;AAAA,MAClD;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,SAAS,OAAO,KAAK,EAAE,QAAQ,OAAO,EAAE;AAC9C,QAAI,CAAC,QAAQ;AACX,aAAO,aAAa,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IAC9C;AACA,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC;AAAA,EACrC,QAAQ;AAEN,WAAO,aAAa,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AACF;;;ACjFA,SAAsB,gBAAAA,qBAAoB;AAC1C,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,QAAAC,aAAY;AACrB,SAAS,YAAAC,iBAAgB;AACzB,SAAS,aAAAC,kBAAiB;AAG1B,IAAMC,iBAAgBC,WAAUC,SAAQ;AAExC,eAAe,YAA6B;AAC1C,QAAM,EAAE,OAAO,IAAI,MAAMF,eAAc,OAAO,CAAC,aAAa,gBAAgB,MAAM,GAAG;AAAA,IACnF,KAAK,QAAQ,IAAI;AAAA,EACnB,CAAC;AACD,SAAO,OAAO,KAAK;AACrB;AAEA,IAAM,cAAc,CAAC,UAAU,OAAO;AAGtC,IAAM,cAAc,CAAC,YAAY,YAAY,QAAQ,WAAW;AAEhE,IAAM,kBAAkB;AAQxB,eAAsB,WAAW,KAAyC;AACxE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAOG,cAAa;AAAA,MAClB,EAAE,OAAO,oBAAoB;AAAA,MAC7B,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,EAAE,MAAM,MAAM,MAAM,IAAI;AAE9B,MAAI,CAAC,YAAY,SAAS,IAAsB,GAAG;AACjD,WAAOA,cAAa;AAAA,MAClB,EAAE,OAAO,4CAA4C;AAAA,MACrD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,CAAC,YAAY,SAAS,IAAoC,GAAG;AAC/D,WAAOA,cAAa;AAAA,MAClB,EAAE,OAAO,0DAA0D;AAAA,MACnE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,WAAW,eAAe,GAAG;AACnE,WAAOA,cAAa;AAAA,MAClB,EAAE,OAAO,0DAA0D;AAAA,MACnE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,MAAM,gBAAgB,MAAM;AACjD,QAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;AAE3C,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,WAAW,GAAG,MAAM,IAAI,IAAI;AAClC,QAAM,WAAWC,MAAK,SAAS,QAAQ;AAEvC,MAAI;AACF,UAAMC,WAAU,UAAU,MAAM;AAAA,EAClC,SAAS,KAAK;AACZ,WAAOF,cAAa;AAAA,MAClB,EAAE,OAAO,8BAA8B,QAAQ,OAAO,GAAG,EAAE;AAAA,MAC3D,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAOA,cAAa,KAAK,EAAE,SAAS,MAAM,MAAM,SAAS,CAAC;AAC5D;;;ACjFA,SAAsB,gBAAAG,qBAAoB;AAC1C,SAAS,YAAAC,iBAAgB;AACzB,SAAS,QAAQ,SAAAC,QAAO,gBAAgB;AACxC,SAAS,QAAAC,aAAY;AACrB,SAAS,aAAAC,kBAAiB;AAG1B,IAAMC,iBAAgBC,WAAUC,SAAQ;AASxC,eAAe,IACb,KACA,MAC6C;AAC7C,SAAOF,eAAc,KAAK,MAAM,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC;AACxD;AAEA,eAAe,cAAgC;AAC7C,MAAI;AACF,UAAM,IAAI,MAAM,CAAC,WAAW,CAAC;AAC7B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,YAAoC;AACjD,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,MAAM;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,WAAW,KAAyC;AAExE,MAAI,CAAE,MAAM,YAAY,GAAI;AAC1B,WAAOG,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,kDAAkD;AAAA,MAC3E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,KAAK,MAAM,UAAU;AAC3B,MAAI,CAAC,IAAI;AACP,WAAOA,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,iCAAiC;AAAA,MAC1D,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,SAAS,GAAG;AAClB,QAAM,aAAaC,MAAK,SAAS,GAAG,MAAM,aAAa;AACvD,QAAM,YAAYA,MAAK,SAAS,GAAG,MAAM,YAAY;AAErD,QAAM,CAAC,WAAW,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC9C,WAAW,UAAU;AAAA,IACrB,WAAW,SAAS;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,aAAa,CAAC,UAAU;AAC3B,UAAM,UAAU;AAAA,MACd,CAAC,aAAa,GAAG,MAAM;AAAA,MACvB,CAAC,YAAY,GAAG,MAAM;AAAA,IACxB,EAAE,OAAO,OAAO;AAChB,WAAOD,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,wBAAwB,QAAQ,KAAK,IAAI,CAAC,GAAG;AAAA,MACtE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,UAAUC,MAAK,QAAQ,IAAI,GAAG,cAAc;AAClD,QAAM,aAAaA,MAAK,SAAS,GAAG,MAAM,aAAa;AACvD,QAAM,YAAYA,MAAK,SAAS,GAAG,MAAM,YAAY;AAErD,MAAI;AACF,UAAMC,OAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,UAAM,SAAS,YAAY,UAAU;AACrC,UAAM,SAAS,WAAW,SAAS;AAAA,EACrC,SAAS,KAAK;AACZ,WAAOF,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,wCAAwC,QAAQ,OAAO,GAAG,EAAE;AAAA,MACrF,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,IAAI,OAAO,CAAC,OAAO,YAAY,SAAS,CAAC;AAC/C,UAAM,IAAI,OAAO;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AAGZ,UAAM,MAAM,OAAO,GAAG;AACtB,QAAI,CAAC,IAAI,SAAS,mBAAmB,GAAG;AACtC,aAAOA,cAAa;AAAA,QAClB,EAAE,SAAS,OAAO,OAAO,gCAAgC,QAAQ,IAAI;AAAA,QACrE,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,IAAI,OAAO,CAAC,MAAM,CAAC;AAAA,EAC3B,SAAS,KAAK;AACZ,WAAOA,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,4BAA4B,QAAQ,OAAO,GAAG,EAAE;AAAA,MACzE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,QAAQ,GAAG,eAAe,MAAM;AACtC,QAAM,OAAO,GAAG,eAAe;AAC/B,QAAM,UAAU,qCAAqC,KAAK,IAAI,IAAI,IAAI,MAAM;AAE5E,QAAM,YAAY,GAAG,OAAO,iBAAiB,MAAM;AACnD,QAAM,WAAW,GAAG,OAAO,iBAAiB,MAAM;AAGlD,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,SAAS,MAAM,EAAE,gBAAgB,QAAQ,MAAM,EAAE;AAAA,EAClE,EAAE,KAAK,IAAI;AAGX,MAAI;AACJ,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,MAAM;AAAA,MACjC;AAAA,MACA;AAAA,MACA,OAAO,GAAG,MAAM;AAAA,MAChB;AAAA,MACA;AAAA,IACF,CAAC;AAED,iBAAa,OAAO,KAAK,KAAK;AAAA,EAChC,SAAS,KAAK;AACZ,WAAOA,cAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,6BAA6B,QAAQ,OAAO,GAAG,EAAE;AAAA,MAC1E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAOA,cAAa,KAAK;AAAA,IACvB,SAAS;AAAA,IACT,UAAU,GAAG;AAAA,IACb,OAAO,GAAG;AAAA,IACV;AAAA,EACF,CAAC;AACH;;;ACtLA,SAAsB,gBAAAG,qBAAoB;AAC1C,SAAS,YAAAC,iBAAgB;AAGzB,eAAsB,WAAW,MAA0C;AACzE,QAAM,UAAU,MAAM,WAAW;AAEjC,QAAM,MACJ,QAAQ,aAAa,WACjB,SACA,QAAQ,aAAa,UACnB,aACA;AAER,EAAAC,UAAS,KAAK,CAAC,OAAO,CAAC;AAEvB,SAAOC,cAAa,KAAK,EAAE,SAAS,KAAK,CAAC;AAC5C;","names":["NextResponse","writeFile","join","execFile","promisify","execFileAsync","promisify","execFile","NextResponse","join","writeFile","NextResponse","execFile","mkdir","join","promisify","execFileAsync","promisify","execFile","NextResponse","join","mkdir","NextResponse","execFile","execFile","NextResponse"]}
|