git-shots-cli 0.6.1 → 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 +81 -35
  2. package/package.json +27 -27
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`;
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
- {
2
- "name": "git-shots-cli",
3
- "version": "0.6.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
- "dotenv": "^16.4.0",
20
- "glob": "^11.0.0"
21
- },
22
- "devDependencies": {
23
- "tsup": "^8.0.0",
24
- "typescript": "^5.0.0",
25
- "@types/node": "^22.0.0"
26
- }
27
- }
1
+ {
2
+ "name": "git-shots-cli",
3
+ "version": "0.6.2",
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
+ "dotenv": "^16.4.0",
20
+ "glob": "^11.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.0.0",
25
+ "@types/node": "^22.0.0"
26
+ }
27
+ }