git-shots-cli 0.2.0 → 0.3.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/dist/index.js +12 -7
- package/package.json +26 -23
- package/src/compare.ts +0 -57
- package/src/config.ts +0 -28
- package/src/hook.ts +0 -124
- package/src/index.ts +0 -140
- package/src/pull-baselines.ts +0 -87
- package/src/review.ts +0 -199
- package/src/status.ts +0 -42
- package/src/upload.ts +0 -74
- package/tsconfig.json +0 -14
package/dist/index.js
CHANGED
|
@@ -39,10 +39,11 @@ async function upload(config, options) {
|
|
|
39
39
|
}
|
|
40
40
|
const branch = options.branch ?? execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
41
41
|
const sha = options.sha ?? execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
42
|
-
console.log(chalk.dim(`Project:
|
|
43
|
-
console.log(chalk.dim(`
|
|
44
|
-
console.log(chalk.dim(`
|
|
45
|
-
console.log(chalk.dim(`
|
|
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}`));
|
|
46
47
|
console.log();
|
|
47
48
|
const files = await glob("**/*.png", { cwd: dir });
|
|
48
49
|
if (files.length === 0) {
|
|
@@ -54,6 +55,7 @@ async function upload(config, options) {
|
|
|
54
55
|
formData.append("project", config.project);
|
|
55
56
|
formData.append("branch", branch);
|
|
56
57
|
formData.append("gitSha", sha);
|
|
58
|
+
if (config.platform) formData.append("platform", config.platform);
|
|
57
59
|
for (const file of files) {
|
|
58
60
|
const fullPath = resolve2(dir, file);
|
|
59
61
|
const buffer = readFileSync2(fullPath);
|
|
@@ -446,21 +448,23 @@ async function hookUninstall(cwd = process.cwd()) {
|
|
|
446
448
|
// src/index.ts
|
|
447
449
|
var program = new Command();
|
|
448
450
|
program.name("git-shots").description("CLI for git-shots visual regression platform").version("0.1.0");
|
|
449
|
-
program.command("upload").description("Upload screenshots to git-shots").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("-d, --directory <path>", "Screenshots directory").option("-b, --branch <name>", "Git branch (auto-detected)").option("--sha <hash>", "Git SHA (auto-detected)").action(async (options) => {
|
|
451
|
+
program.command("upload").description("Upload screenshots to git-shots").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("-d, --directory <path>", "Screenshots directory").option("-b, --branch <name>", "Git branch (auto-detected)").option("--sha <hash>", "Git SHA (auto-detected)").option("--platform <name>", "Platform tag (e.g., android, web)").action(async (options) => {
|
|
450
452
|
const config = loadConfig();
|
|
451
453
|
if (options.project) config.project = options.project;
|
|
452
454
|
if (options.server) config.server = options.server;
|
|
453
455
|
if (options.directory) config.directory = options.directory;
|
|
456
|
+
if (options.platform) config.platform = options.platform;
|
|
454
457
|
if (!config.project) {
|
|
455
458
|
console.error("Error: project slug required. Use --project or .git-shots.json");
|
|
456
459
|
process.exit(1);
|
|
457
460
|
}
|
|
458
461
|
await upload(config, { branch: options.branch, sha: options.sha });
|
|
459
462
|
});
|
|
460
|
-
program.command("compare").description("Compare screenshots between branches").requiredOption("--head <branch>", "Head branch to compare").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("--base <branch>", "Base branch (default: main)").option("-t, --threshold <number>", "Mismatch threshold 0-1", parseFloat).action(async (options) => {
|
|
463
|
+
program.command("compare").description("Compare screenshots between branches").requiredOption("--head <branch>", "Head branch to compare").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("--base <branch>", "Base branch (default: main)").option("-t, --threshold <number>", "Mismatch threshold 0-1", parseFloat).option("--platform <name>", "Platform tag (e.g., android, web)").action(async (options) => {
|
|
461
464
|
const config = loadConfig();
|
|
462
465
|
if (options.project) config.project = options.project;
|
|
463
466
|
if (options.server) config.server = options.server;
|
|
467
|
+
if (options.platform) config.platform = options.platform;
|
|
464
468
|
if (!config.project) {
|
|
465
469
|
console.error("Error: project slug required. Use --project or .git-shots.json");
|
|
466
470
|
process.exit(1);
|
|
@@ -487,11 +491,12 @@ program.command("pull-baselines").description("Download baseline screenshots fro
|
|
|
487
491
|
}
|
|
488
492
|
await pullBaselines(config, { branch: options.branch, output: options.output });
|
|
489
493
|
});
|
|
490
|
-
program.command("review").description("Upload screenshots, create review session, and poll for verdict").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("-d, --directory <path>", "Screenshots directory").option("-b, --branch <name>", "Git branch (auto-detected)").option("--sha <hash>", "Git SHA (auto-detected)").option("--open", "Open review URL in browser", true).option("--no-open", "Do not open review URL in browser").option("--poll", "Poll for verdict and exit with code", true).option("--no-poll", "Do not poll for verdict").option("--timeout <seconds>", "Polling timeout in seconds", parseInt, 300).action(async (options) => {
|
|
494
|
+
program.command("review").description("Upload screenshots, create review session, and poll for verdict").option("-p, --project <slug>", "Project slug").option("-s, --server <url>", "Server URL").option("-d, --directory <path>", "Screenshots directory").option("-b, --branch <name>", "Git branch (auto-detected)").option("--sha <hash>", "Git SHA (auto-detected)").option("--platform <name>", "Platform tag (e.g., android, web)").option("--open", "Open review URL in browser", true).option("--no-open", "Do not open review URL in browser").option("--poll", "Poll for verdict and exit with code", true).option("--no-poll", "Do not poll for verdict").option("--timeout <seconds>", "Polling timeout in seconds", parseInt, 300).action(async (options) => {
|
|
491
495
|
const config = loadConfig();
|
|
492
496
|
if (options.project) config.project = options.project;
|
|
493
497
|
if (options.server) config.server = options.server;
|
|
494
498
|
if (options.directory) config.directory = options.directory;
|
|
499
|
+
if (options.platform) config.platform = options.platform;
|
|
495
500
|
if (!config.project) {
|
|
496
501
|
console.error("Error: project slug required. Use --project or .git-shots.json");
|
|
497
502
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "git-shots-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for git-shots visual regression platform",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"git-shots": "./dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "git-shots-cli",
|
|
3
|
+
"version": "0.3.0",
|
|
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
|
+
}
|
package/src/compare.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import type { GitShotsConfig } from './config.js';
|
|
3
|
-
|
|
4
|
-
export async function compare(
|
|
5
|
-
config: GitShotsConfig,
|
|
6
|
-
options: { base?: string; head: string; threshold?: number }
|
|
7
|
-
) {
|
|
8
|
-
const url = `${config.server}/api/compare`;
|
|
9
|
-
const body = {
|
|
10
|
-
project: config.project,
|
|
11
|
-
base: options.base ?? 'main',
|
|
12
|
-
head: options.head,
|
|
13
|
-
threshold: options.threshold ?? 0.1
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
console.log(chalk.dim(`Comparing ${body.base} vs ${body.head} for ${config.project}...`));
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const res = await fetch(url, {
|
|
20
|
-
method: 'POST',
|
|
21
|
-
headers: { 'Content-Type': 'application/json', Origin: config.server },
|
|
22
|
-
body: JSON.stringify(body)
|
|
23
|
-
});
|
|
24
|
-
const data = await res.json();
|
|
25
|
-
|
|
26
|
-
if (!res.ok) {
|
|
27
|
-
console.error(chalk.red(`Compare failed: ${JSON.stringify(data)}`));
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log();
|
|
32
|
-
console.log(`Compared ${chalk.bold(data.compared)} screens`);
|
|
33
|
-
console.log();
|
|
34
|
-
|
|
35
|
-
if (data.diffs.length === 0) {
|
|
36
|
-
console.log(chalk.green('No visual differences found!'));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Print table
|
|
41
|
-
console.log(chalk.dim('Screen'.padEnd(30) + 'Mismatch'.padEnd(15) + 'Pixels'));
|
|
42
|
-
console.log(chalk.dim('-'.repeat(55)));
|
|
43
|
-
|
|
44
|
-
for (const d of data.diffs) {
|
|
45
|
-
const pct = d.mismatchPercentage.toFixed(2) + '%';
|
|
46
|
-
const color = d.mismatchPercentage > 10 ? chalk.red : d.mismatchPercentage > 1 ? chalk.yellow : chalk.green;
|
|
47
|
-
console.log(
|
|
48
|
-
d.screen.padEnd(30) +
|
|
49
|
-
color(pct.padEnd(15)) +
|
|
50
|
-
chalk.dim(d.mismatchPixels.toLocaleString())
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error(chalk.red(`Request failed: ${err}`));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export interface GitShotsConfig {
|
|
5
|
-
project: string;
|
|
6
|
-
server: string;
|
|
7
|
-
directory: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const DEFAULT_CONFIG: GitShotsConfig = {
|
|
11
|
-
project: '',
|
|
12
|
-
server: 'https://git-shots.rijid356.workers.dev',
|
|
13
|
-
directory: 'docs/screenshots/current'
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function loadConfig(cwd: string = process.cwd()): GitShotsConfig {
|
|
17
|
-
const configPath = resolve(cwd, '.git-shots.json');
|
|
18
|
-
if (!existsSync(configPath)) {
|
|
19
|
-
return DEFAULT_CONFIG;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const raw = readFileSync(configPath, 'utf-8');
|
|
23
|
-
const parsed = JSON.parse(raw);
|
|
24
|
-
return { ...DEFAULT_CONFIG, ...parsed };
|
|
25
|
-
} catch {
|
|
26
|
-
return DEFAULT_CONFIG;
|
|
27
|
-
}
|
|
28
|
-
}
|
package/src/hook.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { existsSync, writeFileSync, unlinkSync, chmodSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, join } from 'node:path';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
|
|
6
|
-
const HOOK_MARKER = '# git-shots-hook';
|
|
7
|
-
|
|
8
|
-
const HOOK_SCRIPT = `#!/bin/sh
|
|
9
|
-
${HOOK_MARKER}
|
|
10
|
-
# Pre-push hook: runs git-shots visual review before pushing.
|
|
11
|
-
# Installed by: git-shots hook install
|
|
12
|
-
|
|
13
|
-
# Skip if no .git-shots.json config
|
|
14
|
-
if [ ! -f ".git-shots.json" ]; then
|
|
15
|
-
exit 0
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
# Read screenshots directory from config (default: docs/screenshots/current)
|
|
19
|
-
SCREENSHOTS_DIR=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('.git-shots.json','utf-8'));console.log(c.directory||'docs/screenshots/current')}catch{console.log('docs/screenshots/current')}" 2>/dev/null)
|
|
20
|
-
|
|
21
|
-
# Skip if no screenshots directory or no PNGs
|
|
22
|
-
if [ ! -d "$SCREENSHOTS_DIR" ]; then
|
|
23
|
-
exit 0
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
PNG_COUNT=$(find "$SCREENSHOTS_DIR" -name "*.png" 2>/dev/null | head -1)
|
|
27
|
-
if [ -z "$PNG_COUNT" ]; then
|
|
28
|
-
exit 0
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Skip on main/master — no base to diff against
|
|
32
|
-
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
33
|
-
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
34
|
-
exit 0
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
echo ""
|
|
38
|
-
echo "git-shots: Running visual review before push..."
|
|
39
|
-
echo ""
|
|
40
|
-
|
|
41
|
-
git-shots review
|
|
42
|
-
EXIT_CODE=$?
|
|
43
|
-
|
|
44
|
-
if [ $EXIT_CODE -eq 1 ]; then
|
|
45
|
-
echo ""
|
|
46
|
-
echo "git-shots: Visual review rejected. Push blocked."
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
if [ $EXIT_CODE -eq 2 ]; then
|
|
51
|
-
echo ""
|
|
52
|
-
echo "git-shots: Visual review timed out. Push allowed (review pending)."
|
|
53
|
-
exit 0
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
exit 0
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
function getGitDir(cwd: string): string {
|
|
60
|
-
try {
|
|
61
|
-
return execSync('git rev-parse --git-dir', { cwd, encoding: 'utf-8' }).trim();
|
|
62
|
-
} catch {
|
|
63
|
-
throw new Error('Not a git repository');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function hookInstall(cwd: string = process.cwd()) {
|
|
68
|
-
const gitDir = getGitDir(cwd);
|
|
69
|
-
const hooksDir = resolve(cwd, gitDir, 'hooks');
|
|
70
|
-
const hookPath = join(hooksDir, 'pre-push');
|
|
71
|
-
|
|
72
|
-
// Check if a pre-push hook already exists
|
|
73
|
-
if (existsSync(hookPath)) {
|
|
74
|
-
const existing = readFileSync(hookPath, 'utf-8');
|
|
75
|
-
if (existing.includes(HOOK_MARKER)) {
|
|
76
|
-
console.log(chalk.yellow('git-shots pre-push hook is already installed.'));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Another hook exists — don't overwrite
|
|
81
|
-
console.error(
|
|
82
|
-
chalk.red('A pre-push hook already exists at ') + chalk.dim(hookPath)
|
|
83
|
-
);
|
|
84
|
-
console.error(
|
|
85
|
-
chalk.dim('Remove or rename it first, then re-run this command.')
|
|
86
|
-
);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
writeFileSync(hookPath, HOOK_SCRIPT, { mode: 0o755 });
|
|
91
|
-
|
|
92
|
-
// chmod on non-Windows (writeFileSync mode doesn't always stick)
|
|
93
|
-
try {
|
|
94
|
-
chmodSync(hookPath, 0o755);
|
|
95
|
-
} catch {
|
|
96
|
-
// Windows — mode already set via writeFileSync
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(chalk.green('Installed pre-push hook at ') + chalk.dim(hookPath));
|
|
100
|
-
console.log();
|
|
101
|
-
console.log(chalk.dim('The hook will run `git-shots review` before every push'));
|
|
102
|
-
console.log(chalk.dim('when .git-shots.json is present and screenshots exist.'));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function hookUninstall(cwd: string = process.cwd()) {
|
|
106
|
-
const gitDir = getGitDir(cwd);
|
|
107
|
-
const hooksDir = resolve(cwd, gitDir, 'hooks');
|
|
108
|
-
const hookPath = join(hooksDir, 'pre-push');
|
|
109
|
-
|
|
110
|
-
if (!existsSync(hookPath)) {
|
|
111
|
-
console.log(chalk.yellow('No pre-push hook found.'));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const existing = readFileSync(hookPath, 'utf-8');
|
|
116
|
-
if (!existing.includes(HOOK_MARKER)) {
|
|
117
|
-
console.error(chalk.red('Pre-push hook exists but was not installed by git-shots.'));
|
|
118
|
-
console.error(chalk.dim('Remove it manually if you want: ') + chalk.dim(hookPath));
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
unlinkSync(hookPath);
|
|
123
|
-
console.log(chalk.green('Removed git-shots pre-push hook.'));
|
|
124
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { loadConfig } from './config.js';
|
|
4
|
-
import { upload } from './upload.js';
|
|
5
|
-
import { compare } from './compare.js';
|
|
6
|
-
import { status } from './status.js';
|
|
7
|
-
import { pullBaselines } from './pull-baselines.js';
|
|
8
|
-
import { review } from './review.js';
|
|
9
|
-
import { hookInstall, hookUninstall } from './hook.js';
|
|
10
|
-
|
|
11
|
-
const program = new Command();
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.name('git-shots')
|
|
15
|
-
.description('CLI for git-shots visual regression platform')
|
|
16
|
-
.version('0.1.0');
|
|
17
|
-
|
|
18
|
-
program
|
|
19
|
-
.command('upload')
|
|
20
|
-
.description('Upload screenshots to git-shots')
|
|
21
|
-
.option('-p, --project <slug>', 'Project slug')
|
|
22
|
-
.option('-s, --server <url>', 'Server URL')
|
|
23
|
-
.option('-d, --directory <path>', 'Screenshots directory')
|
|
24
|
-
.option('-b, --branch <name>', 'Git branch (auto-detected)')
|
|
25
|
-
.option('--sha <hash>', 'Git SHA (auto-detected)')
|
|
26
|
-
.action(async (options) => {
|
|
27
|
-
const config = loadConfig();
|
|
28
|
-
if (options.project) config.project = options.project;
|
|
29
|
-
if (options.server) config.server = options.server;
|
|
30
|
-
if (options.directory) config.directory = options.directory;
|
|
31
|
-
if (!config.project) {
|
|
32
|
-
console.error('Error: project slug required. Use --project or .git-shots.json');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
await upload(config, { branch: options.branch, sha: options.sha });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
program
|
|
39
|
-
.command('compare')
|
|
40
|
-
.description('Compare screenshots between branches')
|
|
41
|
-
.requiredOption('--head <branch>', 'Head branch to compare')
|
|
42
|
-
.option('-p, --project <slug>', 'Project slug')
|
|
43
|
-
.option('-s, --server <url>', 'Server URL')
|
|
44
|
-
.option('--base <branch>', 'Base branch (default: main)')
|
|
45
|
-
.option('-t, --threshold <number>', 'Mismatch threshold 0-1', parseFloat)
|
|
46
|
-
.action(async (options) => {
|
|
47
|
-
const config = loadConfig();
|
|
48
|
-
if (options.project) config.project = options.project;
|
|
49
|
-
if (options.server) config.server = options.server;
|
|
50
|
-
if (!config.project) {
|
|
51
|
-
console.error('Error: project slug required. Use --project or .git-shots.json');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
await compare(config, { base: options.base, head: options.head, threshold: options.threshold });
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
program
|
|
58
|
-
.command('status')
|
|
59
|
-
.description('Show current diff status')
|
|
60
|
-
.option('-p, --project <slug>', 'Project slug')
|
|
61
|
-
.option('-s, --server <url>', 'Server URL')
|
|
62
|
-
.action(async (options) => {
|
|
63
|
-
const config = loadConfig();
|
|
64
|
-
if (options.project) config.project = options.project;
|
|
65
|
-
if (options.server) config.server = options.server;
|
|
66
|
-
if (!config.project) {
|
|
67
|
-
console.error('Error: project slug required. Use --project or .git-shots.json');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
await status(config);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
program
|
|
74
|
-
.command('pull-baselines')
|
|
75
|
-
.description('Download baseline screenshots from git-shots')
|
|
76
|
-
.option('-p, --project <slug>', 'Project slug')
|
|
77
|
-
.option('-s, --server <url>', 'Server URL')
|
|
78
|
-
.option('-o, --output <path>', 'Output directory')
|
|
79
|
-
.option('-b, --branch <name>', 'Branch to pull baselines from (default: main)')
|
|
80
|
-
.action(async (options) => {
|
|
81
|
-
const config = loadConfig();
|
|
82
|
-
if (options.project) config.project = options.project;
|
|
83
|
-
if (options.server) config.server = options.server;
|
|
84
|
-
if (!config.project) {
|
|
85
|
-
console.error('Error: project slug required. Use --project or .git-shots.json');
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
await pullBaselines(config, { branch: options.branch, output: options.output });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
program
|
|
92
|
-
.command('review')
|
|
93
|
-
.description('Upload screenshots, create review session, and poll for verdict')
|
|
94
|
-
.option('-p, --project <slug>', 'Project slug')
|
|
95
|
-
.option('-s, --server <url>', 'Server URL')
|
|
96
|
-
.option('-d, --directory <path>', 'Screenshots directory')
|
|
97
|
-
.option('-b, --branch <name>', 'Git branch (auto-detected)')
|
|
98
|
-
.option('--sha <hash>', 'Git SHA (auto-detected)')
|
|
99
|
-
.option('--open', 'Open review URL in browser', true)
|
|
100
|
-
.option('--no-open', 'Do not open review URL in browser')
|
|
101
|
-
.option('--poll', 'Poll for verdict and exit with code', true)
|
|
102
|
-
.option('--no-poll', 'Do not poll for verdict')
|
|
103
|
-
.option('--timeout <seconds>', 'Polling timeout in seconds', parseInt, 300)
|
|
104
|
-
.action(async (options) => {
|
|
105
|
-
const config = loadConfig();
|
|
106
|
-
if (options.project) config.project = options.project;
|
|
107
|
-
if (options.server) config.server = options.server;
|
|
108
|
-
if (options.directory) config.directory = options.directory;
|
|
109
|
-
if (!config.project) {
|
|
110
|
-
console.error('Error: project slug required. Use --project or .git-shots.json');
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
await review(config, {
|
|
114
|
-
branch: options.branch,
|
|
115
|
-
sha: options.sha,
|
|
116
|
-
open: options.open,
|
|
117
|
-
poll: options.poll,
|
|
118
|
-
timeout: options.timeout
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const hook = program
|
|
123
|
-
.command('hook')
|
|
124
|
-
.description('Manage git hooks for automatic visual review');
|
|
125
|
-
|
|
126
|
-
hook
|
|
127
|
-
.command('install')
|
|
128
|
-
.description('Install a pre-push hook that runs git-shots review before each push')
|
|
129
|
-
.action(async () => {
|
|
130
|
-
await hookInstall();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
hook
|
|
134
|
-
.command('uninstall')
|
|
135
|
-
.description('Remove the git-shots pre-push hook')
|
|
136
|
-
.action(async () => {
|
|
137
|
-
await hookUninstall();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
program.parse();
|
package/src/pull-baselines.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, dirname } from 'node:path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import type { GitShotsConfig } from './config.js';
|
|
5
|
-
|
|
6
|
-
interface Snapshot {
|
|
7
|
-
screen_slug: string;
|
|
8
|
-
category: string | null;
|
|
9
|
-
r2_key: string;
|
|
10
|
-
git_sha: string;
|
|
11
|
-
width: number;
|
|
12
|
-
height: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function pullBaselines(
|
|
16
|
-
config: GitShotsConfig,
|
|
17
|
-
options: { branch?: string; output?: string }
|
|
18
|
-
) {
|
|
19
|
-
const branch = options.branch ?? 'main';
|
|
20
|
-
const outputDir = resolve(process.cwd(), options.output ?? config.directory);
|
|
21
|
-
|
|
22
|
-
console.log(chalk.dim(`Project: ${config.project}`));
|
|
23
|
-
console.log(chalk.dim(`Branch: ${branch}`));
|
|
24
|
-
console.log(chalk.dim(`Output: ${outputDir}`));
|
|
25
|
-
console.log();
|
|
26
|
-
|
|
27
|
-
// Fetch manifest
|
|
28
|
-
const manifestUrl = `${config.server}/api/projects/${encodeURIComponent(config.project)}/snapshots?branch=${encodeURIComponent(branch)}`;
|
|
29
|
-
let snapshots: Snapshot[];
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const res = await fetch(manifestUrl);
|
|
33
|
-
const data = await res.json();
|
|
34
|
-
|
|
35
|
-
if (!res.ok) {
|
|
36
|
-
console.error(chalk.red(`Failed to fetch snapshots: ${JSON.stringify(data)}`));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
snapshots = data.snapshots;
|
|
41
|
-
} catch (err) {
|
|
42
|
-
console.error(chalk.red(`Request failed: ${err}`));
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (snapshots.length === 0) {
|
|
47
|
-
console.log(chalk.yellow('No baseline snapshots found.'));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
console.log(chalk.dim(`Found ${snapshots.length} baselines to download`));
|
|
52
|
-
console.log();
|
|
53
|
-
|
|
54
|
-
// Download in batches of 5
|
|
55
|
-
const batchSize = 5;
|
|
56
|
-
let downloaded = 0;
|
|
57
|
-
|
|
58
|
-
for (let i = 0; i < snapshots.length; i += batchSize) {
|
|
59
|
-
const batch = snapshots.slice(i, i + batchSize);
|
|
60
|
-
await Promise.all(
|
|
61
|
-
batch.map(async (snap) => {
|
|
62
|
-
const imageUrl = `${config.server}/api/images/${snap.r2_key}`;
|
|
63
|
-
const res = await fetch(imageUrl);
|
|
64
|
-
if (!res.ok) {
|
|
65
|
-
console.error(chalk.red(` Failed to download ${snap.screen_slug}: ${res.status}`));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
70
|
-
const subDir = snap.category ?? '';
|
|
71
|
-
const filePath = resolve(outputDir, subDir, `${snap.screen_slug}.png`);
|
|
72
|
-
|
|
73
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
74
|
-
writeFileSync(filePath, buffer);
|
|
75
|
-
|
|
76
|
-
downloaded++;
|
|
77
|
-
console.log(
|
|
78
|
-
chalk.green(` [${downloaded}/${snapshots.length}]`) +
|
|
79
|
-
` ${subDir ? subDir + '/' : ''}${snap.screen_slug}.png`
|
|
80
|
-
);
|
|
81
|
-
})
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
console.log();
|
|
86
|
-
console.log(chalk.green(`Downloaded ${downloaded} baselines to ${outputDir}`));
|
|
87
|
-
}
|
package/src/review.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { platform } from 'node:os';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import type { GitShotsConfig } from './config.js';
|
|
5
|
-
import { upload } from './upload.js';
|
|
6
|
-
|
|
7
|
-
interface ReviewOptions {
|
|
8
|
-
branch?: string;
|
|
9
|
-
sha?: string;
|
|
10
|
-
open?: boolean;
|
|
11
|
-
poll?: boolean;
|
|
12
|
-
timeout?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ReviewResponse {
|
|
16
|
-
review: {
|
|
17
|
-
id: number;
|
|
18
|
-
status: string;
|
|
19
|
-
url: string;
|
|
20
|
-
};
|
|
21
|
-
diffs: Array<{
|
|
22
|
-
screen: string;
|
|
23
|
-
mismatchPercentage: number;
|
|
24
|
-
mismatchPixels: number;
|
|
25
|
-
}>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ReviewStatusResponse {
|
|
29
|
-
review: {
|
|
30
|
-
id: number;
|
|
31
|
-
status: string;
|
|
32
|
-
};
|
|
33
|
-
diffs: Array<{
|
|
34
|
-
base: { screen_slug: string };
|
|
35
|
-
mismatch_percentage: number;
|
|
36
|
-
status: string;
|
|
37
|
-
}>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function openBrowser(url: string): void {
|
|
41
|
-
try {
|
|
42
|
-
const os = platform();
|
|
43
|
-
if (os === 'win32') {
|
|
44
|
-
execSync(`start "" "${url}"`, { stdio: 'ignore' });
|
|
45
|
-
} else if (os === 'darwin') {
|
|
46
|
-
execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
47
|
-
} else {
|
|
48
|
-
execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
console.log(chalk.dim(`Could not open browser. Visit the URL manually.`));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function sleep(ms: number): Promise<void> {
|
|
56
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function review(config: GitShotsConfig, options: ReviewOptions) {
|
|
60
|
-
const shouldOpen = options.open ?? true;
|
|
61
|
-
const shouldPoll = options.poll ?? true;
|
|
62
|
-
const timeoutSec = options.timeout ?? 300;
|
|
63
|
-
|
|
64
|
-
// Get git info
|
|
65
|
-
const branch =
|
|
66
|
-
options.branch ??
|
|
67
|
-
execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
68
|
-
const sha =
|
|
69
|
-
options.sha ?? execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
|
|
70
|
-
|
|
71
|
-
console.log(chalk.dim(`Project: ${config.project}`));
|
|
72
|
-
console.log(chalk.dim(`Branch: ${branch}`));
|
|
73
|
-
console.log(chalk.dim(`SHA: ${sha.slice(0, 7)}`));
|
|
74
|
-
console.log();
|
|
75
|
-
|
|
76
|
-
// Step 1: Upload screenshots
|
|
77
|
-
console.log(chalk.dim('Uploading screenshots...'));
|
|
78
|
-
await upload(config, { branch, sha });
|
|
79
|
-
console.log();
|
|
80
|
-
|
|
81
|
-
// Step 2: Create review session via POST /api/reviews
|
|
82
|
-
console.log(chalk.dim('Creating review session...'));
|
|
83
|
-
|
|
84
|
-
const reviewUrl = `${config.server}/api/reviews`;
|
|
85
|
-
let reviewData: ReviewResponse;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const res = await fetch(reviewUrl, {
|
|
89
|
-
method: 'POST',
|
|
90
|
-
headers: { 'Content-Type': 'application/json', Origin: config.server },
|
|
91
|
-
body: JSON.stringify({
|
|
92
|
-
project: config.project,
|
|
93
|
-
branch,
|
|
94
|
-
gitSha: sha
|
|
95
|
-
})
|
|
96
|
-
});
|
|
97
|
-
const data = await res.json();
|
|
98
|
-
|
|
99
|
-
if (!res.ok) {
|
|
100
|
-
console.error(chalk.red(`Failed to create review: ${JSON.stringify(data)}`));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
reviewData = data as ReviewResponse;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error(chalk.red(`Request failed: ${err}`));
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Step 3: Check if all diffs are 0% mismatch
|
|
111
|
-
const allZero =
|
|
112
|
-
reviewData.diffs.length === 0 ||
|
|
113
|
-
reviewData.diffs.every((d) => d.mismatchPercentage === 0);
|
|
114
|
-
|
|
115
|
-
if (allZero) {
|
|
116
|
-
console.log(chalk.green('No visual changes detected.'));
|
|
117
|
-
process.exit(0);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Step 4: Print review URL and diff summary
|
|
121
|
-
const sessionUrl = `${config.server}/reviews/${reviewData.review.id}`;
|
|
122
|
-
console.log();
|
|
123
|
-
console.log(chalk.bold('Visual changes detected:'));
|
|
124
|
-
console.log();
|
|
125
|
-
|
|
126
|
-
for (const d of reviewData.diffs) {
|
|
127
|
-
const pct = d.mismatchPercentage.toFixed(2) + '%';
|
|
128
|
-
const color =
|
|
129
|
-
d.mismatchPercentage > 10
|
|
130
|
-
? chalk.red
|
|
131
|
-
: d.mismatchPercentage > 1
|
|
132
|
-
? chalk.yellow
|
|
133
|
-
: chalk.green;
|
|
134
|
-
console.log(` ${d.screen.padEnd(30)} ${color(pct)}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
console.log();
|
|
138
|
-
console.log(`Review: ${chalk.cyan(sessionUrl)}`);
|
|
139
|
-
|
|
140
|
-
// Step 5: Open in browser
|
|
141
|
-
if (shouldOpen) {
|
|
142
|
-
openBrowser(sessionUrl);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Step 6: Poll for verdict
|
|
146
|
-
if (!shouldPoll) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
console.log();
|
|
151
|
-
console.log(chalk.dim(`Polling for verdict (timeout: ${timeoutSec}s)...`));
|
|
152
|
-
|
|
153
|
-
const pollInterval = 3000;
|
|
154
|
-
const startTime = Date.now();
|
|
155
|
-
const deadline = startTime + timeoutSec * 1000;
|
|
156
|
-
|
|
157
|
-
while (Date.now() < deadline) {
|
|
158
|
-
await sleep(pollInterval);
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const res = await fetch(`${config.server}/api/reviews/${reviewData.review.id}`, {
|
|
162
|
-
headers: { Origin: config.server }
|
|
163
|
-
});
|
|
164
|
-
const data = (await res.json()) as ReviewStatusResponse;
|
|
165
|
-
|
|
166
|
-
if (!res.ok) continue;
|
|
167
|
-
|
|
168
|
-
if (data.review.status === 'approved') {
|
|
169
|
-
console.log();
|
|
170
|
-
console.log(chalk.green.bold('Review approved!'));
|
|
171
|
-
process.exit(0);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (data.review.status === 'rejected') {
|
|
175
|
-
console.log();
|
|
176
|
-
console.log(chalk.red.bold('Review rejected.'));
|
|
177
|
-
|
|
178
|
-
// Show which screens had issues
|
|
179
|
-
const rejected = data.diffs.filter((d) => d.status === 'rejected');
|
|
180
|
-
if (rejected.length > 0) {
|
|
181
|
-
console.log(chalk.dim('Rejected screens:'));
|
|
182
|
-
for (const d of rejected) {
|
|
183
|
-
console.log(chalk.red(` - ${d.base.screen_slug}`));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
} catch {
|
|
190
|
-
// Network error — keep polling
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Timeout
|
|
195
|
-
console.log();
|
|
196
|
-
console.log(chalk.yellow(`Review timed out after ${timeoutSec}s.`));
|
|
197
|
-
console.log(chalk.dim(`Visit ${sessionUrl} to complete the review.`));
|
|
198
|
-
process.exit(2);
|
|
199
|
-
}
|
package/src/status.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import type { GitShotsConfig } from './config.js';
|
|
3
|
-
|
|
4
|
-
export async function status(config: GitShotsConfig) {
|
|
5
|
-
const url = `${config.server}/api/diffs?project=${encodeURIComponent(config.project)}`;
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const res = await fetch(url);
|
|
9
|
-
const data = await res.json();
|
|
10
|
-
|
|
11
|
-
if (!res.ok) {
|
|
12
|
-
console.error(chalk.red(`Status failed: ${JSON.stringify(data)}`));
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (data.length === 0) {
|
|
17
|
-
console.log(chalk.dim('No diffs found for this project.'));
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
console.log(`${chalk.bold(data.length)} diffs for ${config.project}`);
|
|
22
|
-
console.log();
|
|
23
|
-
console.log(chalk.dim('ID'.padEnd(8) + 'Status'.padEnd(12) + 'Mismatch'.padEnd(12) + 'Date'));
|
|
24
|
-
console.log(chalk.dim('-'.repeat(50)));
|
|
25
|
-
|
|
26
|
-
for (const { diff } of data) {
|
|
27
|
-
const statusColor =
|
|
28
|
-
diff.status === 'approved' ? chalk.green :
|
|
29
|
-
diff.status === 'rejected' ? chalk.red :
|
|
30
|
-
chalk.yellow;
|
|
31
|
-
console.log(
|
|
32
|
-
String(diff.id).padEnd(8) +
|
|
33
|
-
statusColor(diff.status.padEnd(12)) +
|
|
34
|
-
(diff.mismatch_percentage.toFixed(2) + '%').padEnd(12) +
|
|
35
|
-
chalk.dim(new Date(diff.created_at * 1000).toLocaleDateString())
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
} catch (err) {
|
|
39
|
-
console.error(chalk.red(`Request failed: ${err}`));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
}
|
package/src/upload.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { resolve, basename, relative, dirname } from 'node:path';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import type { GitShotsConfig } from './config.js';
|
|
7
|
-
|
|
8
|
-
export async function upload(config: GitShotsConfig, options: { branch?: string; sha?: string }) {
|
|
9
|
-
const dir = resolve(process.cwd(), config.directory);
|
|
10
|
-
if (!existsSync(dir)) {
|
|
11
|
-
console.error(chalk.red(`Directory not found: ${dir}`));
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Get git info
|
|
16
|
-
const branch = options.branch ?? execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
17
|
-
const sha = options.sha ?? execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
|
|
18
|
-
|
|
19
|
-
console.log(chalk.dim(`Project: ${config.project}`));
|
|
20
|
-
console.log(chalk.dim(`Branch: ${branch}`));
|
|
21
|
-
console.log(chalk.dim(`SHA: ${sha.slice(0, 7)}`));
|
|
22
|
-
console.log(chalk.dim(`Dir: ${dir}`));
|
|
23
|
-
console.log();
|
|
24
|
-
|
|
25
|
-
// Find all PNGs
|
|
26
|
-
const files = await glob('**/*.png', { cwd: dir });
|
|
27
|
-
if (files.length === 0) {
|
|
28
|
-
console.log(chalk.yellow('No PNG files found.'));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(chalk.dim(`Found ${files.length} screenshots`));
|
|
33
|
-
|
|
34
|
-
// Build multipart form
|
|
35
|
-
const formData = new FormData();
|
|
36
|
-
formData.append('project', config.project);
|
|
37
|
-
formData.append('branch', branch);
|
|
38
|
-
formData.append('gitSha', sha);
|
|
39
|
-
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
const fullPath = resolve(dir, file);
|
|
42
|
-
const buffer = readFileSync(fullPath);
|
|
43
|
-
const blob = new Blob([buffer], { type: 'image/png' });
|
|
44
|
-
|
|
45
|
-
// Use directory as field name for category derivation
|
|
46
|
-
const dirName = dirname(file);
|
|
47
|
-
const fieldName = dirName !== '.' ? `${dirName}/${basename(file)}` : basename(file);
|
|
48
|
-
|
|
49
|
-
formData.append(fieldName, blob, basename(file));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Upload
|
|
53
|
-
const url = `${config.server}/api/upload`;
|
|
54
|
-
console.log(chalk.dim(`Uploading to ${url}...`));
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const res = await fetch(url, {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
body: formData,
|
|
60
|
-
headers: { Origin: config.server },
|
|
61
|
-
});
|
|
62
|
-
const data = await res.json();
|
|
63
|
-
|
|
64
|
-
if (!res.ok) {
|
|
65
|
-
console.error(chalk.red(`Upload failed: ${JSON.stringify(data)}`));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log(chalk.green(`Uploaded ${data.uploaded} screenshots`));
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error(chalk.red(`Request failed: ${err}`));
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"outDir": "dist",
|
|
9
|
-
"rootDir": "src",
|
|
10
|
-
"declaration": true,
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src"]
|
|
14
|
-
}
|