git-shots-cli 0.3.1 → 0.4.1

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 (2) hide show
  1. package/dist/index.js +126 -98
  2. package/package.json +26 -26
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { Command } from "commander";
6
6
  // src/config.ts
7
7
  import { readFileSync, existsSync } from "fs";
8
8
  import { resolve } from "path";
9
+ import chalk from "chalk";
9
10
  var DEFAULT_CONFIG = {
10
11
  project: "",
11
12
  server: "https://git-shots.rijid356.workers.dev",
@@ -13,15 +14,29 @@ var DEFAULT_CONFIG = {
13
14
  };
14
15
  function loadConfig(cwd = process.cwd()) {
15
16
  const configPath = resolve(cwd, ".git-shots.json");
16
- if (!existsSync(configPath)) {
17
- return DEFAULT_CONFIG;
17
+ let config = { ...DEFAULT_CONFIG };
18
+ if (existsSync(configPath)) {
19
+ try {
20
+ const raw = readFileSync(configPath, "utf-8");
21
+ const parsed = JSON.parse(raw);
22
+ config = { ...config, ...parsed };
23
+ } catch {
24
+ }
18
25
  }
19
- try {
20
- const raw = readFileSync(configPath, "utf-8");
21
- const parsed = JSON.parse(raw);
22
- return { ...DEFAULT_CONFIG, ...parsed };
23
- } catch {
24
- return DEFAULT_CONFIG;
26
+ const envKey = process.env.GIT_SHOTS_API_KEY;
27
+ if (envKey) {
28
+ config.apiKey = envKey;
29
+ }
30
+ return config;
31
+ }
32
+ function authHeaders(config) {
33
+ if (!config.apiKey) return {};
34
+ return { Authorization: `Bearer ${config.apiKey}` };
35
+ }
36
+ function checkAuthError(res) {
37
+ if (res.status === 401 || res.status === 403) {
38
+ console.error(chalk.red("Authentication failed. Set GIT_SHOTS_API_KEY or add apiKey to .git-shots.json"));
39
+ process.exit(1);
25
40
  }
26
41
  }
27
42
 
@@ -30,27 +45,27 @@ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
30
45
  import { resolve as resolve2, basename, dirname } from "path";
31
46
  import { execSync } from "child_process";
32
47
  import { glob } from "glob";
33
- import chalk from "chalk";
48
+ import chalk2 from "chalk";
34
49
  async function upload(config, options) {
35
50
  const dir = resolve2(process.cwd(), config.directory);
36
51
  if (!existsSync2(dir)) {
37
- console.error(chalk.red(`Directory not found: ${dir}`));
52
+ console.error(chalk2.red(`Directory not found: ${dir}`));
38
53
  process.exit(1);
39
54
  }
40
55
  const branch = options.branch ?? execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
41
56
  const sha = options.sha ?? execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
42
- console.log(chalk.dim(`Project: ${config.project}`));
43
- if (config.platform) console.log(chalk.dim(`Platform: ${config.platform}`));
44
- console.log(chalk.dim(`Branch: ${branch}`));
45
- console.log(chalk.dim(`SHA: ${sha.slice(0, 7)}`));
46
- console.log(chalk.dim(`Dir: ${dir}`));
57
+ console.log(chalk2.dim(`Project: ${config.project}`));
58
+ if (config.platform) console.log(chalk2.dim(`Platform: ${config.platform}`));
59
+ console.log(chalk2.dim(`Branch: ${branch}`));
60
+ console.log(chalk2.dim(`SHA: ${sha.slice(0, 7)}`));
61
+ console.log(chalk2.dim(`Dir: ${dir}`));
47
62
  console.log();
48
63
  const files = await glob("**/*.png", { cwd: dir });
49
64
  if (files.length === 0) {
50
- console.log(chalk.yellow("No PNG files found."));
65
+ console.log(chalk2.yellow("No PNG files found."));
51
66
  return;
52
67
  }
53
- console.log(chalk.dim(`Found ${files.length} screenshots`));
68
+ console.log(chalk2.dim(`Found ${files.length} screenshots`));
54
69
  const formData = new FormData();
55
70
  formData.append("project", config.project);
56
71
  formData.append("branch", branch);
@@ -65,27 +80,28 @@ async function upload(config, options) {
65
80
  formData.append(fieldName, blob, basename(file));
66
81
  }
67
82
  const url = `${config.server}/api/upload`;
68
- console.log(chalk.dim(`Uploading to ${url}...`));
83
+ console.log(chalk2.dim(`Uploading to ${url}...`));
69
84
  try {
70
85
  const res = await fetch(url, {
71
86
  method: "POST",
72
87
  body: formData,
73
- headers: { Origin: config.server }
88
+ headers: { Origin: config.server, ...authHeaders(config) }
74
89
  });
75
90
  const data = await res.json();
91
+ checkAuthError(res);
76
92
  if (!res.ok) {
77
- console.error(chalk.red(`Upload failed: ${JSON.stringify(data)}`));
93
+ console.error(chalk2.red(`Upload failed: ${JSON.stringify(data)}`));
78
94
  process.exit(1);
79
95
  }
80
- console.log(chalk.green(`Uploaded ${data.uploaded} screenshots`));
96
+ console.log(chalk2.green(`Uploaded ${data.uploaded} screenshots`));
81
97
  } catch (err) {
82
- console.error(chalk.red(`Request failed: ${err}`));
98
+ console.error(chalk2.red(`Request failed: ${err}`));
83
99
  process.exit(1);
84
100
  }
85
101
  }
86
102
 
87
103
  // src/compare.ts
88
- import chalk2 from "chalk";
104
+ import chalk3 from "chalk";
89
105
  async function compare(config, options) {
90
106
  const url = `${config.server}/api/compare`;
91
107
  const body = {
@@ -94,67 +110,71 @@ async function compare(config, options) {
94
110
  head: options.head,
95
111
  threshold: options.threshold ?? 0.1
96
112
  };
97
- console.log(chalk2.dim(`Comparing ${body.base} vs ${body.head} for ${config.project}...`));
113
+ console.log(chalk3.dim(`Comparing ${body.base} vs ${body.head} for ${config.project}...`));
98
114
  try {
99
115
  const res = await fetch(url, {
100
116
  method: "POST",
101
- headers: { "Content-Type": "application/json", Origin: config.server },
117
+ headers: { "Content-Type": "application/json", Origin: config.server, ...authHeaders(config) },
102
118
  body: JSON.stringify(body)
103
119
  });
104
120
  const data = await res.json();
121
+ checkAuthError(res);
105
122
  if (!res.ok) {
106
- console.error(chalk2.red(`Compare failed: ${JSON.stringify(data)}`));
123
+ console.error(chalk3.red(`Compare failed: ${JSON.stringify(data)}`));
107
124
  process.exit(1);
108
125
  }
109
126
  console.log();
110
- console.log(`Compared ${chalk2.bold(data.compared)} screens`);
127
+ console.log(`Compared ${chalk3.bold(data.compared)} screens`);
111
128
  console.log();
112
129
  if (data.diffs.length === 0) {
113
- console.log(chalk2.green("No visual differences found!"));
130
+ console.log(chalk3.green("No visual differences found!"));
114
131
  return;
115
132
  }
116
- console.log(chalk2.dim("Screen".padEnd(30) + "Mismatch".padEnd(15) + "Pixels"));
117
- console.log(chalk2.dim("-".repeat(55)));
133
+ console.log(chalk3.dim("Screen".padEnd(30) + "Mismatch".padEnd(15) + "Pixels"));
134
+ console.log(chalk3.dim("-".repeat(55)));
118
135
  for (const d of data.diffs) {
119
136
  const pct = d.mismatchPercentage.toFixed(2) + "%";
120
- const color = d.mismatchPercentage > 10 ? chalk2.red : d.mismatchPercentage > 1 ? chalk2.yellow : chalk2.green;
137
+ const color = d.mismatchPercentage > 10 ? chalk3.red : d.mismatchPercentage > 1 ? chalk3.yellow : chalk3.green;
121
138
  console.log(
122
- d.screen.padEnd(30) + color(pct.padEnd(15)) + chalk2.dim(d.mismatchPixels.toLocaleString())
139
+ d.screen.padEnd(30) + color(pct.padEnd(15)) + chalk3.dim(d.mismatchPixels.toLocaleString())
123
140
  );
124
141
  }
125
142
  } catch (err) {
126
- console.error(chalk2.red(`Request failed: ${err}`));
143
+ console.error(chalk3.red(`Request failed: ${err}`));
127
144
  process.exit(1);
128
145
  }
129
146
  }
130
147
 
131
148
  // src/status.ts
132
- import chalk3 from "chalk";
149
+ import chalk4 from "chalk";
133
150
  async function status(config) {
134
151
  const url = `${config.server}/api/diffs?project=${encodeURIComponent(config.project)}`;
135
152
  try {
136
- const res = await fetch(url);
153
+ const res = await fetch(url, {
154
+ headers: { ...authHeaders(config) }
155
+ });
137
156
  const data = await res.json();
157
+ checkAuthError(res);
138
158
  if (!res.ok) {
139
- console.error(chalk3.red(`Status failed: ${JSON.stringify(data)}`));
159
+ console.error(chalk4.red(`Status failed: ${JSON.stringify(data)}`));
140
160
  process.exit(1);
141
161
  }
142
162
  if (data.length === 0) {
143
- console.log(chalk3.dim("No diffs found for this project."));
163
+ console.log(chalk4.dim("No diffs found for this project."));
144
164
  return;
145
165
  }
146
- console.log(`${chalk3.bold(data.length)} diffs for ${config.project}`);
166
+ console.log(`${chalk4.bold(data.length)} diffs for ${config.project}`);
147
167
  console.log();
148
- console.log(chalk3.dim("ID".padEnd(8) + "Status".padEnd(12) + "Mismatch".padEnd(12) + "Date"));
149
- console.log(chalk3.dim("-".repeat(50)));
168
+ console.log(chalk4.dim("ID".padEnd(8) + "Status".padEnd(12) + "Mismatch".padEnd(12) + "Date"));
169
+ console.log(chalk4.dim("-".repeat(50)));
150
170
  for (const { diff } of data) {
151
- const statusColor = diff.status === "approved" ? chalk3.green : diff.status === "rejected" ? chalk3.red : chalk3.yellow;
171
+ const statusColor = diff.status === "approved" ? chalk4.green : diff.status === "rejected" ? chalk4.red : chalk4.yellow;
152
172
  console.log(
153
- String(diff.id).padEnd(8) + statusColor(diff.status.padEnd(12)) + (diff.mismatch_percentage.toFixed(2) + "%").padEnd(12) + chalk3.dim(new Date(diff.created_at * 1e3).toLocaleDateString())
173
+ String(diff.id).padEnd(8) + statusColor(diff.status.padEnd(12)) + (diff.mismatch_percentage.toFixed(2) + "%").padEnd(12) + chalk4.dim(new Date(diff.created_at * 1e3).toLocaleDateString())
154
174
  );
155
175
  }
156
176
  } catch (err) {
157
- console.error(chalk3.red(`Request failed: ${err}`));
177
+ console.error(chalk4.red(`Request failed: ${err}`));
158
178
  process.exit(1);
159
179
  }
160
180
  }
@@ -162,33 +182,36 @@ async function status(config) {
162
182
  // src/pull-baselines.ts
163
183
  import { mkdirSync, writeFileSync } from "fs";
164
184
  import { resolve as resolve3, dirname as dirname2 } from "path";
165
- import chalk4 from "chalk";
185
+ import chalk5 from "chalk";
166
186
  async function pullBaselines(config, options) {
167
187
  const branch = options.branch ?? "main";
168
188
  const outputDir = resolve3(process.cwd(), options.output ?? config.directory);
169
- console.log(chalk4.dim(`Project: ${config.project}`));
170
- console.log(chalk4.dim(`Branch: ${branch}`));
171
- console.log(chalk4.dim(`Output: ${outputDir}`));
189
+ console.log(chalk5.dim(`Project: ${config.project}`));
190
+ console.log(chalk5.dim(`Branch: ${branch}`));
191
+ console.log(chalk5.dim(`Output: ${outputDir}`));
172
192
  console.log();
173
193
  const manifestUrl = `${config.server}/api/projects/${encodeURIComponent(config.project)}/snapshots?branch=${encodeURIComponent(branch)}`;
174
194
  let snapshots;
175
195
  try {
176
- const res = await fetch(manifestUrl);
196
+ const res = await fetch(manifestUrl, {
197
+ headers: { ...authHeaders(config) }
198
+ });
177
199
  const data = await res.json();
200
+ checkAuthError(res);
178
201
  if (!res.ok) {
179
- console.error(chalk4.red(`Failed to fetch snapshots: ${JSON.stringify(data)}`));
202
+ console.error(chalk5.red(`Failed to fetch snapshots: ${JSON.stringify(data)}`));
180
203
  process.exit(1);
181
204
  }
182
205
  snapshots = data.snapshots;
183
206
  } catch (err) {
184
- console.error(chalk4.red(`Request failed: ${err}`));
207
+ console.error(chalk5.red(`Request failed: ${err}`));
185
208
  process.exit(1);
186
209
  }
187
210
  if (snapshots.length === 0) {
188
- console.log(chalk4.yellow("No baseline snapshots found."));
211
+ console.log(chalk5.yellow("No baseline snapshots found."));
189
212
  return;
190
213
  }
191
- console.log(chalk4.dim(`Found ${snapshots.length} baselines to download`));
214
+ console.log(chalk5.dim(`Found ${snapshots.length} baselines to download`));
192
215
  console.log();
193
216
  const batchSize = 5;
194
217
  let downloaded = 0;
@@ -197,9 +220,11 @@ async function pullBaselines(config, options) {
197
220
  await Promise.all(
198
221
  batch.map(async (snap) => {
199
222
  const imageUrl = `${config.server}/api/images/${snap.r2_key}`;
200
- const res = await fetch(imageUrl);
223
+ const res = await fetch(imageUrl, {
224
+ headers: { ...authHeaders(config) }
225
+ });
201
226
  if (!res.ok) {
202
- console.error(chalk4.red(` Failed to download ${snap.screen_slug}: ${res.status}`));
227
+ console.error(chalk5.red(` Failed to download ${snap.screen_slug}: ${res.status}`));
203
228
  return;
204
229
  }
205
230
  const buffer = Buffer.from(await res.arrayBuffer());
@@ -209,19 +234,19 @@ async function pullBaselines(config, options) {
209
234
  writeFileSync(filePath, buffer);
210
235
  downloaded++;
211
236
  console.log(
212
- chalk4.green(` [${downloaded}/${snapshots.length}]`) + ` ${subDir ? subDir + "/" : ""}${snap.screen_slug}.png`
237
+ chalk5.green(` [${downloaded}/${snapshots.length}]`) + ` ${subDir ? subDir + "/" : ""}${snap.screen_slug}.png`
213
238
  );
214
239
  })
215
240
  );
216
241
  }
217
242
  console.log();
218
- console.log(chalk4.green(`Downloaded ${downloaded} baselines to ${outputDir}`));
243
+ console.log(chalk5.green(`Downloaded ${downloaded} baselines to ${outputDir}`));
219
244
  }
220
245
 
221
246
  // src/review.ts
222
247
  import { execSync as execSync2 } from "child_process";
223
248
  import { platform } from "os";
224
- import chalk5 from "chalk";
249
+ import chalk6 from "chalk";
225
250
  function openBrowser(url) {
226
251
  try {
227
252
  const os = platform();
@@ -233,7 +258,7 @@ function openBrowser(url) {
233
258
  execSync2(`xdg-open "${url}"`, { stdio: "ignore" });
234
259
  }
235
260
  } catch {
236
- console.log(chalk5.dim(`Could not open browser. Visit the URL manually.`));
261
+ console.log(chalk6.dim(`Could not open browser. Visit the URL manually.`));
237
262
  }
238
263
  }
239
264
  function sleep(ms) {
@@ -245,20 +270,20 @@ async function review(config, options) {
245
270
  const timeoutSec = options.timeout ?? 300;
246
271
  const branch = options.branch ?? execSync2("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
247
272
  const sha = options.sha ?? execSync2("git rev-parse HEAD", { encoding: "utf-8" }).trim();
248
- console.log(chalk5.dim(`Project: ${config.project}`));
249
- console.log(chalk5.dim(`Branch: ${branch}`));
250
- console.log(chalk5.dim(`SHA: ${sha.slice(0, 7)}`));
273
+ console.log(chalk6.dim(`Project: ${config.project}`));
274
+ console.log(chalk6.dim(`Branch: ${branch}`));
275
+ console.log(chalk6.dim(`SHA: ${sha.slice(0, 7)}`));
251
276
  console.log();
252
- console.log(chalk5.dim("Uploading screenshots..."));
277
+ console.log(chalk6.dim("Uploading screenshots..."));
253
278
  await upload(config, { branch, sha });
254
279
  console.log();
255
- console.log(chalk5.dim("Creating review session..."));
280
+ console.log(chalk6.dim("Creating review session..."));
256
281
  const reviewUrl = `${config.server}/api/reviews`;
257
282
  let reviewData;
258
283
  try {
259
284
  const res = await fetch(reviewUrl, {
260
285
  method: "POST",
261
- headers: { "Content-Type": "application/json", Origin: config.server },
286
+ headers: { "Content-Type": "application/json", Origin: config.server, ...authHeaders(config) },
262
287
  body: JSON.stringify({
263
288
  project: config.project,
264
289
  branch,
@@ -266,31 +291,32 @@ async function review(config, options) {
266
291
  })
267
292
  });
268
293
  const data = await res.json();
294
+ checkAuthError(res);
269
295
  if (!res.ok) {
270
- console.error(chalk5.red(`Failed to create review: ${JSON.stringify(data)}`));
296
+ console.error(chalk6.red(`Failed to create review: ${JSON.stringify(data)}`));
271
297
  process.exit(1);
272
298
  }
273
299
  reviewData = data;
274
300
  } catch (err) {
275
- console.error(chalk5.red(`Request failed: ${err}`));
301
+ console.error(chalk6.red(`Request failed: ${err}`));
276
302
  process.exit(1);
277
303
  }
278
304
  const allZero = reviewData.diffs.length === 0 || reviewData.diffs.every((d) => d.mismatchPercentage === 0);
279
305
  if (allZero) {
280
- console.log(chalk5.green("No visual changes detected."));
306
+ console.log(chalk6.green("No visual changes detected."));
281
307
  process.exit(0);
282
308
  }
283
309
  const sessionUrl = `${config.server}/reviews/${reviewData.review.id}`;
284
310
  console.log();
285
- console.log(chalk5.bold("Visual changes detected:"));
311
+ console.log(chalk6.bold("Visual changes detected:"));
286
312
  console.log();
287
313
  for (const d of reviewData.diffs) {
288
314
  const pct = d.mismatchPercentage.toFixed(2) + "%";
289
- const color = d.mismatchPercentage > 10 ? chalk5.red : d.mismatchPercentage > 1 ? chalk5.yellow : chalk5.green;
315
+ const color = d.mismatchPercentage > 10 ? chalk6.red : d.mismatchPercentage > 1 ? chalk6.yellow : chalk6.green;
290
316
  console.log(` ${d.screen.padEnd(30)} ${color(pct)}`);
291
317
  }
292
318
  console.log();
293
- console.log(`Review: ${chalk5.cyan(sessionUrl)}`);
319
+ console.log(`Review: ${chalk6.cyan(sessionUrl)}`);
294
320
  if (shouldOpen) {
295
321
  openBrowser(sessionUrl);
296
322
  }
@@ -298,7 +324,7 @@ async function review(config, options) {
298
324
  return;
299
325
  }
300
326
  console.log();
301
- console.log(chalk5.dim(`Polling for verdict (timeout: ${timeoutSec}s)...`));
327
+ console.log(chalk6.dim(`Polling for verdict (timeout: ${timeoutSec}s)...`));
302
328
  const pollInterval = 3e3;
303
329
  const startTime = Date.now();
304
330
  const deadline = startTime + timeoutSec * 1e3;
@@ -306,23 +332,23 @@ async function review(config, options) {
306
332
  await sleep(pollInterval);
307
333
  try {
308
334
  const res = await fetch(`${config.server}/api/reviews/${reviewData.review.id}`, {
309
- headers: { Origin: config.server }
335
+ headers: { Origin: config.server, ...authHeaders(config) }
310
336
  });
311
337
  const data = await res.json();
312
338
  if (!res.ok) continue;
313
339
  if (data.review.status === "approved") {
314
340
  console.log();
315
- console.log(chalk5.green.bold("Review approved!"));
341
+ console.log(chalk6.green.bold("Review approved!"));
316
342
  process.exit(0);
317
343
  }
318
344
  if (data.review.status === "rejected") {
319
345
  console.log();
320
- console.log(chalk5.red.bold("Review rejected."));
346
+ console.log(chalk6.red.bold("Review rejected."));
321
347
  const rejected = data.diffs.filter((d) => d.status === "rejected");
322
348
  if (rejected.length > 0) {
323
- console.log(chalk5.dim("Rejected screens:"));
349
+ console.log(chalk6.dim("Rejected screens:"));
324
350
  for (const d of rejected) {
325
- console.log(chalk5.red(` - ${d.base.screen_slug}`));
351
+ console.log(chalk6.red(` - ${d.base.screen_slug}`));
326
352
  }
327
353
  }
328
354
  process.exit(1);
@@ -331,8 +357,8 @@ async function review(config, options) {
331
357
  }
332
358
  }
333
359
  console.log();
334
- console.log(chalk5.yellow(`Review timed out after ${timeoutSec}s.`));
335
- console.log(chalk5.dim(`Visit ${sessionUrl} to complete the review.`));
360
+ console.log(chalk6.yellow(`Review timed out after ${timeoutSec}s.`));
361
+ console.log(chalk6.dim(`Visit ${sessionUrl} to complete the review.`));
336
362
  process.exit(2);
337
363
  }
338
364
 
@@ -340,7 +366,7 @@ async function review(config, options) {
340
366
  import { existsSync as existsSync3, writeFileSync as writeFileSync2, unlinkSync, chmodSync, readFileSync as readFileSync3 } from "fs";
341
367
  import { resolve as resolve4, join } from "path";
342
368
  import { execSync as execSync3 } from "child_process";
343
- import chalk6 from "chalk";
369
+ import chalk7 from "chalk";
344
370
  var HOOK_MARKER = "# git-shots-hook";
345
371
  var HOOK_SCRIPT = `#!/bin/sh
346
372
  ${HOOK_MARKER}
@@ -406,14 +432,14 @@ async function hookInstall(cwd = process.cwd()) {
406
432
  if (existsSync3(hookPath)) {
407
433
  const existing = readFileSync3(hookPath, "utf-8");
408
434
  if (existing.includes(HOOK_MARKER)) {
409
- console.log(chalk6.yellow("git-shots pre-push hook is already installed."));
435
+ console.log(chalk7.yellow("git-shots pre-push hook is already installed."));
410
436
  return;
411
437
  }
412
438
  console.error(
413
- chalk6.red("A pre-push hook already exists at ") + chalk6.dim(hookPath)
439
+ chalk7.red("A pre-push hook already exists at ") + chalk7.dim(hookPath)
414
440
  );
415
441
  console.error(
416
- chalk6.dim("Remove or rename it first, then re-run this command.")
442
+ chalk7.dim("Remove or rename it first, then re-run this command.")
417
443
  );
418
444
  process.exit(1);
419
445
  }
@@ -422,37 +448,39 @@ async function hookInstall(cwd = process.cwd()) {
422
448
  chmodSync(hookPath, 493);
423
449
  } catch {
424
450
  }
425
- console.log(chalk6.green("Installed pre-push hook at ") + chalk6.dim(hookPath));
451
+ console.log(chalk7.green("Installed pre-push hook at ") + chalk7.dim(hookPath));
426
452
  console.log();
427
- console.log(chalk6.dim("The hook will run `git-shots review` before every push"));
428
- console.log(chalk6.dim("when .git-shots.json is present and screenshots exist."));
453
+ console.log(chalk7.dim("The hook will run `git-shots review` before every push"));
454
+ console.log(chalk7.dim("when .git-shots.json is present and screenshots exist."));
455
+ console.log();
456
+ console.log(chalk7.dim("Set GIT_SHOTS_API_KEY in your environment or add apiKey to .git-shots.json"));
429
457
  }
430
458
  async function hookUninstall(cwd = process.cwd()) {
431
459
  const gitDir = getGitDir(cwd);
432
460
  const hooksDir = resolve4(cwd, gitDir, "hooks");
433
461
  const hookPath = join(hooksDir, "pre-push");
434
462
  if (!existsSync3(hookPath)) {
435
- console.log(chalk6.yellow("No pre-push hook found."));
463
+ console.log(chalk7.yellow("No pre-push hook found."));
436
464
  return;
437
465
  }
438
466
  const existing = readFileSync3(hookPath, "utf-8");
439
467
  if (!existing.includes(HOOK_MARKER)) {
440
- console.error(chalk6.red("Pre-push hook exists but was not installed by git-shots."));
441
- console.error(chalk6.dim("Remove it manually if you want: ") + chalk6.dim(hookPath));
468
+ console.error(chalk7.red("Pre-push hook exists but was not installed by git-shots."));
469
+ console.error(chalk7.dim("Remove it manually if you want: ") + chalk7.dim(hookPath));
442
470
  process.exit(1);
443
471
  }
444
472
  unlinkSync(hookPath);
445
- console.log(chalk6.green("Removed git-shots pre-push hook."));
473
+ console.log(chalk7.green("Removed git-shots pre-push hook."));
446
474
  }
447
475
 
448
476
  // src/flows.ts
449
- import chalk7 from "chalk";
477
+ import chalk8 from "chalk";
450
478
  async function syncFlows(config) {
451
479
  if (!config.flows || config.flows.length === 0) {
452
- console.log(chalk7.dim("No flows defined in config, skipping sync."));
480
+ console.log(chalk8.dim("No flows defined in config, skipping sync."));
453
481
  return;
454
482
  }
455
- console.log(chalk7.dim(`Syncing ${config.flows.length} flow(s)...`));
483
+ console.log(chalk8.dim(`Syncing ${config.flows.length} flow(s)...`));
456
484
  const listUrl = `${config.server}/api/projects/${config.project}/flows`;
457
485
  let existingFlows = [];
458
486
  try {
@@ -479,7 +507,7 @@ async function syncFlows(config) {
479
507
  });
480
508
  if (!patchRes.ok) {
481
509
  const err = await patchRes.text();
482
- console.error(chalk7.red(` Failed to update flow ${flow.slug}: ${err}`));
510
+ console.error(chalk8.red(` Failed to update flow ${flow.slug}: ${err}`));
483
511
  continue;
484
512
  }
485
513
  const stepsUrl = `${config.server}/api/projects/${config.project}/flows/${flow.slug}/steps`;
@@ -489,10 +517,10 @@ async function syncFlows(config) {
489
517
  body: JSON.stringify({ steps: flow.steps })
490
518
  });
491
519
  if (stepsRes.ok) {
492
- console.log(chalk7.green(` Updated flow: ${flow.name} (${flow.steps.length} steps)`));
520
+ console.log(chalk8.green(` Updated flow: ${flow.name} (${flow.steps.length} steps)`));
493
521
  } else {
494
522
  const err = await stepsRes.text();
495
- console.error(chalk7.red(` Failed to update steps for ${flow.slug}: ${err}`));
523
+ console.error(chalk8.red(` Failed to update steps for ${flow.slug}: ${err}`));
496
524
  }
497
525
  } else {
498
526
  const createUrl = `${config.server}/api/projects/${config.project}/flows`;
@@ -508,14 +536,14 @@ async function syncFlows(config) {
508
536
  })
509
537
  });
510
538
  if (res.ok) {
511
- console.log(chalk7.green(` Created flow: ${flow.name} (${flow.steps.length} steps)`));
539
+ console.log(chalk8.green(` Created flow: ${flow.name} (${flow.steps.length} steps)`));
512
540
  } else {
513
541
  const err = await res.text();
514
- console.error(chalk7.red(` Failed to create flow ${flow.slug}: ${err}`));
542
+ console.error(chalk8.red(` Failed to create flow ${flow.slug}: ${err}`));
515
543
  }
516
544
  }
517
545
  }
518
- console.log(chalk7.green("Flows synced."));
546
+ console.log(chalk8.green("Flows synced."));
519
547
  }
520
548
 
521
549
  // src/index.ts
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
- {
2
- "name": "git-shots-cli",
3
- "version": "0.3.1",
4
- "description": "CLI for git-shots visual regression platform",
5
- "type": "module",
6
- "bin": {
7
- "git-shots": "./dist/index.js"
8
- },
9
- "files": [
10
- "dist"
11
- ],
12
- "scripts": {
13
- "build": "tsup src/index.ts --format esm --dts",
14
- "dev": "tsup src/index.ts --format esm --watch"
15
- },
16
- "dependencies": {
17
- "commander": "^12.0.0",
18
- "chalk": "^5.3.0",
19
- "glob": "^11.0.0"
20
- },
21
- "devDependencies": {
22
- "tsup": "^8.0.0",
23
- "typescript": "^5.0.0",
24
- "@types/node": "^22.0.0"
25
- }
26
- }
1
+ {
2
+ "name": "git-shots-cli",
3
+ "version": "0.4.1",
4
+ "description": "CLI for git-shots visual regression platform",
5
+ "type": "module",
6
+ "bin": {
7
+ "git-shots": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm --dts",
14
+ "dev": "tsup src/index.ts --format esm --watch"
15
+ },
16
+ "dependencies": {
17
+ "commander": "^12.0.0",
18
+ "chalk": "^5.3.0",
19
+ "glob": "^11.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "tsup": "^8.0.0",
23
+ "typescript": "^5.0.0",
24
+ "@types/node": "^22.0.0"
25
+ }
26
+ }