@pixeyedev/cli 0.1.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.
@@ -0,0 +1,14 @@
1
+ export interface PixeyeConfig {
2
+ server?: string;
3
+ token?: string;
4
+ }
5
+ export interface ProjectConfig {
6
+ server: string;
7
+ token: string;
8
+ screenshotDir?: string;
9
+ }
10
+ export declare function readGlobalConfig(): Promise<PixeyeConfig>;
11
+ export declare function writeGlobalConfig(config: PixeyeConfig): Promise<void>;
12
+ export declare function readProjectConfig(cwd: string): Promise<ProjectConfig | null>;
13
+ export declare function writeProjectConfig(cwd: string, config: ProjectConfig): Promise<void>;
14
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,CAAC,CAO9D;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAOlF;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAO1F"}
package/dist/config.js ADDED
@@ -0,0 +1,46 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const GLOBAL_CONFIG_DIR = join(homedir(), ".pixeye");
5
+ const GLOBAL_CONFIG_PATH = join(GLOBAL_CONFIG_DIR, "config.json");
6
+ const PROJECT_CONFIG_NAME = ".pixeye.yml";
7
+ export async function readGlobalConfig() {
8
+ try {
9
+ const content = await readFile(GLOBAL_CONFIG_PATH, "utf-8");
10
+ return JSON.parse(content);
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ }
16
+ export async function writeGlobalConfig(config) {
17
+ await mkdir(GLOBAL_CONFIG_DIR, { recursive: true });
18
+ await writeFile(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
19
+ }
20
+ export async function readProjectConfig(cwd) {
21
+ try {
22
+ const content = await readFile(join(cwd, PROJECT_CONFIG_NAME), "utf-8");
23
+ return parseYamlLike(content);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ export async function writeProjectConfig(cwd, config) {
30
+ const lines = [
31
+ `server: ${config.server}`,
32
+ `token: ${config.token}`,
33
+ ...(config.screenshotDir ? [`screenshotDir: ${config.screenshotDir}`] : []),
34
+ ];
35
+ await writeFile(join(cwd, PROJECT_CONFIG_NAME), lines.join("\n") + "\n");
36
+ }
37
+ function parseYamlLike(content) {
38
+ const result = {};
39
+ for (const line of content.split("\n")) {
40
+ const match = line.match(/^(\w+):\s*(.+)$/);
41
+ if (match)
42
+ result[match[1]] = match[2].trim();
43
+ }
44
+ return result;
45
+ }
46
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAalC,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACrD,MAAM,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;AAClE,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAoB;IAC1D,MAAM,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW;IACjD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW,EAAE,MAAqB;IACzE,MAAM,KAAK,GAAG;QACZ,WAAW,MAAM,CAAC,MAAM,EAAE;QAC1B,UAAU,MAAM,CAAC,KAAK,EAAE;QACxB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,kBAAkB,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5E,CAAC;IACF,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC5C,IAAI,KAAK;YAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,MAAkC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface DiscoveredFile {
2
+ filePath: string;
3
+ name: string;
4
+ fileHash: string;
5
+ width: number;
6
+ height: number;
7
+ metadata?: Record<string, unknown>;
8
+ }
9
+ export declare function discoverScreenshots(directory: string): Promise<DiscoveredFile[]>;
10
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAWD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA4BtF"}
@@ -0,0 +1,36 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { glob } from "glob";
4
+ function readPngDimensions(buffer) {
5
+ // PNG header: 8-byte signature, then IHDR chunk
6
+ // Bytes 16-19: width (big-endian uint32)
7
+ // Bytes 20-23: height (big-endian uint32)
8
+ const width = buffer.readUInt32BE(16);
9
+ const height = buffer.readUInt32BE(20);
10
+ return { width, height };
11
+ }
12
+ export async function discoverScreenshots(directory) {
13
+ const pngFiles = await glob("**/*.png", { cwd: directory, posix: true });
14
+ pngFiles.sort();
15
+ const results = [];
16
+ for (const relativePath of pngFiles) {
17
+ const filePath = `${directory}/${relativePath}`;
18
+ const fileBuffer = await readFile(filePath);
19
+ const fileHash = createHash("sha256").update(fileBuffer).digest("hex");
20
+ const { width, height } = readPngDimensions(fileBuffer);
21
+ const name = relativePath.replace(/\.png$/, "");
22
+ // Check for companion .argos.json metadata file
23
+ const metadataPath = filePath.replace(/\.png$/, ".argos.json");
24
+ let metadata;
25
+ try {
26
+ const metadataContent = await readFile(metadataPath, "utf-8");
27
+ metadata = JSON.parse(metadataContent);
28
+ }
29
+ catch {
30
+ // No metadata file or invalid JSON — skip
31
+ }
32
+ results.push({ filePath, name, fileHash, width, height, metadata });
33
+ }
34
+ return results;
35
+ }
36
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,SAAS,iBAAiB,CAAC,MAAc;IACvC,gDAAgD;IAChD,yCAAyC;IACzC,0CAA0C;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACvC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IACzD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEhB,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,YAAY,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,YAAY,EAAE,CAAC;QAChD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE5C,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEhD,gDAAgD;QAChD,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC/D,IAAI,QAA6C,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAC9D,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { Command } from "commander";
4
+ import { discoverScreenshots } from "./discover.js";
5
+ import { uploadScreenshots } from "./upload.js";
6
+ import { initCommand } from "./init.js";
7
+ import { loginCommand } from "./login.js";
8
+ import { readProjectConfig, readGlobalConfig } from "./config.js";
9
+ const program = new Command();
10
+ program.name("pixeye").description("Pixeye — Visual regression testing CLI").version("0.1.0");
11
+ // ─── init ──────────────────────────────────────────────────────────────────────
12
+ program
13
+ .command("init")
14
+ .description("Initialize Pixeye for this project (creates .pixeye.yml)")
15
+ .action(initCommand);
16
+ // ─── login ─────────────────────────────────────────────────────────────────────
17
+ program
18
+ .command("login")
19
+ .description("Save API credentials globally (~/.pixeye/config.json)")
20
+ .option("--server <url>", "API server URL")
21
+ .option("--token <token>", "API token")
22
+ .action(loginCommand);
23
+ // ─── upload ────────────────────────────────────────────────────────────────────
24
+ program
25
+ .command("upload")
26
+ .description("Upload screenshots from a directory")
27
+ .argument("[directory]", "Directory containing PNG screenshots")
28
+ .option("--server <url>", "API server URL")
29
+ .option("--token <token>", "Project API token")
30
+ .requiredOption("--commit <sha>", "Git commit SHA")
31
+ .requiredOption("--branch <ref>", "Git branch name")
32
+ .option("--pr <number>", "PR number")
33
+ .option("--shard-index <n>", "Current shard index", "0")
34
+ .option("--shard-total <n>", "Total shard count", "1")
35
+ .action(async (directory, options) => {
36
+ // Resolve config from .pixeye.yml → ~/.pixeye/config.json → CLI flags
37
+ const projectConfig = await readProjectConfig(process.cwd());
38
+ const globalConfig = await readGlobalConfig();
39
+ const server = options.server ?? projectConfig?.server ?? globalConfig.server;
40
+ const token = options.token ?? projectConfig?.token ?? globalConfig.token;
41
+ const dir = directory ?? projectConfig?.screenshotDir ?? "./screenshots";
42
+ if (!server) {
43
+ console.error("Error: --server is required (or set in .pixeye.yml / pixeye login)");
44
+ process.exit(1);
45
+ }
46
+ if (!token) {
47
+ console.error("Error: --token is required (or set in .pixeye.yml / pixeye login)");
48
+ process.exit(1);
49
+ }
50
+ const resolvedDir = resolve(dir);
51
+ console.log(`Discovering screenshots in: ${resolvedDir}`);
52
+ const files = await discoverScreenshots(resolvedDir);
53
+ console.log(`Found ${files.length} screenshots.`);
54
+ if (files.length === 0) {
55
+ console.log("No screenshots found. Nothing to upload.");
56
+ return;
57
+ }
58
+ await uploadScreenshots(files, {
59
+ serverUrl: server.replace(/\/$/, ""),
60
+ projectToken: token,
61
+ commitSha: options.commit,
62
+ branch: options.branch,
63
+ prNumber: options.pr ? parseInt(options.pr, 10) : undefined,
64
+ shardIndex: parseInt(options.shardIndex, 10),
65
+ shardTotal: parseInt(options.shardTotal, 10),
66
+ });
67
+ });
68
+ // ─── storybook-upload ─────────────────────────────────────────────────────────
69
+ program
70
+ .command("storybook-upload")
71
+ .description("Upload a built static Storybook for per-branch hosting")
72
+ .argument("[directory]", "Storybook build output directory", "./storybook-static")
73
+ .option("--server <url>", "API server URL")
74
+ .option("--token <token>", "Project API token")
75
+ .requiredOption("--commit <sha>", "Git commit SHA")
76
+ .requiredOption("--branch <ref>", "Git branch name")
77
+ .action(async (directory, options) => {
78
+ const projectConfig = await readProjectConfig(process.cwd());
79
+ const globalConfig = await readGlobalConfig();
80
+ const server = options.server ?? projectConfig?.server ?? globalConfig.server;
81
+ const token = options.token ?? projectConfig?.token ?? globalConfig.token;
82
+ if (!server || !token) {
83
+ console.error("Error: --server and --token are required");
84
+ process.exit(1);
85
+ }
86
+ const { uploadStorybook } = await import("./storybook-upload.js");
87
+ await uploadStorybook(resolve(directory), {
88
+ serverUrl: server.replace(/\/$/, ""),
89
+ projectToken: token,
90
+ commitSha: options.commit,
91
+ branch: options.branch,
92
+ });
93
+ });
94
+ // ─── storybook-test ───────────────────────────────────────────────────────────
95
+ program
96
+ .command("storybook-test")
97
+ .description("Capture screenshots of every Storybook story and upload to Pixeye")
98
+ .argument("[directory]", "Storybook build output directory", "./storybook-static")
99
+ .option("--server <url>", "API server URL")
100
+ .option("--token <token>", "Project API token")
101
+ .requiredOption("--commit <sha>", "Git commit SHA")
102
+ .requiredOption("--branch <ref>", "Git branch name")
103
+ .option("--pr <number>", "PR number")
104
+ .option("--shard-index <n>", "Current shard index", "0")
105
+ .option("--shard-total <n>", "Total shard count", "1")
106
+ .action(async (directory, options) => {
107
+ const projectConfig = await readProjectConfig(process.cwd());
108
+ const globalConfig = await readGlobalConfig();
109
+ const server = options.server ?? projectConfig?.server ?? globalConfig.server;
110
+ const token = options.token ?? projectConfig?.token ?? globalConfig.token;
111
+ if (!server || !token) {
112
+ console.error("Error: --server and --token are required");
113
+ process.exit(1);
114
+ }
115
+ const { testStorybook } = await import("./storybook-test.js");
116
+ await testStorybook(resolve(directory), {
117
+ serverUrl: server.replace(/\/$/, ""),
118
+ projectToken: token,
119
+ commitSha: options.commit,
120
+ branch: options.branch,
121
+ prNumber: options.pr ? parseInt(options.pr, 10) : undefined,
122
+ shardIndex: parseInt(options.shardIndex, 10),
123
+ shardTotal: parseInt(options.shardTotal, 10),
124
+ });
125
+ });
126
+ program.parse();
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function initCommand(): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA8BjD"}
package/dist/init.js ADDED
@@ -0,0 +1,29 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ import { readGlobalConfig, writeProjectConfig } from "./config.js";
4
+ export async function initCommand() {
5
+ const globalConfig = await readGlobalConfig();
6
+ const rl = createInterface({ input: stdin, output: stdout });
7
+ try {
8
+ console.log("Initialize Pixeye for this project\n");
9
+ const server = await rl.question(`Server URL [${globalConfig.server ?? "https://app.pixeye.io"}]: `);
10
+ const token = await rl.question(`Project token [${globalConfig.token ? "****" : ""}]: `);
11
+ const screenshotDir = await rl.question("Screenshot directory [./screenshots]: ");
12
+ const config = {
13
+ server: server || globalConfig.server || "https://app.pixeye.io",
14
+ token: token || globalConfig.token || "",
15
+ screenshotDir: screenshotDir || "./screenshots",
16
+ };
17
+ if (!config.token) {
18
+ console.error("\nError: A project token is required. Get one from the Pixeye dashboard.");
19
+ process.exit(1);
20
+ }
21
+ await writeProjectConfig(process.cwd(), config);
22
+ console.log("\nCreated .pixeye.yml");
23
+ console.log("Add it to .gitignore if it contains sensitive tokens.");
24
+ }
25
+ finally {
26
+ rl.close();
27
+ }
28
+ }
29
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC9C,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAE7D,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QAEpD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAC9B,eAAe,YAAY,CAAC,MAAM,IAAI,uBAAuB,KAAK,CACnE,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACzF,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC,CAAC;QAElF,MAAM,MAAM,GAAG;YACb,MAAM,EAAE,MAAM,IAAI,YAAY,CAAC,MAAM,IAAI,uBAAuB;YAChE,KAAK,EAAE,KAAK,IAAI,YAAY,CAAC,KAAK,IAAI,EAAE;YACxC,aAAa,EAAE,aAAa,IAAI,eAAe;SAChD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,0EAA0E,CAAC,CAAC;YAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function loginCommand(options: {
2
+ server?: string;
3
+ token?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAIA,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC9F"}
package/dist/login.js ADDED
@@ -0,0 +1,32 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ import { readGlobalConfig, writeGlobalConfig } from "./config.js";
4
+ export async function loginCommand(options) {
5
+ const globalConfig = await readGlobalConfig();
6
+ const rl = createInterface({ input: stdin, output: stdout });
7
+ try {
8
+ const defaultServer = globalConfig.server ?? "https://app.pixeye.io";
9
+ const input = options.server ?? (await rl.question(`Server URL [${defaultServer}]: `));
10
+ const server = input || defaultServer;
11
+ const token = options.token ?? (await rl.question("API token: "));
12
+ if (!token) {
13
+ console.error("Error: Token is required.");
14
+ process.exit(1);
15
+ }
16
+ // Verify the token works
17
+ const res = await fetch(`${server}/api/v1/projects`, {
18
+ headers: { Authorization: `Bearer ${token}` },
19
+ });
20
+ if (!res.ok) {
21
+ console.error(`Error: Could not authenticate with ${server} (${res.status})`);
22
+ process.exit(1);
23
+ }
24
+ await writeGlobalConfig({ server, token });
25
+ console.log(`Logged in to ${server}`);
26
+ console.log(`Config saved to ~/.pixeye/config.json`);
27
+ }
28
+ finally {
29
+ rl.close();
30
+ }
31
+ }
32
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4C;IAC7E,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC9C,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAE7D,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,IAAI,uBAAuB,CAAC;QACrE,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,aAAa,KAAK,CAAC,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC;QAEtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;QAElE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,yBAAyB;QACzB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,kBAAkB,EAAE;YACnD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACvD,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ interface StorybookTestOptions {
2
+ serverUrl: string;
3
+ projectToken: string;
4
+ commitSha: string;
5
+ branch: string;
6
+ prNumber?: number;
7
+ shardIndex: number;
8
+ shardTotal: number;
9
+ outputDir?: string;
10
+ }
11
+ /**
12
+ * Capture screenshots of all stories in a built Storybook using Playwright.
13
+ * Outputs PNG files to outputDir, then uploads them via the normal Pixeye upload flow.
14
+ */
15
+ export declare function testStorybook(storybookDir: string, opts: StorybookTestOptions): Promise<void>;
16
+ export {};
17
+ //# sourceMappingURL=storybook-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-test.d.ts","sourceRoot":"","sources":["../src/storybook-test.ts"],"names":[],"mappings":"AAKA,UAAU,oBAAoB;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CA2Ef"}
@@ -0,0 +1,125 @@
1
+ import { readFile, mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { createServer } from "node:http";
5
+ /**
6
+ * Start a local HTTP server to serve the static Storybook.
7
+ */
8
+ function startServer(dir, port) {
9
+ return new Promise((resolve) => {
10
+ const server = createServer(async (req, res) => {
11
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
12
+ const filePath = join(dir, url.pathname === "/" ? "index.html" : url.pathname);
13
+ try {
14
+ const data = await readFile(filePath);
15
+ const ext = filePath.split(".").pop() ?? "";
16
+ const types = {
17
+ html: "text/html",
18
+ js: "application/javascript",
19
+ css: "text/css",
20
+ json: "application/json",
21
+ png: "image/png",
22
+ svg: "image/svg+xml",
23
+ };
24
+ res.writeHead(200, { "Content-Type": types[ext] ?? "application/octet-stream" });
25
+ res.end(data);
26
+ }
27
+ catch {
28
+ res.writeHead(404);
29
+ res.end("Not found");
30
+ }
31
+ });
32
+ server.listen(port, () => {
33
+ console.log(`Storybook server started on http://localhost:${port}`);
34
+ resolve(server);
35
+ });
36
+ });
37
+ }
38
+ /**
39
+ * Discover stories from a built Storybook's index.
40
+ */
41
+ async function discoverStories(storybookDir) {
42
+ // Storybook 8 generates index.json or stories.json
43
+ for (const name of ["index.json", "stories.json"]) {
44
+ const path = join(storybookDir, name);
45
+ if (existsSync(path)) {
46
+ const data = JSON.parse(await readFile(path, "utf-8"));
47
+ const entries = data.entries ?? data.stories ?? {};
48
+ return Object.values(entries).filter((s) => s.type === "story");
49
+ }
50
+ }
51
+ throw new Error("No index.json or stories.json found in Storybook build. Ensure Storybook is built correctly.");
52
+ }
53
+ /**
54
+ * Capture screenshots of all stories in a built Storybook using Playwright.
55
+ * Outputs PNG files to outputDir, then uploads them via the normal Pixeye upload flow.
56
+ */
57
+ export async function testStorybook(storybookDir, opts) {
58
+ const outputDir = opts.outputDir ?? join(storybookDir, ".pixeye-screenshots");
59
+ await mkdir(outputDir, { recursive: true });
60
+ // Discover stories
61
+ const stories = await discoverStories(storybookDir);
62
+ console.log(`Found ${stories.length} stories`);
63
+ if (stories.length === 0) {
64
+ console.log("No stories to test.");
65
+ return;
66
+ }
67
+ // Start local server
68
+ const port = 6007;
69
+ const server = await startServer(storybookDir, port);
70
+ try {
71
+ // Dynamically import Playwright (it's a peer dep)
72
+ const { chromium } = await import("playwright");
73
+ const browser = await chromium.launch();
74
+ const context = await browser.newContext({
75
+ viewport: { width: 1280, height: 720 },
76
+ colorScheme: "dark",
77
+ });
78
+ let captured = 0;
79
+ for (const story of stories) {
80
+ const page = await context.newPage();
81
+ const storyUrl = `http://localhost:${port}/iframe.html?id=${encodeURIComponent(story.id)}&viewMode=story`;
82
+ try {
83
+ await page.goto(storyUrl, { waitUntil: "networkidle", timeout: 15000 });
84
+ // Stabilization: freeze animations, wait for render
85
+ await page.evaluate(() => {
86
+ // Pause CSS animations
87
+ const style = document.createElement("style");
88
+ style.textContent = "*, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; }";
89
+ document.head.appendChild(style);
90
+ });
91
+ await page.waitForTimeout(200);
92
+ // Screenshot
93
+ const screenshotPath = join(outputDir, `${story.id}.png`);
94
+ await page.screenshot({ path: screenshotPath, fullPage: false });
95
+ captured++;
96
+ }
97
+ catch (err) {
98
+ console.error(`Failed to capture ${story.id}: ${err.message}`);
99
+ }
100
+ finally {
101
+ await page.close();
102
+ }
103
+ }
104
+ await browser.close();
105
+ console.log(`Captured ${captured}/${stories.length} screenshots to ${outputDir}`);
106
+ }
107
+ finally {
108
+ server.close();
109
+ }
110
+ // Now upload via the normal upload flow
111
+ console.log("Uploading screenshots...");
112
+ const { discoverScreenshots } = await import("./discover.js");
113
+ const { uploadScreenshots } = await import("./upload.js");
114
+ const files = await discoverScreenshots(outputDir);
115
+ await uploadScreenshots(files, {
116
+ serverUrl: opts.serverUrl,
117
+ projectToken: opts.projectToken,
118
+ commitSha: opts.commitSha,
119
+ branch: opts.branch,
120
+ prNumber: opts.prNumber,
121
+ shardIndex: opts.shardIndex,
122
+ shardTotal: opts.shardTotal,
123
+ });
124
+ }
125
+ //# sourceMappingURL=storybook-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-test.js","sourceRoot":"","sources":["../src/storybook-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ interface StorybookUploadOptions {
2
+ serverUrl: string;
3
+ projectToken: string;
4
+ commitSha: string;
5
+ branch: string;
6
+ }
7
+ /**
8
+ * Upload a built static Storybook to the public Storybook bucket on R2.
9
+ */
10
+ export declare function uploadStorybook(directory: string, opts: StorybookUploadOptions): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=storybook-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-upload.d.ts","sourceRoot":"","sources":["../src/storybook-upload.ts"],"names":[],"mappings":"AAGA,UAAU,sBAAsB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AA2CD;;GAEG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqEf"}
@@ -0,0 +1,97 @@
1
+ import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ async function discoverFiles(dir, base = dir) {
4
+ const files = [];
5
+ const entries = await readdir(dir, { withFileTypes: true });
6
+ for (const entry of entries) {
7
+ const fullPath = join(dir, entry.name);
8
+ if (entry.isDirectory()) {
9
+ files.push(...(await discoverFiles(fullPath, base)));
10
+ }
11
+ else {
12
+ const s = await stat(fullPath);
13
+ files.push({ path: fullPath, relativePath: relative(base, fullPath), size: s.size });
14
+ }
15
+ }
16
+ return files;
17
+ }
18
+ function getContentType(filePath) {
19
+ const ext = filePath.split(".").pop() ?? "";
20
+ const types = {
21
+ html: "text/html",
22
+ js: "application/javascript",
23
+ mjs: "application/javascript",
24
+ css: "text/css",
25
+ json: "application/json",
26
+ png: "image/png",
27
+ jpg: "image/jpeg",
28
+ svg: "image/svg+xml",
29
+ woff: "font/woff",
30
+ woff2: "font/woff2",
31
+ ttf: "font/ttf",
32
+ ico: "image/x-icon",
33
+ map: "application/json",
34
+ txt: "text/plain",
35
+ };
36
+ return types[ext] ?? "application/octet-stream";
37
+ }
38
+ /**
39
+ * Upload a built static Storybook to the public Storybook bucket on R2.
40
+ */
41
+ export async function uploadStorybook(directory, opts) {
42
+ const { serverUrl, projectToken, commitSha, branch } = opts;
43
+ console.log(`Discovering files in ${directory}...`);
44
+ const files = await discoverFiles(directory);
45
+ console.log(`Found ${files.length} files`);
46
+ const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
47
+ const safeBranch = branch.replace(/[^a-zA-Z0-9_\-.]/g, "_");
48
+ // The API will resolve orgId/projectId from the token and build the storage prefix
49
+ // For now, we use branch/commit as the prefix — the API adds the org/project prefix
50
+ const storagePrefix = `storybook/${safeBranch}/${commitSha}/`;
51
+ // Upload each file to the Storybook public bucket
52
+ let uploaded = 0;
53
+ for (const file of files) {
54
+ const buffer = await readFile(file.path);
55
+ const key = `${storagePrefix}${file.relativePath}`;
56
+ const contentType = getContentType(file.relativePath);
57
+ const blob = new Blob([buffer], { type: contentType });
58
+ const formData = new FormData();
59
+ formData.append("file", blob, file.relativePath);
60
+ formData.append("key", key);
61
+ formData.append("token", projectToken);
62
+ formData.append("contentType", contentType);
63
+ const uploadRes = await fetch(`${serverUrl}/api/v1/storybook/upload`, {
64
+ method: "POST",
65
+ body: formData,
66
+ });
67
+ if (!uploadRes.ok) {
68
+ console.error(`Failed to upload ${file.relativePath}: ${await uploadRes.text()}`);
69
+ continue;
70
+ }
71
+ uploaded++;
72
+ if (uploaded % 50 === 0)
73
+ console.log(`Uploaded ${uploaded}/${files.length} files...`);
74
+ }
75
+ console.log(`Uploaded ${uploaded} files (${Math.round(totalBytes / 1024)} KB total)`);
76
+ // Register the Storybook build
77
+ const registerRes = await fetch(`${serverUrl}/api/v1/storybook/builds`, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({
81
+ projectToken,
82
+ commitSha,
83
+ branch,
84
+ fileCount: files.length,
85
+ totalBytes,
86
+ storagePrefix,
87
+ }),
88
+ });
89
+ if (!registerRes.ok) {
90
+ console.error(`Failed to register Storybook build: ${await registerRes.text()}`);
91
+ return;
92
+ }
93
+ const { build, storybookUrl } = (await registerRes.json());
94
+ console.log(`Storybook build registered: ${build.id}`);
95
+ console.log(`View at: ${storybookUrl}`);
96
+ }
97
+ //# sourceMappingURL=storybook-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-upload.js","sourceRoot":"","sources":["../src/storybook-upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { DiscoveredFile } from "./discover.js";
2
+ interface UploadOptions {
3
+ serverUrl: string;
4
+ projectToken: string;
5
+ commitSha: string;
6
+ branch: string;
7
+ prNumber?: number;
8
+ shardIndex: number;
9
+ shardTotal: number;
10
+ }
11
+ export declare function uploadScreenshots(files: DiscoveredFile[], opts: UploadOptions): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AA8LD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,cAAc,EAAE,EACvB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CAgFf"}
package/dist/upload.js ADDED
@@ -0,0 +1,211 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { basename, join } from "node:path";
5
+ async function apiRequest(serverUrl, path, options = {}) {
6
+ const url = `${serverUrl}${path}`;
7
+ const response = await fetch(url, {
8
+ ...options,
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ ...options.headers,
12
+ },
13
+ });
14
+ if (!response.ok) {
15
+ const body = await response.text();
16
+ throw new Error(`API ${options.method ?? "GET"} ${path} failed (${response.status}): ${body}`);
17
+ }
18
+ return response.json();
19
+ }
20
+ /**
21
+ * Auto-detect test runner from config files in the project root.
22
+ */
23
+ function detectRunner(cwd) {
24
+ const checks = [
25
+ [["playwright.config.ts", "playwright.config.js", "playwright.config.mjs"], "playwright"],
26
+ [["cypress.config.ts", "cypress.config.js", "cypress.config.mjs", "cypress.json"], "cypress"],
27
+ [[".storybook"], "storybook"],
28
+ ];
29
+ for (const [files, runner] of checks) {
30
+ if (files.some((f) => existsSync(join(cwd, f))))
31
+ return runner;
32
+ }
33
+ return "unknown";
34
+ }
35
+ /**
36
+ * Discover Playwright trace files in test-results directory.
37
+ * Returns map of test dir name → trace file path.
38
+ */
39
+ async function discoverTraces(cwd) {
40
+ const traces = new Map();
41
+ const testResultsDir = join(cwd, "test-results");
42
+ if (!existsSync(testResultsDir))
43
+ return traces;
44
+ try {
45
+ const entries = await readdir(testResultsDir, { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ if (!entry.isDirectory())
48
+ continue;
49
+ const tracePath = join(testResultsDir, entry.name, "trace.zip");
50
+ if (existsSync(tracePath)) {
51
+ traces.set(entry.name, tracePath);
52
+ }
53
+ }
54
+ }
55
+ catch {
56
+ // test-results dir might not exist
57
+ }
58
+ return traces;
59
+ }
60
+ /**
61
+ * Match a screenshot name to a trace dir name.
62
+ * Playwright trace dirs are like "auth-login-page-renders-chromium" and
63
+ * screenshot names might be "auth-login-page" or similar.
64
+ */
65
+ function matchTraceToScreenshot(screenshotName, traceDirs) {
66
+ // Normalize: lowercase, remove extensions
67
+ const normalized = screenshotName.toLowerCase().replace(/\.(png|jpg)$/, "");
68
+ // Exact match first
69
+ const exact = traceDirs.find((d) => d.toLowerCase() === normalized);
70
+ if (exact)
71
+ return exact;
72
+ // Prefix match: screenshot name is a prefix of trace dir
73
+ const prefix = traceDirs.find((d) => d.toLowerCase().startsWith(normalized));
74
+ if (prefix)
75
+ return prefix;
76
+ // Substring match: screenshot name contained in trace dir
77
+ return traceDirs.find((d) => d.toLowerCase().includes(normalized));
78
+ }
79
+ async function createBuild(serverUrl, opts, runner) {
80
+ const body = {
81
+ projectToken: opts.projectToken,
82
+ commitSha: opts.commitSha,
83
+ branch: opts.branch,
84
+ prNumber: opts.prNumber,
85
+ shardCount: opts.shardTotal,
86
+ metadata: { runner },
87
+ };
88
+ console.log(`Creating build (runner: ${runner})...`);
89
+ const result = await apiRequest(serverUrl, "/api/v1/builds", {
90
+ method: "POST",
91
+ body: JSON.stringify(body),
92
+ });
93
+ console.log(`Build created: ${result.build.id}`);
94
+ return result.build;
95
+ }
96
+ async function checkFileExists(serverUrl, hash, token) {
97
+ const result = await apiRequest(serverUrl, `/api/v1/upload/${hash}?token=${encodeURIComponent(token)}`);
98
+ return result.exists;
99
+ }
100
+ async function uploadFile(serverUrl, filePath, hash, token) {
101
+ const fileBuffer = await readFile(filePath);
102
+ const blob = new Blob([fileBuffer], { type: "image/png" });
103
+ const formData = new FormData();
104
+ formData.append("file", blob, basename(filePath));
105
+ formData.append("hash", hash);
106
+ formData.append("token", token);
107
+ const url = `${serverUrl}/api/v1/upload`;
108
+ const response = await fetch(url, { method: "POST", body: formData });
109
+ if (!response.ok) {
110
+ const body = await response.text();
111
+ throw new Error(`Upload failed for ${filePath} (${response.status}): ${body}`);
112
+ }
113
+ }
114
+ /**
115
+ * Upload a trace.zip file. Uses the same upload endpoint with a special
116
+ * "trace:" prefix on the hash to distinguish from screenshots.
117
+ */
118
+ async function uploadTraceFile(serverUrl, filePath, token) {
119
+ const fileBuffer = await readFile(filePath);
120
+ const hash = "trace_" + createHash("sha256").update(fileBuffer).digest("hex");
121
+ // Check if already uploaded
122
+ const existsRes = await apiRequest(serverUrl, `/api/v1/upload/${hash}?token=${encodeURIComponent(token)}`);
123
+ if (existsRes.exists)
124
+ return hash;
125
+ const blob = new Blob([fileBuffer], { type: "application/zip" });
126
+ const formData = new FormData();
127
+ formData.append("file", blob, basename(filePath));
128
+ formData.append("hash", hash);
129
+ formData.append("token", token);
130
+ const url = `${serverUrl}/api/v1/upload`;
131
+ const response = await fetch(url, { method: "POST", body: formData });
132
+ if (!response.ok) {
133
+ const body = await response.text();
134
+ throw new Error(`Trace upload failed (${response.status}): ${body}`);
135
+ }
136
+ return hash;
137
+ }
138
+ async function submitShard(serverUrl, buildId, shardIndex, shardTotal, screenshots) {
139
+ await apiRequest(serverUrl, `/api/v1/builds/${buildId}/shards`, {
140
+ method: "POST",
141
+ body: JSON.stringify({ shardIndex, screenshots }),
142
+ });
143
+ }
144
+ export async function uploadScreenshots(files, opts) {
145
+ const { serverUrl } = opts;
146
+ // Detect test runner
147
+ const runner = detectRunner(process.cwd());
148
+ // Discover trace files
149
+ const traces = await discoverTraces(process.cwd());
150
+ if (traces.size > 0) {
151
+ console.log(`Found ${traces.size} trace files`);
152
+ }
153
+ // Step 1: Create or retrieve build
154
+ const build = await createBuild(serverUrl, opts, runner);
155
+ // Step 2: Upload screenshots (with dedup)
156
+ console.log(`Checking ${files.length} files for deduplication...`);
157
+ let uploadedCount = 0;
158
+ let skippedCount = 0;
159
+ for (const file of files) {
160
+ const exists = await checkFileExists(serverUrl, file.fileHash, opts.projectToken);
161
+ if (exists) {
162
+ skippedCount++;
163
+ continue;
164
+ }
165
+ await uploadFile(serverUrl, file.filePath, file.fileHash, opts.projectToken);
166
+ uploadedCount++;
167
+ console.log(`Uploaded (${uploadedCount}): ${file.name}`);
168
+ }
169
+ console.log(`Upload complete: ${uploadedCount} uploaded, ${skippedCount} skipped (already exist).`);
170
+ // Step 3: Upload trace files and build trace map
171
+ const traceMap = new Map(); // screenshot name → trace storage key
172
+ if (traces.size > 0) {
173
+ const traceDirs = [...traces.keys()];
174
+ let traceUploaded = 0;
175
+ for (const file of files) {
176
+ const matchedDir = matchTraceToScreenshot(file.name, traceDirs);
177
+ if (!matchedDir)
178
+ continue;
179
+ const tracePath = traces.get(matchedDir);
180
+ try {
181
+ const traceHash = await uploadTraceFile(serverUrl, tracePath, opts.projectToken);
182
+ // The trace will be stored at the same path pattern as screenshots but with trace hash
183
+ traceMap.set(file.name, traceHash);
184
+ traceUploaded++;
185
+ }
186
+ catch (err) {
187
+ console.error(`Failed to upload trace for ${file.name}: ${err.message}`);
188
+ }
189
+ }
190
+ if (traceUploaded > 0) {
191
+ console.log(`Uploaded ${traceUploaded} trace files`);
192
+ }
193
+ }
194
+ // Step 4: Submit shard manifest (with trace metadata)
195
+ const manifest = files.map((f) => {
196
+ const traceHash = traceMap.get(f.name);
197
+ return {
198
+ name: f.name,
199
+ fileHash: f.fileHash,
200
+ width: f.width,
201
+ height: f.height,
202
+ metadata: {
203
+ ...f.metadata,
204
+ ...(traceHash ? { traceHash } : {}),
205
+ },
206
+ };
207
+ });
208
+ await submitShard(serverUrl, build.id, opts.shardIndex, opts.shardTotal, manifest);
209
+ console.log("Done.");
210
+ }
211
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@pixeyedev/cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "bin": {
10
+ "pixeye": "dist/index.js"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "typecheck": "tsc --noEmit",
15
+ "lint": "echo 'ok'",
16
+ "publish:dry": "npm run build && npm publish --access public --dry-run",
17
+ "publish:real": "npm run build && npm publish --access public"
18
+ },
19
+ "dependencies": {
20
+ "commander": "^13.0.0",
21
+ "glob": "^11.0.0",
22
+ "@pixeyedev/shared": "^0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.7.0"
27
+ }
28
+ }