git-shots-cli 0.6.0 → 0.6.2

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 +85 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -48,6 +48,14 @@ import { resolve as resolve2, basename, dirname } from "path";
48
48
  import { execSync } from "child_process";
49
49
  import { glob } from "glob";
50
50
  import chalk2 from "chalk";
51
+ var BATCH_SIZE = 3;
52
+ function chunk(arr, size) {
53
+ const chunks = [];
54
+ for (let i = 0; i < arr.length; i += size) {
55
+ chunks.push(arr.slice(i, i + size));
56
+ }
57
+ return chunks;
58
+ }
51
59
  async function upload(config, options) {
52
60
  const dir = resolve2(process.cwd(), config.directory);
53
61
  if (!existsSync2(dir)) {
@@ -62,48 +70,73 @@ async function upload(config, options) {
62
70
  console.log(chalk2.dim(`SHA: ${sha.slice(0, 7)}`));
63
71
  console.log(chalk2.dim(`Dir: ${dir}`));
64
72
  console.log();
65
- const files = await glob("**/*.png", { cwd: dir });
66
- if (files.length === 0) {
73
+ const rawFiles = await glob("**/*.png", { cwd: dir });
74
+ if (rawFiles.length === 0) {
67
75
  console.log(chalk2.yellow("No PNG files found."));
68
76
  return;
69
77
  }
70
- console.log(chalk2.dim(`Found ${files.length} screenshots`));
71
- const formData = new FormData();
72
- formData.append("project", config.project);
73
- formData.append("branch", branch);
74
- formData.append("gitSha", sha);
75
- if (config.platform) formData.append("platform", config.platform);
76
- for (const file of files) {
77
- const fullPath = resolve2(dir, file);
78
- const buffer = readFileSync2(fullPath);
79
- const blob = new Blob([buffer], { type: "image/png" });
78
+ const files = rawFiles.map((file) => {
80
79
  const dirName = dirname(file);
81
- const fieldName = dirName !== "." ? `${dirName}/${basename(file)}` : basename(file);
82
- formData.append(fieldName, blob, basename(file));
83
- }
80
+ return {
81
+ fieldName: dirName !== "." ? `${dirName}/${basename(file)}` : basename(file),
82
+ fileName: basename(file),
83
+ fullPath: resolve2(dir, file)
84
+ };
85
+ });
86
+ const localSlugs = files.map((f) => f.fileName.replace(/\.png$/, ""));
87
+ const allSlugs = [.../* @__PURE__ */ new Set([...options.baseManifest ?? [], ...localSlugs])];
88
+ console.log(chalk2.dim(`Found ${files.length} screenshots`));
84
89
  const url = `${config.server}/api/upload`;
85
- console.log(chalk2.dim(`Uploading to ${url}...`));
86
- try {
87
- const res = await fetch(url, {
88
- method: "POST",
89
- body: formData,
90
- headers: { Origin: config.server, ...authHeaders(config) }
91
- });
92
- const data = await res.json();
93
- checkAuthError(res);
94
- if (!res.ok) {
95
- console.error(chalk2.red(`Upload failed: ${JSON.stringify(data)}`));
96
- process.exit(1);
90
+ const batches = chunk(files, BATCH_SIZE);
91
+ let totalUploaded = 0;
92
+ let totalSkipped = 0;
93
+ for (let i = 0; i < batches.length; i++) {
94
+ const batch = batches[i];
95
+ const isLastBatch = i === batches.length - 1;
96
+ const formData = new FormData();
97
+ formData.append("project", config.project);
98
+ formData.append("branch", branch);
99
+ formData.append("gitSha", sha);
100
+ if (config.platform) formData.append("platform", config.platform);
101
+ if (isLastBatch) {
102
+ formData.append("allSlugs", JSON.stringify(allSlugs));
97
103
  }
98
- if (data.skipped > 0) {
99
- console.log(chalk2.green(`Uploaded ${data.uploaded} screenshots`) + chalk2.dim(` (${data.skipped} unchanged, skipped)`));
100
- } else {
101
- console.log(chalk2.green(`Uploaded ${data.uploaded} screenshots`));
104
+ for (const file of batch) {
105
+ const buffer = readFileSync2(file.fullPath);
106
+ const blob = new Blob([buffer], { type: "image/png" });
107
+ formData.append(file.fieldName, blob, file.fileName);
108
+ }
109
+ const batchLabel = batches.length > 1 ? chalk2.dim(`[${i + 1}/${batches.length}] `) : "";
110
+ try {
111
+ const res = await fetch(url, {
112
+ method: "POST",
113
+ body: formData,
114
+ headers: { Origin: config.server, ...authHeaders(config) }
115
+ });
116
+ checkAuthError(res);
117
+ const data = await res.json();
118
+ if (!res.ok) {
119
+ console.error(chalk2.red(`${batchLabel}Upload failed: ${JSON.stringify(data)}`));
120
+ process.exit(1);
121
+ }
122
+ totalUploaded += data.uploaded ?? 0;
123
+ totalSkipped += data.skipped ?? 0;
124
+ if (batches.length > 1) {
125
+ const parts2 = [];
126
+ if (data.uploaded > 0) parts2.push(chalk2.green(`${data.uploaded} uploaded`));
127
+ if (data.skipped > 0) parts2.push(chalk2.dim(`${data.skipped} skipped`));
128
+ console.log(`${batchLabel}${parts2.join(", ")}`);
129
+ }
130
+ } catch (err) {
131
+ console.error(chalk2.red(`${batchLabel}Request failed: ${err}`));
132
+ process.exit(1);
102
133
  }
103
- } catch (err) {
104
- console.error(chalk2.red(`Request failed: ${err}`));
105
- process.exit(1);
106
134
  }
135
+ const parts = [];
136
+ if (totalUploaded > 0) parts.push(chalk2.green(`${totalUploaded} uploaded`));
137
+ if (totalSkipped > 0) parts.push(chalk2.dim(`${totalSkipped} unchanged`));
138
+ console.log(`
139
+ ${parts.join(", ") || chalk2.dim("nothing to do")}`);
107
140
  }
108
141
 
109
142
  // src/compare.ts
@@ -286,8 +319,21 @@ async function review(config, options) {
286
319
  console.log(chalk6.dim(`Branch: ${branch}`));
287
320
  console.log(chalk6.dim(`SHA: ${sha.slice(0, 7)}`));
288
321
  console.log();
322
+ let baseManifest;
323
+ try {
324
+ const manifestUrl = `${config.server}/api/projects/${encodeURIComponent(config.project)}/manifest?branch=main`;
325
+ const manifestRes = await fetch(manifestUrl, {
326
+ headers: { Origin: config.server, ...authHeaders(config) }
327
+ });
328
+ if (manifestRes.ok) {
329
+ const data = await manifestRes.json();
330
+ baseManifest = data.screenSlugs;
331
+ console.log(chalk6.dim(`Base manifest: ${baseManifest.length} screens on main`));
332
+ }
333
+ } catch {
334
+ }
289
335
  console.log(chalk6.dim("Uploading screenshots..."));
290
- await upload(config, { branch, sha });
336
+ await upload(config, { branch, sha, baseManifest });
291
337
  console.log();
292
338
  console.log(chalk6.dim("Creating review session..."));
293
339
  const reviewUrl = `${config.server}/api/reviews`;
@@ -503,7 +549,7 @@ async function syncFlows(config) {
503
549
  const listUrl = `${config.server}/api/projects/${config.project}/flows`;
504
550
  let existingFlows = [];
505
551
  try {
506
- const res = await fetch(listUrl, { headers: { Origin: config.server } });
552
+ const res = await fetch(listUrl, { headers: { Origin: config.server, ...authHeaders(config) } });
507
553
  if (res.ok) {
508
554
  const data = await res.json();
509
555
  existingFlows = data.flows ?? [];
@@ -517,7 +563,7 @@ async function syncFlows(config) {
517
563
  const patchUrl = `${config.server}/api/projects/${config.project}/flows/${flow.slug}`;
518
564
  const patchRes = await fetch(patchUrl, {
519
565
  method: "PATCH",
520
- headers: { "Content-Type": "application/json", Origin: config.server },
566
+ headers: { "Content-Type": "application/json", Origin: config.server, ...authHeaders(config) },
521
567
  body: JSON.stringify({
522
568
  name: flow.name,
523
569
  description: flow.description ?? null,
@@ -532,7 +578,7 @@ async function syncFlows(config) {
532
578
  const stepsUrl = `${config.server}/api/projects/${config.project}/flows/${flow.slug}/steps`;
533
579
  const stepsRes = await fetch(stepsUrl, {
534
580
  method: "PUT",
535
- headers: { "Content-Type": "application/json", Origin: config.server },
581
+ headers: { "Content-Type": "application/json", Origin: config.server, ...authHeaders(config) },
536
582
  body: JSON.stringify({ steps: flow.steps })
537
583
  });
538
584
  if (stepsRes.ok) {
@@ -545,7 +591,7 @@ async function syncFlows(config) {
545
591
  const createUrl = `${config.server}/api/projects/${config.project}/flows`;
546
592
  const res = await fetch(createUrl, {
547
593
  method: "POST",
548
- headers: { "Content-Type": "application/json", Origin: config.server },
594
+ headers: { "Content-Type": "application/json", Origin: config.server, ...authHeaders(config) },
549
595
  body: JSON.stringify({
550
596
  slug: flow.slug,
551
597
  name: flow.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-shots-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "CLI for git-shots visual regression platform",
5
5
  "type": "module",
6
6
  "bin": {