@browserstack/mcp-server 1.2.15-beta.2 → 1.2.16-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server-factory.js +0 -4
- package/dist/tools/percy-sdk.js +20 -11
- package/dist/tools/testmanagement-utils/get-testplan.d.ts +16 -0
- package/dist/tools/testmanagement-utils/get-testplan.js +99 -0
- package/dist/tools/testmanagement-utils/list-folders.d.ts +16 -0
- package/dist/tools/testmanagement-utils/list-folders.js +77 -0
- package/dist/tools/testmanagement-utils/list-testcases.js +1 -1
- package/dist/tools/testmanagement-utils/list-testplans.d.ts +15 -0
- package/dist/tools/testmanagement-utils/list-testplans.js +75 -0
- package/dist/tools/testmanagement-utils/update-testcase.d.ts +16 -0
- package/dist/tools/testmanagement-utils/update-testcase.js +133 -10
- package/dist/tools/testmanagement.d.ts +15 -0
- package/dist/tools/testmanagement.js +73 -2
- package/package.json +2 -3
- package/dist/lib/percy-api/auth.d.ts +0 -41
- package/dist/lib/percy-api/auth.js +0 -96
- package/dist/lib/percy-api/cache.d.ts +0 -28
- package/dist/lib/percy-api/cache.js +0 -48
- package/dist/lib/percy-api/client.d.ts +0 -69
- package/dist/lib/percy-api/client.js +0 -275
- package/dist/lib/percy-api/errors.d.ts +0 -15
- package/dist/lib/percy-api/errors.js +0 -52
- package/dist/lib/percy-api/formatter.d.ts +0 -16
- package/dist/lib/percy-api/formatter.js +0 -344
- package/dist/lib/percy-api/percy-auth.d.ts +0 -43
- package/dist/lib/percy-api/percy-auth.js +0 -137
- package/dist/lib/percy-api/percy-error-handler.d.ts +0 -24
- package/dist/lib/percy-api/percy-error-handler.js +0 -302
- package/dist/lib/percy-api/percy-session.d.ts +0 -42
- package/dist/lib/percy-api/percy-session.js +0 -87
- package/dist/lib/percy-api/polling.d.ts +0 -26
- package/dist/lib/percy-api/polling.js +0 -42
- package/dist/lib/percy-api/types.d.ts +0 -56
- package/dist/lib/percy-api/types.js +0 -76
- package/dist/tools/percy-mcp/advanced/branchline-operations.d.ts +0 -16
- package/dist/tools/percy-mcp/advanced/branchline-operations.js +0 -81
- package/dist/tools/percy-mcp/advanced/manage-variants.d.ts +0 -16
- package/dist/tools/percy-mcp/advanced/manage-variants.js +0 -155
- package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.d.ts +0 -16
- package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.js +0 -171
- package/dist/tools/percy-mcp/auth/auth-status.d.ts +0 -3
- package/dist/tools/percy-mcp/auth/auth-status.js +0 -131
- package/dist/tools/percy-mcp/core/approve-build.d.ts +0 -14
- package/dist/tools/percy-mcp/core/approve-build.js +0 -97
- package/dist/tools/percy-mcp/core/get-build-items.d.ts +0 -13
- package/dist/tools/percy-mcp/core/get-build-items.js +0 -65
- package/dist/tools/percy-mcp/core/get-build.d.ts +0 -10
- package/dist/tools/percy-mcp/core/get-build.js +0 -16
- package/dist/tools/percy-mcp/core/get-comparison.d.ts +0 -11
- package/dist/tools/percy-mcp/core/get-comparison.js +0 -59
- package/dist/tools/percy-mcp/core/get-snapshot.d.ts +0 -10
- package/dist/tools/percy-mcp/core/get-snapshot.js +0 -40
- package/dist/tools/percy-mcp/core/list-builds.d.ts +0 -14
- package/dist/tools/percy-mcp/core/list-builds.js +0 -45
- package/dist/tools/percy-mcp/core/list-projects.d.ts +0 -12
- package/dist/tools/percy-mcp/core/list-projects.js +0 -51
- package/dist/tools/percy-mcp/creation/create-app-snapshot.d.ts +0 -12
- package/dist/tools/percy-mcp/creation/create-app-snapshot.js +0 -29
- package/dist/tools/percy-mcp/creation/create-build.d.ts +0 -19
- package/dist/tools/percy-mcp/creation/create-build.js +0 -68
- package/dist/tools/percy-mcp/creation/create-comparison.d.ts +0 -18
- package/dist/tools/percy-mcp/creation/create-comparison.js +0 -90
- package/dist/tools/percy-mcp/creation/create-snapshot.d.ts +0 -17
- package/dist/tools/percy-mcp/creation/create-snapshot.js +0 -99
- package/dist/tools/percy-mcp/creation/finalize-build.d.ts +0 -12
- package/dist/tools/percy-mcp/creation/finalize-build.js +0 -33
- package/dist/tools/percy-mcp/creation/finalize-comparison.d.ts +0 -10
- package/dist/tools/percy-mcp/creation/finalize-comparison.js +0 -16
- package/dist/tools/percy-mcp/creation/finalize-snapshot.d.ts +0 -12
- package/dist/tools/percy-mcp/creation/finalize-snapshot.js +0 -33
- package/dist/tools/percy-mcp/creation/upload-resource.d.ts +0 -15
- package/dist/tools/percy-mcp/creation/upload-resource.js +0 -43
- package/dist/tools/percy-mcp/creation/upload-tile.d.ts +0 -11
- package/dist/tools/percy-mcp/creation/upload-tile.js +0 -53
- package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.d.ts +0 -13
- package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.js +0 -65
- package/dist/tools/percy-mcp/diagnostics/get-build-logs.d.ts +0 -17
- package/dist/tools/percy-mcp/diagnostics/get-build-logs.js +0 -74
- package/dist/tools/percy-mcp/diagnostics/get-network-logs.d.ts +0 -5
- package/dist/tools/percy-mcp/diagnostics/get-network-logs.js +0 -21
- package/dist/tools/percy-mcp/diagnostics/get-suggestions.d.ts +0 -7
- package/dist/tools/percy-mcp/diagnostics/get-suggestions.js +0 -24
- package/dist/tools/percy-mcp/index.d.ts +0 -36
- package/dist/tools/percy-mcp/index.js +0 -1137
- package/dist/tools/percy-mcp/intelligence/get-ai-analysis.d.ts +0 -15
- package/dist/tools/percy-mcp/intelligence/get-ai-analysis.js +0 -166
- package/dist/tools/percy-mcp/intelligence/get-ai-quota.d.ts +0 -9
- package/dist/tools/percy-mcp/intelligence/get-ai-quota.js +0 -73
- package/dist/tools/percy-mcp/intelligence/get-build-summary.d.ts +0 -11
- package/dist/tools/percy-mcp/intelligence/get-build-summary.js +0 -78
- package/dist/tools/percy-mcp/intelligence/get-rca.d.ts +0 -6
- package/dist/tools/percy-mcp/intelligence/get-rca.js +0 -153
- package/dist/tools/percy-mcp/intelligence/suggest-prompt.d.ts +0 -15
- package/dist/tools/percy-mcp/intelligence/suggest-prompt.js +0 -86
- package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.d.ts +0 -16
- package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.js +0 -64
- package/dist/tools/percy-mcp/management/create-project.d.ts +0 -14
- package/dist/tools/percy-mcp/management/create-project.js +0 -52
- package/dist/tools/percy-mcp/management/get-usage-stats.d.ts +0 -12
- package/dist/tools/percy-mcp/management/get-usage-stats.js +0 -61
- package/dist/tools/percy-mcp/management/manage-browser-targets.d.ts +0 -12
- package/dist/tools/percy-mcp/management/manage-browser-targets.js +0 -136
- package/dist/tools/percy-mcp/management/manage-comments.d.ts +0 -14
- package/dist/tools/percy-mcp/management/manage-comments.js +0 -147
- package/dist/tools/percy-mcp/management/manage-ignored-regions.d.ts +0 -18
- package/dist/tools/percy-mcp/management/manage-ignored-regions.js +0 -182
- package/dist/tools/percy-mcp/management/manage-project-settings.d.ts +0 -16
- package/dist/tools/percy-mcp/management/manage-project-settings.js +0 -97
- package/dist/tools/percy-mcp/management/manage-tokens.d.ts +0 -14
- package/dist/tools/percy-mcp/management/manage-tokens.js +0 -90
- package/dist/tools/percy-mcp/management/manage-webhooks.d.ts +0 -15
- package/dist/tools/percy-mcp/management/manage-webhooks.js +0 -180
- package/dist/tools/percy-mcp/v2/auth-status.d.ts +0 -3
- package/dist/tools/percy-mcp/v2/auth-status.js +0 -80
- package/dist/tools/percy-mcp/v2/clone-build.d.ts +0 -24
- package/dist/tools/percy-mcp/v2/clone-build.js +0 -539
- package/dist/tools/percy-mcp/v2/create-app-build.d.ts +0 -28
- package/dist/tools/percy-mcp/v2/create-app-build.js +0 -442
- package/dist/tools/percy-mcp/v2/create-build.d.ts +0 -16
- package/dist/tools/percy-mcp/v2/create-build.js +0 -601
- package/dist/tools/percy-mcp/v2/create-project.d.ts +0 -8
- package/dist/tools/percy-mcp/v2/create-project.js +0 -33
- package/dist/tools/percy-mcp/v2/discover-urls.d.ts +0 -7
- package/dist/tools/percy-mcp/v2/discover-urls.js +0 -38
- package/dist/tools/percy-mcp/v2/figma-baseline.d.ts +0 -7
- package/dist/tools/percy-mcp/v2/figma-baseline.js +0 -18
- package/dist/tools/percy-mcp/v2/figma-build.d.ts +0 -7
- package/dist/tools/percy-mcp/v2/figma-build.js +0 -39
- package/dist/tools/percy-mcp/v2/figma-link.d.ts +0 -6
- package/dist/tools/percy-mcp/v2/figma-link.js +0 -27
- package/dist/tools/percy-mcp/v2/get-ai-summary.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/get-ai-summary.js +0 -109
- package/dist/tools/percy-mcp/v2/get-build-detail.d.ts +0 -22
- package/dist/tools/percy-mcp/v2/get-build-detail.js +0 -567
- package/dist/tools/percy-mcp/v2/get-builds.d.ts +0 -8
- package/dist/tools/percy-mcp/v2/get-builds.js +0 -63
- package/dist/tools/percy-mcp/v2/get-comparison.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/get-comparison.js +0 -94
- package/dist/tools/percy-mcp/v2/get-devices.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/get-devices.js +0 -33
- package/dist/tools/percy-mcp/v2/get-insights.d.ts +0 -7
- package/dist/tools/percy-mcp/v2/get-insights.js +0 -52
- package/dist/tools/percy-mcp/v2/get-projects.d.ts +0 -6
- package/dist/tools/percy-mcp/v2/get-projects.js +0 -41
- package/dist/tools/percy-mcp/v2/get-snapshot.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/get-snapshot.js +0 -96
- package/dist/tools/percy-mcp/v2/get-test-case-history.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/get-test-case-history.js +0 -20
- package/dist/tools/percy-mcp/v2/get-test-cases.d.ts +0 -6
- package/dist/tools/percy-mcp/v2/get-test-cases.js +0 -36
- package/dist/tools/percy-mcp/v2/index.d.ts +0 -35
- package/dist/tools/percy-mcp/v2/index.js +0 -544
- package/dist/tools/percy-mcp/v2/list-integrations.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/list-integrations.js +0 -41
- package/dist/tools/percy-mcp/v2/manage-domains.d.ts +0 -8
- package/dist/tools/percy-mcp/v2/manage-domains.js +0 -33
- package/dist/tools/percy-mcp/v2/manage-insights-email.d.ts +0 -8
- package/dist/tools/percy-mcp/v2/manage-insights-email.js +0 -49
- package/dist/tools/percy-mcp/v2/manage-usage-alerts.d.ts +0 -10
- package/dist/tools/percy-mcp/v2/manage-usage-alerts.js +0 -43
- package/dist/tools/percy-mcp/v2/migrate-integrations.d.ts +0 -6
- package/dist/tools/percy-mcp/v2/migrate-integrations.js +0 -20
- package/dist/tools/percy-mcp/v2/preview-comparison.d.ts +0 -5
- package/dist/tools/percy-mcp/v2/preview-comparison.js +0 -17
- package/dist/tools/percy-mcp/v2/search-build-items.d.ts +0 -12
- package/dist/tools/percy-mcp/v2/search-build-items.js +0 -45
- package/dist/tools/percy-mcp/workflows/auto-triage.d.ts +0 -7
- package/dist/tools/percy-mcp/workflows/auto-triage.js +0 -82
- package/dist/tools/percy-mcp/workflows/clone-build.d.ts +0 -22
- package/dist/tools/percy-mcp/workflows/clone-build.js +0 -414
- package/dist/tools/percy-mcp/workflows/create-percy-build.d.ts +0 -32
- package/dist/tools/percy-mcp/workflows/create-percy-build.js +0 -434
- package/dist/tools/percy-mcp/workflows/debug-failed-build.d.ts +0 -5
- package/dist/tools/percy-mcp/workflows/debug-failed-build.js +0 -122
- package/dist/tools/percy-mcp/workflows/diff-explain.d.ts +0 -6
- package/dist/tools/percy-mcp/workflows/diff-explain.js +0 -147
- package/dist/tools/percy-mcp/workflows/pr-visual-report.d.ts +0 -8
- package/dist/tools/percy-mcp/workflows/pr-visual-report.js +0 -184
- package/dist/tools/percy-mcp/workflows/run-tests.d.ts +0 -17
- package/dist/tools/percy-mcp/workflows/run-tests.js +0 -107
- package/dist/tools/percy-mcp/workflows/snapshot-urls.d.ts +0 -18
- package/dist/tools/percy-mcp/workflows/snapshot-urls.js +0 -197
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* percy_create_app_build — Create an App Percy BYOS (Bring Your Own Screenshots) build.
|
|
3
|
-
*
|
|
4
|
-
* Two modes:
|
|
5
|
-
* 1. Sample mode (use_sample_data=true): auto-generates 3 devices × 2 screenshots
|
|
6
|
-
* using sharp. Zero setup — just provide a project name.
|
|
7
|
-
* 2. Custom mode (resources_dir): reads your own device folders with device.json + PNGs.
|
|
8
|
-
*
|
|
9
|
-
* Expected directory structure for custom mode:
|
|
10
|
-
* resources/
|
|
11
|
-
* iPhone_14_Pro/
|
|
12
|
-
* device.json ← { deviceName, osName, osVersion, orientation, deviceScreenSize }
|
|
13
|
-
* Home.png
|
|
14
|
-
* Settings.png
|
|
15
|
-
* Pixel_7/
|
|
16
|
-
* device.json
|
|
17
|
-
* Home.png
|
|
18
|
-
*/
|
|
19
|
-
import { percyTokenPost, getOrCreateProjectToken, } from "../../../lib/percy-api/percy-auth.js";
|
|
20
|
-
import { setActiveProject, setActiveBuild, } from "../../../lib/percy-api/percy-session.js";
|
|
21
|
-
import { execFile } from "child_process";
|
|
22
|
-
import { promisify } from "util";
|
|
23
|
-
import { readdir, readFile, stat, writeFile, mkdir } from "fs/promises";
|
|
24
|
-
import { join, basename, extname } from "path";
|
|
25
|
-
import { tmpdir } from "os";
|
|
26
|
-
import { createHash } from "crypto";
|
|
27
|
-
import sharp from "sharp";
|
|
28
|
-
const execFileAsync = promisify(execFile);
|
|
29
|
-
// ── Built-in sample devices ─────────────────────────────────────────────────
|
|
30
|
-
const SAMPLE_DEVICES = [
|
|
31
|
-
{
|
|
32
|
-
folder: "iPhone_14_Pro",
|
|
33
|
-
config: {
|
|
34
|
-
deviceName: "iPhone 14 Pro",
|
|
35
|
-
osName: "iOS",
|
|
36
|
-
osVersion: "16",
|
|
37
|
-
orientation: "portrait",
|
|
38
|
-
deviceScreenSize: "1179x2556",
|
|
39
|
-
statusBarHeight: 132,
|
|
40
|
-
navBarHeight: 0,
|
|
41
|
-
},
|
|
42
|
-
screenshots: ["Home Screen", "Login Screen"],
|
|
43
|
-
background: { r: 230, g: 230, b: 250 }, // light lavender
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
folder: "Pixel_7",
|
|
47
|
-
config: {
|
|
48
|
-
deviceName: "Pixel 7",
|
|
49
|
-
osName: "Android",
|
|
50
|
-
osVersion: "13",
|
|
51
|
-
orientation: "portrait",
|
|
52
|
-
deviceScreenSize: "1080x2400",
|
|
53
|
-
statusBarHeight: 118,
|
|
54
|
-
navBarHeight: 63,
|
|
55
|
-
},
|
|
56
|
-
screenshots: ["Home Screen", "Login Screen"],
|
|
57
|
-
background: { r: 230, g: 250, b: 230 }, // light green
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
folder: "Samsung_Galaxy_S23",
|
|
61
|
-
config: {
|
|
62
|
-
deviceName: "Samsung Galaxy S23",
|
|
63
|
-
osName: "Android",
|
|
64
|
-
osVersion: "13",
|
|
65
|
-
orientation: "portrait",
|
|
66
|
-
deviceScreenSize: "1080x2340",
|
|
67
|
-
statusBarHeight: 110,
|
|
68
|
-
navBarHeight: 63,
|
|
69
|
-
},
|
|
70
|
-
screenshots: ["Home Screen", "Login Screen"],
|
|
71
|
-
background: { r: 250, g: 240, b: 230 }, // light peach
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
75
|
-
async function getGitBranch() {
|
|
76
|
-
try {
|
|
77
|
-
return ((await execFileAsync("git", ["branch", "--show-current"])).stdout.trim() || "main");
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return "main";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function getGitSha() {
|
|
84
|
-
try {
|
|
85
|
-
return (await execFileAsync("git", ["rev-parse", "HEAD"])).stdout.trim();
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return createHash("sha1").update(Date.now().toString()).digest("hex");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function parseDimensions(sizeStr) {
|
|
92
|
-
const match = sizeStr.match(/^(\d+)\s*[xX×]\s*(\d+)$/);
|
|
93
|
-
if (!match)
|
|
94
|
-
return null;
|
|
95
|
-
return [parseInt(match[1], 10), parseInt(match[2], 10)];
|
|
96
|
-
}
|
|
97
|
-
function readPngDimensions(buffer) {
|
|
98
|
-
if (buffer.length >= 24 &&
|
|
99
|
-
buffer[0] === 0x89 &&
|
|
100
|
-
buffer[1] === 0x50 &&
|
|
101
|
-
buffer[2] === 0x4e &&
|
|
102
|
-
buffer[3] === 0x47) {
|
|
103
|
-
return {
|
|
104
|
-
width: buffer.readUInt32BE(16),
|
|
105
|
-
height: buffer.readUInt32BE(20),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
// ── Sample data generation ──────────────────────────────────────────────────
|
|
111
|
-
async function generateSampleResources() {
|
|
112
|
-
const ts = Date.now();
|
|
113
|
-
const tmpDir = join(tmpdir(), `percy-app-samples-${ts}`);
|
|
114
|
-
await mkdir(tmpDir, { recursive: true });
|
|
115
|
-
for (const device of SAMPLE_DEVICES) {
|
|
116
|
-
const deviceDir = join(tmpDir, device.folder);
|
|
117
|
-
await mkdir(deviceDir, { recursive: true });
|
|
118
|
-
// Write device.json
|
|
119
|
-
await writeFile(join(deviceDir, "device.json"), JSON.stringify(device.config, null, 2));
|
|
120
|
-
// Generate PNGs at correct dimensions
|
|
121
|
-
const dims = parseDimensions(device.config.deviceScreenSize);
|
|
122
|
-
const [width, height] = dims;
|
|
123
|
-
for (const name of device.screenshots) {
|
|
124
|
-
await sharp({
|
|
125
|
-
create: {
|
|
126
|
-
width,
|
|
127
|
-
height,
|
|
128
|
-
channels: 3,
|
|
129
|
-
background: device.background,
|
|
130
|
-
},
|
|
131
|
-
})
|
|
132
|
-
.png({ compressionLevel: 9 })
|
|
133
|
-
.toFile(join(deviceDir, `${name}.png`));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return tmpDir;
|
|
137
|
-
}
|
|
138
|
-
// ── Discovery: find device folders ──────────────────────────────────────────
|
|
139
|
-
async function discoverDevices(resourcesDir) {
|
|
140
|
-
const devices = [];
|
|
141
|
-
const errors = [];
|
|
142
|
-
let entries;
|
|
143
|
-
try {
|
|
144
|
-
entries = await readdir(resourcesDir);
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
return {
|
|
148
|
-
devices: [],
|
|
149
|
-
errors: [`Cannot read "${resourcesDir}": ${e.message}`],
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
for (const entry of entries) {
|
|
153
|
-
const folderPath = join(resourcesDir, entry);
|
|
154
|
-
const folderStat = await stat(folderPath).catch(() => null);
|
|
155
|
-
if (!folderStat?.isDirectory())
|
|
156
|
-
continue;
|
|
157
|
-
// Must have device.json
|
|
158
|
-
const configPath = join(folderPath, "device.json");
|
|
159
|
-
const configExists = await stat(configPath).catch(() => null);
|
|
160
|
-
if (!configExists)
|
|
161
|
-
continue;
|
|
162
|
-
// Parse device.json
|
|
163
|
-
let deviceConfig;
|
|
164
|
-
try {
|
|
165
|
-
const raw = await readFile(configPath, "utf-8");
|
|
166
|
-
deviceConfig = JSON.parse(raw);
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
errors.push(`${entry}: invalid device.json — ${e.message}`);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
// Validate required fields
|
|
173
|
-
if (!deviceConfig.deviceName) {
|
|
174
|
-
errors.push(`${entry}: device.json missing "deviceName"`);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (!deviceConfig.osName) {
|
|
178
|
-
errors.push(`${entry}: device.json missing "osName"`);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
if (!deviceConfig.deviceScreenSize) {
|
|
182
|
-
errors.push(`${entry}: device.json missing "deviceScreenSize"`);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
const dims = parseDimensions(deviceConfig.deviceScreenSize);
|
|
186
|
-
if (!dims) {
|
|
187
|
-
errors.push(`${entry}: invalid deviceScreenSize "${deviceConfig.deviceScreenSize}" — expected "WIDTHxHEIGHT"`);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
// Find .png screenshots
|
|
191
|
-
const allFiles = await readdir(folderPath);
|
|
192
|
-
const screenshots = allFiles
|
|
193
|
-
.filter((f) => /\.png$/i.test(f))
|
|
194
|
-
.map((f) => join(folderPath, f));
|
|
195
|
-
if (screenshots.length === 0) {
|
|
196
|
-
errors.push(`${entry}: no .png files found`);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
devices.push({
|
|
200
|
-
folder: entry,
|
|
201
|
-
config: deviceConfig,
|
|
202
|
-
screenshots,
|
|
203
|
-
width: dims[0],
|
|
204
|
-
height: dims[1],
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
return { devices, errors };
|
|
208
|
-
}
|
|
209
|
-
// ── Main handler ────────────────────────────────────────────────────────────
|
|
210
|
-
export async function percyCreateAppBuildV2(args, config) {
|
|
211
|
-
const branch = args.branch || (await getGitBranch());
|
|
212
|
-
const commitSha = await getGitSha();
|
|
213
|
-
const usingSamples = args.use_sample_data === true || !args.resources_dir;
|
|
214
|
-
// ── 1. Resolve resources directory ────────────────────────────────────────
|
|
215
|
-
let resourcesDir;
|
|
216
|
-
if (usingSamples) {
|
|
217
|
-
try {
|
|
218
|
-
resourcesDir = await generateSampleResources();
|
|
219
|
-
}
|
|
220
|
-
catch (e) {
|
|
221
|
-
return {
|
|
222
|
-
content: [
|
|
223
|
-
{
|
|
224
|
-
type: "text",
|
|
225
|
-
text: `Failed to generate sample data: ${e.message}\n\nMake sure \`sharp\` is installed: \`npm install sharp\``,
|
|
226
|
-
},
|
|
227
|
-
],
|
|
228
|
-
isError: true,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
resourcesDir = args.resources_dir;
|
|
234
|
-
}
|
|
235
|
-
// ── 2. Get app project token ──────────────────────────────────────────────
|
|
236
|
-
let token;
|
|
237
|
-
try {
|
|
238
|
-
token = await getOrCreateProjectToken(args.project_name, config, "app");
|
|
239
|
-
setActiveProject({ name: args.project_name, token, type: "app" });
|
|
240
|
-
}
|
|
241
|
-
catch (e) {
|
|
242
|
-
return {
|
|
243
|
-
content: [
|
|
244
|
-
{
|
|
245
|
-
type: "text",
|
|
246
|
-
text: `Failed to access app project "${args.project_name}": ${e.message}\n\nMake sure the project exists or your BrowserStack credentials have permission to create app Percy projects.`,
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
isError: true,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
// ── 3. Discover devices & screenshots ─────────────────────────────────────
|
|
253
|
-
const { devices, errors: discoveryErrors } = await discoverDevices(resourcesDir);
|
|
254
|
-
if (devices.length === 0) {
|
|
255
|
-
let output = `## App Percy Build — No Valid Devices\n\n`;
|
|
256
|
-
output += `No device folders with valid device.json found in \`${resourcesDir}\`.\n\n`;
|
|
257
|
-
if (discoveryErrors.length > 0) {
|
|
258
|
-
output += `**Errors:**\n`;
|
|
259
|
-
for (const err of discoveryErrors) {
|
|
260
|
-
output += `- ${err}\n`;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
output += `\n**Expected structure:**\n`;
|
|
264
|
-
output += `\`\`\`\nresources/\n iPhone_14_Pro/\n device.json\n Home.png\n Pixel_7/\n device.json\n Home.png\n\`\`\`\n`;
|
|
265
|
-
output += `\n**device.json format:**\n`;
|
|
266
|
-
output += `\`\`\`json\n{\n "deviceName": "iPhone 14 Pro",\n "osName": "iOS",\n "osVersion": "16",\n "orientation": "portrait",\n "deviceScreenSize": "1290x2796"\n}\n\`\`\`\n`;
|
|
267
|
-
return { content: [{ type: "text", text: output }], isError: true };
|
|
268
|
-
}
|
|
269
|
-
const totalScreenshots = devices.reduce((sum, d) => sum + d.screenshots.length, 0);
|
|
270
|
-
// ── 4. Create build ───────────────────────────────────────────────────────
|
|
271
|
-
let buildId;
|
|
272
|
-
let buildUrl;
|
|
273
|
-
try {
|
|
274
|
-
const buildResponse = await percyTokenPost("/builds", token, {
|
|
275
|
-
data: {
|
|
276
|
-
type: "builds",
|
|
277
|
-
attributes: { branch, "commit-sha": commitSha },
|
|
278
|
-
relationships: { resources: { data: [] } },
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
buildId = buildResponse?.data?.id;
|
|
282
|
-
buildUrl = buildResponse?.data?.attributes?.["web-url"] || "";
|
|
283
|
-
// Store in session
|
|
284
|
-
if (buildId) {
|
|
285
|
-
setActiveBuild({ id: buildId, url: buildUrl, branch });
|
|
286
|
-
}
|
|
287
|
-
if (!buildId) {
|
|
288
|
-
return {
|
|
289
|
-
content: [
|
|
290
|
-
{
|
|
291
|
-
type: "text",
|
|
292
|
-
text: "Failed to create app build — no build ID returned.",
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
isError: true,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch (e) {
|
|
300
|
-
return {
|
|
301
|
-
content: [
|
|
302
|
-
{ type: "text", text: `Failed to create app build: ${e.message}` },
|
|
303
|
-
],
|
|
304
|
-
isError: true,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
// ── 5. Upload screenshots per device ──────────────────────────────────────
|
|
308
|
-
let output = `## App Percy Build — ${args.project_name}\n\n`;
|
|
309
|
-
if (usingSamples) {
|
|
310
|
-
output += `> Using built-in sample data (3 devices × 2 screenshots). Pass \`resources_dir\` for custom screenshots.\n\n`;
|
|
311
|
-
}
|
|
312
|
-
output += `| Field | Value |\n|---|---|\n`;
|
|
313
|
-
output += `| **Build ID** | ${buildId} |\n`;
|
|
314
|
-
output += `| **Project** | ${args.project_name} |\n`;
|
|
315
|
-
output += `| **Branch** | ${branch} |\n`;
|
|
316
|
-
output += `| **Devices** | ${devices.length} |\n`;
|
|
317
|
-
output += `| **Screenshots** | ${totalScreenshots} |\n`;
|
|
318
|
-
output += `| **Token** | \`${token.slice(0, 8)}...${token.slice(-4)}\` |\n`;
|
|
319
|
-
if (buildUrl)
|
|
320
|
-
output += `| **Build URL** | ${buildUrl} |\n`;
|
|
321
|
-
output += "\n";
|
|
322
|
-
if (discoveryErrors.length > 0) {
|
|
323
|
-
output += `**Skipped (validation errors):**\n`;
|
|
324
|
-
for (const err of discoveryErrors) {
|
|
325
|
-
output += `- ${err}\n`;
|
|
326
|
-
}
|
|
327
|
-
output += `\n`;
|
|
328
|
-
}
|
|
329
|
-
let uploaded = 0;
|
|
330
|
-
let failed = 0;
|
|
331
|
-
for (const device of devices) {
|
|
332
|
-
const dc = device.config;
|
|
333
|
-
output += `### ${dc.deviceName}`;
|
|
334
|
-
if (dc.osName)
|
|
335
|
-
output += ` (${dc.osName}${dc.osVersion ? ` ${dc.osVersion}` : ""})`;
|
|
336
|
-
if (dc.orientation)
|
|
337
|
-
output += ` — ${dc.orientation}`;
|
|
338
|
-
output += `\n`;
|
|
339
|
-
for (const screenshotPath of device.screenshots) {
|
|
340
|
-
const screenshotName = basename(screenshotPath, extname(screenshotPath)).replace(/[-_]/g, " ");
|
|
341
|
-
try {
|
|
342
|
-
const content = await readFile(screenshotPath);
|
|
343
|
-
const sha = createHash("sha256").update(content).digest("hex");
|
|
344
|
-
// Validate PNG dimensions match device config
|
|
345
|
-
const pngDims = readPngDimensions(content);
|
|
346
|
-
if (pngDims) {
|
|
347
|
-
if (pngDims.width !== device.width ||
|
|
348
|
-
pngDims.height !== device.height) {
|
|
349
|
-
output += `- ✗ **${screenshotName}** — dimension mismatch: image is ${pngDims.width}x${pngDims.height}, device.json expects ${device.width}x${device.height}\n`;
|
|
350
|
-
failed++;
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
const base64 = content.toString("base64");
|
|
355
|
-
// Create snapshot
|
|
356
|
-
const snapAttrs = { name: screenshotName };
|
|
357
|
-
if (args.test_case)
|
|
358
|
-
snapAttrs["test-case"] = args.test_case;
|
|
359
|
-
const snapRes = await percyTokenPost(`/builds/${buildId}/snapshots`, token, { data: { type: "snapshots", attributes: snapAttrs } });
|
|
360
|
-
const snapId = snapRes?.data?.id;
|
|
361
|
-
if (!snapId) {
|
|
362
|
-
output += `- ✗ **${screenshotName}** — snapshot creation failed\n`;
|
|
363
|
-
failed++;
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
// Create comparison with device tag
|
|
367
|
-
const compRes = await percyTokenPost(`/snapshots/${snapId}/comparisons`, token, {
|
|
368
|
-
data: {
|
|
369
|
-
attributes: {
|
|
370
|
-
"external-debug-url": null,
|
|
371
|
-
"dom-info-sha": null,
|
|
372
|
-
},
|
|
373
|
-
relationships: {
|
|
374
|
-
tag: {
|
|
375
|
-
data: {
|
|
376
|
-
attributes: {
|
|
377
|
-
name: dc.deviceName,
|
|
378
|
-
width: device.width,
|
|
379
|
-
height: device.height,
|
|
380
|
-
"os-name": dc.osName,
|
|
381
|
-
...(dc.osVersion ? { "os-version": dc.osVersion } : {}),
|
|
382
|
-
orientation: dc.orientation || "portrait",
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
tiles: {
|
|
387
|
-
data: [
|
|
388
|
-
{
|
|
389
|
-
attributes: {
|
|
390
|
-
sha,
|
|
391
|
-
"status-bar-height": dc.statusBarHeight || 0,
|
|
392
|
-
"nav-bar-height": dc.navBarHeight || 0,
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
],
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
const compId = compRes?.data?.id;
|
|
401
|
-
if (!compId) {
|
|
402
|
-
output += `- ✗ **${screenshotName}** — comparison creation failed\n`;
|
|
403
|
-
failed++;
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
// Upload tile
|
|
407
|
-
await percyTokenPost(`/comparisons/${compId}/tiles`, token, {
|
|
408
|
-
data: { attributes: { "base64-content": base64 } },
|
|
409
|
-
});
|
|
410
|
-
// Finalize comparison
|
|
411
|
-
await percyTokenPost(`/comparisons/${compId}/finalize`, token, {});
|
|
412
|
-
uploaded++;
|
|
413
|
-
output += `- ✓ **${screenshotName}** (${device.width}×${device.height})\n`;
|
|
414
|
-
}
|
|
415
|
-
catch (e) {
|
|
416
|
-
output += `- ✗ **${screenshotName}** — ${e.message}\n`;
|
|
417
|
-
failed++;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
output += `\n`;
|
|
421
|
-
}
|
|
422
|
-
// ── 6. Finalize build ─────────────────────────────────────────────────────
|
|
423
|
-
try {
|
|
424
|
-
await percyTokenPost(`/builds/${buildId}/finalize`, token, {});
|
|
425
|
-
output += `---\n\n**Build finalized.** ${uploaded}/${totalScreenshots} snapshots uploaded`;
|
|
426
|
-
if (failed > 0)
|
|
427
|
-
output += `, ${failed} failed`;
|
|
428
|
-
output += `.\n`;
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
output += `---\n\n**Finalize failed:** ${e.message}\n`;
|
|
432
|
-
}
|
|
433
|
-
if (buildUrl) {
|
|
434
|
-
output += `\n**View build:** ${buildUrl}\n`;
|
|
435
|
-
}
|
|
436
|
-
output += `\n### Next Steps\n\n`;
|
|
437
|
-
output += `- \`percy_get_build\` with build_id "${buildId}" — View build details\n`;
|
|
438
|
-
output += `- \`percy_get_build\` with build_id "${buildId}" and detail "snapshots" — List snapshots\n`;
|
|
439
|
-
output += `- \`percy_get_build\` with build_id "${buildId}" and detail "ai_summary" — AI analysis\n`;
|
|
440
|
-
output += `- \`percy_get_builds\` — List all builds for this project\n`;
|
|
441
|
-
return { content: [{ type: "text", text: output }] };
|
|
442
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { BrowserStackConfig } from "../../../lib/types.js";
|
|
2
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
interface CreateBuildArgs {
|
|
4
|
-
project_name: string;
|
|
5
|
-
urls?: string;
|
|
6
|
-
screenshots_dir?: string;
|
|
7
|
-
screenshot_files?: string;
|
|
8
|
-
test_command?: string;
|
|
9
|
-
branch?: string;
|
|
10
|
-
widths?: string;
|
|
11
|
-
type?: string;
|
|
12
|
-
snapshot_names?: string;
|
|
13
|
-
test_case?: string;
|
|
14
|
-
}
|
|
15
|
-
export declare function percyCreateBuildV2(args: CreateBuildArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
16
|
-
export {};
|