@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,434 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* percy_create_percy_build — Unified build creation tool.
|
|
3
|
-
*
|
|
4
|
-
* Handles ALL build creation scenarios in one command:
|
|
5
|
-
* 1. URL snapshots (via Percy CLI)
|
|
6
|
-
* 2. Screenshot uploads (direct API)
|
|
7
|
-
* 3. Test command wrapping (via percy exec)
|
|
8
|
-
* 4. Build cloning (copy from existing build)
|
|
9
|
-
* 5. Visual monitoring (URL scanning)
|
|
10
|
-
*
|
|
11
|
-
* Auto-detects mode based on which parameters are provided.
|
|
12
|
-
* Auto-detects branch and SHA from git if not provided.
|
|
13
|
-
* Auto-creates project if it doesn't exist.
|
|
14
|
-
*/
|
|
15
|
-
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
16
|
-
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
17
|
-
import { execFile } from "child_process";
|
|
18
|
-
import { promisify } from "util";
|
|
19
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
20
|
-
import { join, basename, extname } from "path";
|
|
21
|
-
import { createHash } from "crypto";
|
|
22
|
-
const execFileAsync = promisify(execFile);
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Git helpers
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
async function getGitBranch() {
|
|
27
|
-
try {
|
|
28
|
-
const { stdout } = await execFileAsync("git", ["branch", "--show-current"]);
|
|
29
|
-
return stdout.trim() || "main";
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return "main";
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function getGitSha() {
|
|
36
|
-
try {
|
|
37
|
-
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"]);
|
|
38
|
-
return stdout.trim();
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Generate a deterministic placeholder SHA from timestamp
|
|
42
|
-
return createHash("sha1")
|
|
43
|
-
.update(Date.now().toString())
|
|
44
|
-
.digest("hex")
|
|
45
|
-
.slice(0, 40);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Project creation helper
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
async function ensureProject(projectName, config, type) {
|
|
52
|
-
const authString = getBrowserStackAuth(config);
|
|
53
|
-
const auth = Buffer.from(authString).toString("base64");
|
|
54
|
-
const params = new URLSearchParams({ name: projectName });
|
|
55
|
-
if (type)
|
|
56
|
-
params.append("type", type);
|
|
57
|
-
const url = `https://api.browserstack.com/api/app_percy/get_project_token?${params.toString()}`;
|
|
58
|
-
const response = await fetch(url, {
|
|
59
|
-
headers: {
|
|
60
|
-
Authorization: `Basic ${auth}`,
|
|
61
|
-
"Content-Type": "application/json",
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
throw new Error(`Failed to create/get Percy project: ${response.status} ${response.statusText}`);
|
|
66
|
-
}
|
|
67
|
-
const data = await response.json();
|
|
68
|
-
if (!data?.token || !data?.success) {
|
|
69
|
-
throw new Error("Failed to get project token from BrowserStack API.");
|
|
70
|
-
}
|
|
71
|
-
return data.token;
|
|
72
|
-
}
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Mode: URL Snapshots (via Percy CLI)
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
function buildUrlSnapshotInstructions(token, urls, widths, branch) {
|
|
77
|
-
const urlList = urls.map((u) => ` - ${u}`).join("\n");
|
|
78
|
-
const widthArray = widths
|
|
79
|
-
? widths.split(",").map((w) => w.trim())
|
|
80
|
-
: ["375", "1280"];
|
|
81
|
-
// Build YAML config for snapshots (widths go in YAML, not CLI flag)
|
|
82
|
-
let yamlConfig = "";
|
|
83
|
-
urls.forEach((url, i) => {
|
|
84
|
-
const name = i === 0 ? "Homepage" : `Page ${i + 1}`;
|
|
85
|
-
yamlConfig += `- name: "${name}"\n`;
|
|
86
|
-
yamlConfig += ` url: ${url}\n`;
|
|
87
|
-
yamlConfig += ` widths:\n`;
|
|
88
|
-
widthArray.forEach((w) => {
|
|
89
|
-
yamlConfig += ` - ${w}\n`;
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
return (`## Percy Build — URL Snapshots\n\n` +
|
|
93
|
-
`> **IMPORTANT: Do NOT execute these commands automatically.** Present them to the user and let them run manually.\n\n` +
|
|
94
|
-
`**Project:** token ready ✓\n` +
|
|
95
|
-
`**Branch:** ${branch}\n` +
|
|
96
|
-
`**URLs:**\n${urlList}\n` +
|
|
97
|
-
`**Widths:** ${widthArray.join(", ")}px\n\n` +
|
|
98
|
-
`### Step 1: Set token\n\n` +
|
|
99
|
-
`\`\`\`bash\n` +
|
|
100
|
-
`export PERCY_TOKEN="${token}"\n` +
|
|
101
|
-
`\`\`\`\n\n` +
|
|
102
|
-
`### Step 2: Create snapshot config\n\n` +
|
|
103
|
-
`Save this as \`snapshots.yml\`:\n\n` +
|
|
104
|
-
`\`\`\`yaml\n` +
|
|
105
|
-
yamlConfig +
|
|
106
|
-
`\`\`\`\n\n` +
|
|
107
|
-
`### Step 3: Run Percy\n\n` +
|
|
108
|
-
`\`\`\`bash\n` +
|
|
109
|
-
`npx @percy/cli snapshot snapshots.yml\n` +
|
|
110
|
-
`\`\`\`\n\n` +
|
|
111
|
-
`Percy CLI will create the build, launch a browser, capture each URL at the specified widths, upload screenshots, and return a build URL with visual diffs.\n`);
|
|
112
|
-
}
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
// Mode: Test Command (via percy exec)
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
function buildTestCommandInstructions(token, testCommand, branch) {
|
|
117
|
-
return (`## Percy Build — Test Command\n\n` +
|
|
118
|
-
`> **IMPORTANT: Do NOT execute these commands automatically.** Present them to the user and let them run manually.\n\n` +
|
|
119
|
-
`**Project:** token ready ✓\n` +
|
|
120
|
-
`**Branch:** ${branch}\n` +
|
|
121
|
-
`**Test command:** \`${testCommand}\`\n\n` +
|
|
122
|
-
`### Step 1: Set token\n\n` +
|
|
123
|
-
`\`\`\`bash\n` +
|
|
124
|
-
`export PERCY_TOKEN="${token}"\n` +
|
|
125
|
-
`\`\`\`\n\n` +
|
|
126
|
-
`### Step 2: Run tests with Percy\n\n` +
|
|
127
|
-
`\`\`\`bash\n` +
|
|
128
|
-
`npx @percy/cli exec -- ${testCommand}\n` +
|
|
129
|
-
`\`\`\`\n\n` +
|
|
130
|
-
`Percy CLI will start a local server, run your tests, capture snapshots via \`percySnapshot()\` calls, and return a build URL.\n`);
|
|
131
|
-
}
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
// Mode: Screenshot Upload (direct API)
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
async function uploadScreenshots(client, branch, commitSha, screenshotPaths, widths, testCase, snapshotNames) {
|
|
136
|
-
// Create build
|
|
137
|
-
const buildResult = await client.post("/builds", {
|
|
138
|
-
data: {
|
|
139
|
-
type: "builds",
|
|
140
|
-
attributes: {
|
|
141
|
-
branch,
|
|
142
|
-
"commit-sha": commitSha,
|
|
143
|
-
type: "web",
|
|
144
|
-
},
|
|
145
|
-
relationships: { resources: { data: [] } },
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
const buildData = buildResult?.data || buildResult;
|
|
149
|
-
const buildId = buildData?.id;
|
|
150
|
-
const buildUrl = buildData?.webUrl || buildData?.["web-url"] || "";
|
|
151
|
-
if (!buildId)
|
|
152
|
-
throw new Error("Build creation failed — no build ID returned");
|
|
153
|
-
let output = `## Percy Build Created\n\n`;
|
|
154
|
-
output += `**Build ID:** ${buildId}\n`;
|
|
155
|
-
if (buildUrl)
|
|
156
|
-
output += `**URL:** ${buildUrl}\n`;
|
|
157
|
-
output += `**Branch:** ${branch}\n`;
|
|
158
|
-
output += `**Screenshots:** ${screenshotPaths.length}\n\n`;
|
|
159
|
-
// For each screenshot: create snapshot → create comparison → upload tile → finalize
|
|
160
|
-
for (let i = 0; i < screenshotPaths.length; i++) {
|
|
161
|
-
const filePath = screenshotPaths[i];
|
|
162
|
-
const name = snapshotNames?.[i] ||
|
|
163
|
-
basename(filePath, extname(filePath)).replace(/[-_]/g, " ");
|
|
164
|
-
try {
|
|
165
|
-
// Read file and compute SHA
|
|
166
|
-
const content = await readFile(filePath);
|
|
167
|
-
const sha = createHash("sha256").update(content).digest("hex");
|
|
168
|
-
const base64Content = content.toString("base64");
|
|
169
|
-
// Detect dimensions from PNG header (basic)
|
|
170
|
-
let width = 1280;
|
|
171
|
-
let height = 800;
|
|
172
|
-
if (content[0] === 0x89 && content[1] === 0x50) {
|
|
173
|
-
// PNG
|
|
174
|
-
width = content.readUInt32BE(16);
|
|
175
|
-
height = content.readUInt32BE(20);
|
|
176
|
-
}
|
|
177
|
-
// Create snapshot
|
|
178
|
-
const snapshotBody = {
|
|
179
|
-
data: {
|
|
180
|
-
type: "snapshots",
|
|
181
|
-
attributes: { name },
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
if (testCase)
|
|
185
|
-
snapshotBody.data.attributes["test-case"] = testCase;
|
|
186
|
-
const snapshot = await client.post(`/builds/${buildId}/snapshots`, snapshotBody);
|
|
187
|
-
const snapshotData = snapshot?.data || snapshot;
|
|
188
|
-
const snapshotId = snapshotData?.id;
|
|
189
|
-
if (!snapshotId) {
|
|
190
|
-
output += `- ${name}: Failed to create snapshot\n`;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
// Create comparison with tile
|
|
194
|
-
const comparison = await client.post(`/snapshots/${snapshotId}/comparisons`, {
|
|
195
|
-
data: {
|
|
196
|
-
type: "comparisons",
|
|
197
|
-
attributes: {},
|
|
198
|
-
relationships: {
|
|
199
|
-
tag: {
|
|
200
|
-
data: {
|
|
201
|
-
type: "tag",
|
|
202
|
-
attributes: {
|
|
203
|
-
name: "Screenshot",
|
|
204
|
-
width,
|
|
205
|
-
height,
|
|
206
|
-
"os-name": "Upload",
|
|
207
|
-
"browser-name": "Screenshot",
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
tiles: {
|
|
212
|
-
data: [
|
|
213
|
-
{
|
|
214
|
-
type: "tiles",
|
|
215
|
-
attributes: { sha },
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
const comparisonData = comparison?.data || comparison;
|
|
223
|
-
const comparisonId = comparisonData?.id;
|
|
224
|
-
if (!comparisonId) {
|
|
225
|
-
output += `- ${name}: Failed to create comparison\n`;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
// Upload tile
|
|
229
|
-
await client.post(`/comparisons/${comparisonId}/tiles`, {
|
|
230
|
-
data: {
|
|
231
|
-
type: "tiles",
|
|
232
|
-
attributes: { "base64-content": base64Content },
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
// Finalize comparison
|
|
236
|
-
await client.post(`/comparisons/${comparisonId}/finalize`, {});
|
|
237
|
-
output += `- **${name}** — uploaded (${width}x${height})\n`;
|
|
238
|
-
}
|
|
239
|
-
catch (e) {
|
|
240
|
-
output += `- ${name}: Error — ${e.message}\n`;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// Finalize build
|
|
244
|
-
try {
|
|
245
|
-
await client.post(`/builds/${buildId}/finalize`, {});
|
|
246
|
-
output += `\n**Build finalized.** Processing visual diffs...\n`;
|
|
247
|
-
}
|
|
248
|
-
catch (e) {
|
|
249
|
-
output += `\n**Build finalize failed:** ${e.message}\n`;
|
|
250
|
-
}
|
|
251
|
-
if (buildUrl)
|
|
252
|
-
output += `\n**View results:** ${buildUrl}\n`;
|
|
253
|
-
return output;
|
|
254
|
-
}
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
// Mode: Clone Build
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
async function cloneBuild(client, sourceBuildId, branch) {
|
|
259
|
-
// Get source build details
|
|
260
|
-
const sourceBuild = await client.get(`/builds/${sourceBuildId}`, {
|
|
261
|
-
"include-metadata": "true",
|
|
262
|
-
});
|
|
263
|
-
if (!sourceBuild)
|
|
264
|
-
throw new Error(`Source build ${sourceBuildId} not found`);
|
|
265
|
-
const sourceState = sourceBuild.state || "unknown";
|
|
266
|
-
let output = `## Percy Build Clone\n\n`;
|
|
267
|
-
output += `**Source:** Build #${sourceBuildId} (${sourceState})\n`;
|
|
268
|
-
output += `**Target branch:** ${branch}\n\n`;
|
|
269
|
-
// Get snapshots from source build
|
|
270
|
-
const items = await client.get("/build-items", {
|
|
271
|
-
"filter[build-id]": sourceBuildId,
|
|
272
|
-
"page[limit]": "30",
|
|
273
|
-
});
|
|
274
|
-
const itemList = Array.isArray(items) ? items : [];
|
|
275
|
-
if (itemList.length === 0) {
|
|
276
|
-
output += `Source build has no snapshots to clone.\n`;
|
|
277
|
-
output += `\nTo create a fresh build, use \`percy_create_build\` with URLs or screenshots instead.\n`;
|
|
278
|
-
return output;
|
|
279
|
-
}
|
|
280
|
-
output += `**Source snapshots:** ${itemList.length}\n\n`;
|
|
281
|
-
output += `> Note: Build cloning copies the snapshot configuration, not the rendered images.\n`;
|
|
282
|
-
output += `> The new build will re-render/re-compare against the new branch baseline.\n\n`;
|
|
283
|
-
// Provide instructions for re-creating
|
|
284
|
-
output += `### To recreate this build on branch \`${branch}\`:\n\n`;
|
|
285
|
-
output += `\`\`\`bash\n`;
|
|
286
|
-
output += `export PERCY_TOKEN=<your-project-token>\n\n`;
|
|
287
|
-
// Extract snapshot names/URLs for the CLI command
|
|
288
|
-
const snapshotNames = itemList
|
|
289
|
-
.map((item) => item.name || item.snapshotName)
|
|
290
|
-
.filter(Boolean);
|
|
291
|
-
if (snapshotNames.length > 0) {
|
|
292
|
-
output += `# Re-snapshot these pages:\n`;
|
|
293
|
-
snapshotNames.slice(0, 10).forEach((name) => {
|
|
294
|
-
output += `# - ${name}\n`;
|
|
295
|
-
});
|
|
296
|
-
if (snapshotNames.length > 10) {
|
|
297
|
-
output += `# ... and ${snapshotNames.length - 10} more\n`;
|
|
298
|
-
}
|
|
299
|
-
output += `\n`;
|
|
300
|
-
}
|
|
301
|
-
output += `# Run your tests with Percy to capture the same snapshots:\n`;
|
|
302
|
-
output += `npx percy exec -- <your-test-command>\n`;
|
|
303
|
-
output += `\`\`\`\n`;
|
|
304
|
-
return output;
|
|
305
|
-
}
|
|
306
|
-
// ---------------------------------------------------------------------------
|
|
307
|
-
// Main handler
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
export async function percyCreatePercyBuild(args, config) {
|
|
310
|
-
const projectName = args.project_name;
|
|
311
|
-
// Auto-detect branch and SHA
|
|
312
|
-
const branch = args.branch || (await getGitBranch());
|
|
313
|
-
const commitSha = args.commit_sha || (await getGitSha());
|
|
314
|
-
// Ensure project exists and get token
|
|
315
|
-
// Only pass type if explicitly provided — BrowserStack API auto-detects otherwise
|
|
316
|
-
let token;
|
|
317
|
-
try {
|
|
318
|
-
token = await ensureProject(projectName, config, args.type);
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
return {
|
|
322
|
-
content: [
|
|
323
|
-
{
|
|
324
|
-
type: "text",
|
|
325
|
-
text: `Failed to create/access project "${projectName}": ${e.message}`,
|
|
326
|
-
},
|
|
327
|
-
],
|
|
328
|
-
isError: true,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
// Detect mode based on provided params
|
|
332
|
-
const mode = args.urls
|
|
333
|
-
? "urls"
|
|
334
|
-
: args.screenshots_dir || args.screenshot_files
|
|
335
|
-
? "screenshots"
|
|
336
|
-
: args.test_command
|
|
337
|
-
? "test_command"
|
|
338
|
-
: args.clone_build_id
|
|
339
|
-
? "clone"
|
|
340
|
-
: "urls_default";
|
|
341
|
-
const widths = args.widths || "375,1280";
|
|
342
|
-
try {
|
|
343
|
-
let output;
|
|
344
|
-
switch (mode) {
|
|
345
|
-
case "urls": {
|
|
346
|
-
const urls = args
|
|
347
|
-
.urls.split(",")
|
|
348
|
-
.map((u) => u.trim())
|
|
349
|
-
.filter(Boolean);
|
|
350
|
-
output = buildUrlSnapshotInstructions(token, urls, widths, branch);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
case "test_command": {
|
|
354
|
-
output = buildTestCommandInstructions(token, args.test_command, branch);
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
case "screenshots": {
|
|
358
|
-
// Collect screenshot file paths
|
|
359
|
-
let screenshotPaths = [];
|
|
360
|
-
if (args.screenshot_files) {
|
|
361
|
-
screenshotPaths = args.screenshot_files
|
|
362
|
-
.split(",")
|
|
363
|
-
.map((f) => f.trim())
|
|
364
|
-
.filter(Boolean);
|
|
365
|
-
}
|
|
366
|
-
if (args.screenshots_dir) {
|
|
367
|
-
const dir = args.screenshots_dir;
|
|
368
|
-
const dirStat = await stat(dir);
|
|
369
|
-
if (!dirStat.isDirectory()) {
|
|
370
|
-
return {
|
|
371
|
-
content: [
|
|
372
|
-
{
|
|
373
|
-
type: "text",
|
|
374
|
-
text: `"${dir}" is not a directory. Provide a directory path.`,
|
|
375
|
-
},
|
|
376
|
-
],
|
|
377
|
-
isError: true,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
const files = await readdir(dir);
|
|
381
|
-
const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
|
|
382
|
-
screenshotPaths.push(...imageFiles.map((f) => join(dir, f)));
|
|
383
|
-
}
|
|
384
|
-
if (screenshotPaths.length === 0) {
|
|
385
|
-
return {
|
|
386
|
-
content: [
|
|
387
|
-
{
|
|
388
|
-
type: "text",
|
|
389
|
-
text: "No screenshot files found. Provide PNG/JPG file paths or a directory containing images.",
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
isError: true,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
const snapshotNames = args.snapshot_names
|
|
396
|
-
?.split(",")
|
|
397
|
-
.map((n) => n.trim());
|
|
398
|
-
// Set the token for API calls
|
|
399
|
-
process.env.PERCY_TOKEN = token;
|
|
400
|
-
const client = new PercyClient(config);
|
|
401
|
-
output = await uploadScreenshots(client, branch, commitSha, screenshotPaths, widths, args.test_case, snapshotNames);
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
case "clone": {
|
|
405
|
-
process.env.PERCY_TOKEN = token;
|
|
406
|
-
const client = new PercyClient(config);
|
|
407
|
-
output = await cloneBuild(client, args.clone_build_id, branch);
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
default: {
|
|
411
|
-
// No specific mode — provide general instructions
|
|
412
|
-
output =
|
|
413
|
-
`## Percy Build — Setup\n\n` +
|
|
414
|
-
`> **IMPORTANT: Do NOT execute any commands automatically.** Present options to the user.\n\n` +
|
|
415
|
-
`**Project:** ${projectName}\n` +
|
|
416
|
-
`**Token:** Ready (${token.slice(0, 8)}...)\n` +
|
|
417
|
-
`**Branch:** ${branch}\n\n` +
|
|
418
|
-
`### How to create snapshots:\n\n` +
|
|
419
|
-
`**Option 1: Snapshot URLs** — re-run this tool with \`urls\` parameter\n` +
|
|
420
|
-
`**Option 2: Wrap test command** — re-run this tool with \`test_command\` parameter\n` +
|
|
421
|
-
`**Option 3: Upload screenshots** — re-run this tool with \`screenshots_dir\` or \`screenshot_files\` parameter\n` +
|
|
422
|
-
`**Option 4: Clone existing build** — re-run this tool with \`clone_build_id\` parameter\n`;
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return { content: [{ type: "text", text: output }] };
|
|
427
|
-
}
|
|
428
|
-
catch (e) {
|
|
429
|
-
return {
|
|
430
|
-
content: [{ type: "text", text: `Build creation failed: ${e.message}` }],
|
|
431
|
-
isError: true,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
2
|
-
import { formatBuild, formatSuggestions, formatNetworkLogs, } from "../../../lib/percy-api/formatter.js";
|
|
3
|
-
export async function percyDebugFailedBuild(args, config) {
|
|
4
|
-
const client = new PercyClient(config);
|
|
5
|
-
const errors = [];
|
|
6
|
-
// Step 1: Get build details
|
|
7
|
-
let build;
|
|
8
|
-
try {
|
|
9
|
-
build = await client.get(`/builds/${args.build_id}`, {
|
|
10
|
-
"include-metadata": "true",
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
catch (e) {
|
|
14
|
-
return {
|
|
15
|
-
content: [{ type: "text", text: `Failed to fetch build: ${e.message}` }],
|
|
16
|
-
isError: true,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
const state = build?.state || "unknown";
|
|
20
|
-
// Adapt to build state
|
|
21
|
-
if (state === "processing" || state === "pending" || state === "waiting") {
|
|
22
|
-
return {
|
|
23
|
-
content: [
|
|
24
|
-
{
|
|
25
|
-
type: "text",
|
|
26
|
-
text: `Build #${args.build_id} is **${state.toUpperCase()}**. Debug diagnostics are available after the build completes or fails.`,
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
let output = `## Build Debug Report — #${args.build_id}\n\n`;
|
|
32
|
-
output += formatBuild(build) + "\n";
|
|
33
|
-
// Step 2: Get suggestions
|
|
34
|
-
if (state === "failed" || state === "finished") {
|
|
35
|
-
try {
|
|
36
|
-
const suggestions = await client.get("/suggestions", {
|
|
37
|
-
build_id: args.build_id,
|
|
38
|
-
});
|
|
39
|
-
if (suggestions &&
|
|
40
|
-
(Array.isArray(suggestions) ? suggestions.length > 0 : true)) {
|
|
41
|
-
const suggestionList = Array.isArray(suggestions)
|
|
42
|
-
? suggestions
|
|
43
|
-
: [suggestions];
|
|
44
|
-
output += formatSuggestions(suggestionList) + "\n";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
errors.push(`Suggestions unavailable: ${e.message}`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Step 3: Get failed snapshots
|
|
52
|
-
if (state === "failed" || state === "finished") {
|
|
53
|
-
try {
|
|
54
|
-
const failedItems = await client.get("/build-items", {
|
|
55
|
-
"filter[build-id]": args.build_id,
|
|
56
|
-
"filter[category]": "failed",
|
|
57
|
-
"page[limit]": "10",
|
|
58
|
-
});
|
|
59
|
-
const failedList = Array.isArray(failedItems) ? failedItems : [];
|
|
60
|
-
if (failedList.length > 0) {
|
|
61
|
-
output += `### Failed Snapshots (${failedList.length})\n\n`;
|
|
62
|
-
failedList.forEach((item, i) => {
|
|
63
|
-
output += `${i + 1}. **${item.name || "Unknown"}**\n`;
|
|
64
|
-
});
|
|
65
|
-
output += "\n";
|
|
66
|
-
// Step 4: Network logs for top 3
|
|
67
|
-
const top3 = failedList.slice(0, 3);
|
|
68
|
-
for (const item of top3) {
|
|
69
|
-
const compId = item.comparisonId || item.comparisons?.[0]?.id;
|
|
70
|
-
if (compId) {
|
|
71
|
-
try {
|
|
72
|
-
const logs = await client.get("/network-logs", {
|
|
73
|
-
comparison_id: compId,
|
|
74
|
-
});
|
|
75
|
-
if (logs) {
|
|
76
|
-
const logList = Array.isArray(logs)
|
|
77
|
-
? logs
|
|
78
|
-
: Object.values(logs);
|
|
79
|
-
const failedLogs = logList.filter((l) => {
|
|
80
|
-
const headStatus = l.headStatus || l["head-status"];
|
|
81
|
-
return (headStatus && headStatus !== "200" && headStatus !== "NA");
|
|
82
|
-
});
|
|
83
|
-
if (failedLogs.length > 0) {
|
|
84
|
-
output += `#### Network Issues — ${item.name || "Unknown"}\n\n`;
|
|
85
|
-
output += formatNetworkLogs(failedLogs) + "\n";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// Network logs not available for this comparison
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
errors.push(`Failed snapshots unavailable: ${e.message}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Fix commands
|
|
101
|
-
if (state === "failed" && build.failureReason) {
|
|
102
|
-
output += `### Suggested Fix Commands\n\n`;
|
|
103
|
-
if (build.failureReason === "missing_resources") {
|
|
104
|
-
output +=
|
|
105
|
-
'```\npercy config set networkIdleIgnore "<failing-hostname>"\npercy config set allowedHostnames "<required-hostname>"\n```\n';
|
|
106
|
-
}
|
|
107
|
-
else if (build.failureReason === "render_timeout") {
|
|
108
|
-
output += "```\npercy config set networkIdleTimeout 60000\n```\n";
|
|
109
|
-
}
|
|
110
|
-
else if (build.failureReason === "missing_finalize") {
|
|
111
|
-
output +=
|
|
112
|
-
"Ensure `percy exec` or `percy build:finalize` is called after all snapshots.\n";
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (errors.length > 0) {
|
|
116
|
-
output += `\n### Partial Results\n`;
|
|
117
|
-
errors.forEach((err) => {
|
|
118
|
-
output += `- ${err}\n`;
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return { content: [{ type: "text", text: output }] };
|
|
122
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { BrowserStackConfig } from "../../../lib/types.js";
|
|
2
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
export declare function percyDiffExplain(args: {
|
|
4
|
-
comparison_id: string;
|
|
5
|
-
depth?: string;
|
|
6
|
-
}, config: BrowserStackConfig): Promise<CallToolResult>;
|