@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,414 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* percy_clone_build — Clone snapshots from a source build to a new build,
|
|
3
|
-
* even across different projects.
|
|
4
|
-
*
|
|
5
|
-
* Flow:
|
|
6
|
-
* 1. Read build-items to get snapshot IDs
|
|
7
|
-
* 2. For each snapshot: fetch raw JSON:API with includes to get image URLs
|
|
8
|
-
* 3. Create target build + snapshots + comparisons with downloaded screenshots
|
|
9
|
-
* 4. Finalize
|
|
10
|
-
*/
|
|
11
|
-
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
12
|
-
import { getPercyHeaders, getPercyApiBaseUrl, } from "../../../lib/percy-api/auth.js";
|
|
13
|
-
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
14
|
-
import { createHash } from "crypto";
|
|
15
|
-
import { execFile } from "child_process";
|
|
16
|
-
import { promisify } from "util";
|
|
17
|
-
const execFileAsync = promisify(execFile);
|
|
18
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
19
|
-
async function getGitBranch() {
|
|
20
|
-
try {
|
|
21
|
-
const { stdout } = await execFileAsync("git", ["branch", "--show-current"]);
|
|
22
|
-
return stdout.trim() || "main";
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return "main";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async function getGitSha() {
|
|
29
|
-
try {
|
|
30
|
-
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"]);
|
|
31
|
-
return stdout.trim();
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return createHash("sha1").update(Date.now().toString()).digest("hex");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function getProjectToken(projectName, config) {
|
|
38
|
-
const authString = getBrowserStackAuth(config);
|
|
39
|
-
const auth = Buffer.from(authString).toString("base64");
|
|
40
|
-
const url = `https://api.browserstack.com/api/app_percy/get_project_token?name=${encodeURIComponent(projectName)}`;
|
|
41
|
-
const response = await fetch(url, {
|
|
42
|
-
headers: { Authorization: `Basic ${auth}` },
|
|
43
|
-
});
|
|
44
|
-
if (!response.ok)
|
|
45
|
-
throw new Error(`Failed to get token for "${projectName}"`);
|
|
46
|
-
const data = await response.json();
|
|
47
|
-
if (!data?.token || !data?.success)
|
|
48
|
-
throw new Error(`No token returned for "${projectName}"`);
|
|
49
|
-
return data.token;
|
|
50
|
-
}
|
|
51
|
-
async function fetchImageAsBase64(imageUrl) {
|
|
52
|
-
try {
|
|
53
|
-
const response = await fetch(imageUrl);
|
|
54
|
-
if (!response.ok)
|
|
55
|
-
return null;
|
|
56
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
57
|
-
return buffer.toString("base64");
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Fetch a snapshot with RAW JSON:API response to manually walk the
|
|
65
|
-
* included chain: comparison → head-screenshot → image → url
|
|
66
|
-
*/
|
|
67
|
-
async function fetchSnapshotRaw(snapshotId, config) {
|
|
68
|
-
const headers = await getPercyHeaders(config);
|
|
69
|
-
const baseUrl = getPercyApiBaseUrl();
|
|
70
|
-
const url = `${baseUrl}/snapshots/${snapshotId}?include=comparisons.head-screenshot.image,comparisons.comparison-tag`;
|
|
71
|
-
const response = await fetch(url, { headers });
|
|
72
|
-
if (!response.ok)
|
|
73
|
-
return null;
|
|
74
|
-
const json = await response.json();
|
|
75
|
-
const data = json.data;
|
|
76
|
-
const included = json.included || [];
|
|
77
|
-
if (!data)
|
|
78
|
-
return null;
|
|
79
|
-
const name = data.attributes?.name || "Unknown";
|
|
80
|
-
// Build lookup maps from included
|
|
81
|
-
const byTypeId = new Map();
|
|
82
|
-
for (const item of included) {
|
|
83
|
-
byTypeId.set(`${item.type}:${item.id}`, item);
|
|
84
|
-
}
|
|
85
|
-
// Get comparison IDs from snapshot relationships
|
|
86
|
-
const compRefs = data.relationships?.comparisons?.data || [];
|
|
87
|
-
const comparisons = [];
|
|
88
|
-
// Debug: dump first comparison's relationships keys
|
|
89
|
-
let debugRelKeys = "";
|
|
90
|
-
for (const compRef of compRefs) {
|
|
91
|
-
const comp = byTypeId.get(`comparisons:${compRef.id}`);
|
|
92
|
-
if (!comp)
|
|
93
|
-
continue;
|
|
94
|
-
if (!debugRelKeys && comp.relationships) {
|
|
95
|
-
debugRelKeys = Object.keys(comp.relationships).join(", ");
|
|
96
|
-
}
|
|
97
|
-
const width = comp.attributes?.width || 1280;
|
|
98
|
-
// Walk: comparison → head-screenshot → image
|
|
99
|
-
const hsRef = comp.relationships?.["head-screenshot"]?.data;
|
|
100
|
-
let imageUrl = null;
|
|
101
|
-
let height = 800;
|
|
102
|
-
if (hsRef) {
|
|
103
|
-
const screenshot = byTypeId.get(`screenshots:${hsRef.id}`);
|
|
104
|
-
if (screenshot) {
|
|
105
|
-
const imgRef = screenshot.relationships?.image?.data;
|
|
106
|
-
if (imgRef) {
|
|
107
|
-
const image = byTypeId.get(`images:${imgRef.id}`);
|
|
108
|
-
if (image) {
|
|
109
|
-
imageUrl = image.attributes?.url || null;
|
|
110
|
-
height = image.attributes?.height || 800;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Get comparison tag
|
|
116
|
-
const tagRef = comp.relationships?.["comparison-tag"]?.data;
|
|
117
|
-
let tagName = "Screenshot";
|
|
118
|
-
let osName = "Clone";
|
|
119
|
-
let browserName = "Screenshot";
|
|
120
|
-
if (tagRef) {
|
|
121
|
-
const tag = byTypeId.get(`comparison-tags:${tagRef.id}`);
|
|
122
|
-
if (tag) {
|
|
123
|
-
tagName = tag.attributes?.name || "Screenshot";
|
|
124
|
-
osName = tag.attributes?.["os-name"] || "Clone";
|
|
125
|
-
browserName = tag.attributes?.["browser-name"] || "Screenshot";
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
comparisons.push({
|
|
129
|
-
width,
|
|
130
|
-
height,
|
|
131
|
-
tagName,
|
|
132
|
-
osName,
|
|
133
|
-
browserName,
|
|
134
|
-
imageUrl,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
return { name, comparisons, debugRelKeys };
|
|
138
|
-
}
|
|
139
|
-
export async function percyCloneBuild(args, config) {
|
|
140
|
-
const { source_build_id, target_project_name } = args;
|
|
141
|
-
const branch = args.branch || (await getGitBranch());
|
|
142
|
-
const commitSha = args.commit_sha || (await getGitSha());
|
|
143
|
-
let output = `## Percy Build Clone\n\n`;
|
|
144
|
-
output += `**Source build:** #${source_build_id}\n`;
|
|
145
|
-
output += `**Target project:** ${target_project_name}\n`;
|
|
146
|
-
output += `**Branch:** ${branch}\n\n`;
|
|
147
|
-
// ── Step 1: Set up source token ───────────────────────────────────────
|
|
148
|
-
const originalToken = process.env.PERCY_TOKEN;
|
|
149
|
-
if (args.source_token) {
|
|
150
|
-
process.env.PERCY_TOKEN = args.source_token;
|
|
151
|
-
}
|
|
152
|
-
else if (!process.env.PERCY_TOKEN) {
|
|
153
|
-
return {
|
|
154
|
-
content: [
|
|
155
|
-
{
|
|
156
|
-
type: "text",
|
|
157
|
-
text: "Need a token to read the source build. Provide `source_token` or set PERCY_TOKEN.",
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
isError: true,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
const sourceClient = new PercyClient(config);
|
|
164
|
-
// ── Step 2: Read source build ─────────────────────────────────────────
|
|
165
|
-
let sourceBuild;
|
|
166
|
-
try {
|
|
167
|
-
sourceBuild = await sourceClient.get(`/builds/${source_build_id}`);
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
171
|
-
return {
|
|
172
|
-
content: [
|
|
173
|
-
{
|
|
174
|
-
type: "text",
|
|
175
|
-
text: `Failed to read source build: ${e.message}\n\nUse a full-access token (web_* or auto_*), not a CI token.`,
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
isError: true,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
output += `Source: **${sourceBuild?.state || "unknown"}** — ${sourceBuild?.totalSnapshots || "?"} snapshots, ${sourceBuild?.totalComparisons || "?"} comparisons\n\n`;
|
|
182
|
-
// ── Step 3: Get snapshot IDs from build-items ─────────────────────────
|
|
183
|
-
let allSnapshotIds = [];
|
|
184
|
-
try {
|
|
185
|
-
const items = await sourceClient.get("/build-items", {
|
|
186
|
-
"filter[build-id]": source_build_id,
|
|
187
|
-
"page[limit]": "30",
|
|
188
|
-
});
|
|
189
|
-
const itemList = Array.isArray(items) ? items : [];
|
|
190
|
-
// Extract all snapshot IDs from build-items (grouped format)
|
|
191
|
-
for (const item of itemList) {
|
|
192
|
-
if (item.snapshotIds && Array.isArray(item.snapshotIds)) {
|
|
193
|
-
allSnapshotIds.push(...item.snapshotIds.map((id) => String(id)));
|
|
194
|
-
}
|
|
195
|
-
else if (item.coverSnapshotId) {
|
|
196
|
-
allSnapshotIds.push(String(item.coverSnapshotId));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Deduplicate
|
|
200
|
-
allSnapshotIds = [...new Set(allSnapshotIds)];
|
|
201
|
-
}
|
|
202
|
-
catch (e) {
|
|
203
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: "text",
|
|
208
|
-
text: `Failed to read build items: ${e.message}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
isError: true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
output += `Found **${allSnapshotIds.length}** snapshot(s) to clone.\n\n`;
|
|
215
|
-
if (allSnapshotIds.length === 0) {
|
|
216
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
217
|
-
output += "No snapshots found. Nothing to clone.\n";
|
|
218
|
-
return { content: [{ type: "text", text: output }] };
|
|
219
|
-
}
|
|
220
|
-
// ── Step 4: Fetch each snapshot with raw JSON:API ─────────────────────
|
|
221
|
-
output += `### Reading snapshot details...\n\n`;
|
|
222
|
-
// Limit to 20 snapshots to avoid timeout
|
|
223
|
-
const snapshotsToClone = allSnapshotIds.slice(0, 20);
|
|
224
|
-
const snapshotData = [];
|
|
225
|
-
for (const snapId of snapshotsToClone) {
|
|
226
|
-
const detail = await fetchSnapshotRaw(snapId, config);
|
|
227
|
-
if (detail) {
|
|
228
|
-
snapshotData.push(detail);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
output += `- ⚠ Could not read snapshot ${snapId}\n`;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
output += `Read ${snapshotData.length} snapshot(s) with ${snapshotData.reduce((s, d) => s + d.comparisons.length, 0)} comparison(s).\n\n`;
|
|
235
|
-
// ── Step 5: Create target project and build ───────────────────────────
|
|
236
|
-
output += `### Creating target build...\n\n`;
|
|
237
|
-
let targetToken;
|
|
238
|
-
if (args.target_token) {
|
|
239
|
-
// Use provided token — clones into existing project
|
|
240
|
-
targetToken = args.target_token;
|
|
241
|
-
output += `Using provided target token for project "${target_project_name}".\n`;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
// Auto-create/get project via BrowserStack API
|
|
245
|
-
try {
|
|
246
|
-
targetToken = await getProjectToken(target_project_name, config);
|
|
247
|
-
}
|
|
248
|
-
catch (e) {
|
|
249
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
250
|
-
return {
|
|
251
|
-
content: [
|
|
252
|
-
{
|
|
253
|
-
type: "text",
|
|
254
|
-
text: `Failed to create/access target project: ${e.message}\n\nTip: To clone into an existing project, provide its token via the \`target_token\` parameter.`,
|
|
255
|
-
},
|
|
256
|
-
],
|
|
257
|
-
isError: true,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Switch to target token for writes
|
|
262
|
-
process.env.PERCY_TOKEN = targetToken;
|
|
263
|
-
const targetClient = new PercyClient(config);
|
|
264
|
-
let targetBuildId;
|
|
265
|
-
let targetBuildUrl = "";
|
|
266
|
-
try {
|
|
267
|
-
const build = await targetClient.post("/builds", {
|
|
268
|
-
data: {
|
|
269
|
-
type: "builds",
|
|
270
|
-
attributes: { branch, "commit-sha": commitSha },
|
|
271
|
-
relationships: { resources: { data: [] } },
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
targetBuildId = build?.id || (build?.data || build)?.id;
|
|
275
|
-
targetBuildUrl =
|
|
276
|
-
build?.webUrl ||
|
|
277
|
-
build?.["web-url"] ||
|
|
278
|
-
(build?.data || build)?.webUrl ||
|
|
279
|
-
"";
|
|
280
|
-
}
|
|
281
|
-
catch (e) {
|
|
282
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
283
|
-
return {
|
|
284
|
-
content: [
|
|
285
|
-
{ type: "text", text: `Failed to create target build: ${e.message}` },
|
|
286
|
-
],
|
|
287
|
-
isError: true,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
output += `Target build: **#${targetBuildId}**\n`;
|
|
291
|
-
if (targetBuildUrl)
|
|
292
|
-
output += `URL: ${targetBuildUrl}\n`;
|
|
293
|
-
output += "\n### Cloning snapshots...\n\n";
|
|
294
|
-
// ── Step 6: Clone each snapshot ───────────────────────────────────────
|
|
295
|
-
let clonedCount = 0;
|
|
296
|
-
let failedCount = 0;
|
|
297
|
-
for (const snap of snapshotData) {
|
|
298
|
-
const comparisonsWithImages = snap.comparisons.filter((c) => c.imageUrl);
|
|
299
|
-
if (comparisonsWithImages.length === 0) {
|
|
300
|
-
output += `- ⚠ **${snap.name}** — no downloadable screenshots (web/DOM build)\n`;
|
|
301
|
-
failedCount++;
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
try {
|
|
305
|
-
// Create snapshot in target
|
|
306
|
-
const snapResult = await targetClient.post(`/builds/${targetBuildId}/snapshots`, { data: { type: "snapshots", attributes: { name: snap.name } } });
|
|
307
|
-
const newSnapId = snapResult?.id || (snapResult?.data || snapResult)?.id;
|
|
308
|
-
if (!newSnapId) {
|
|
309
|
-
output += `- ✗ **${snap.name}** — failed to create snapshot\n`;
|
|
310
|
-
failedCount++;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
let compCloned = 0;
|
|
314
|
-
// Debug: output comparison tags for first snapshot
|
|
315
|
-
if (clonedCount === 0) {
|
|
316
|
-
output += ` [DBG] relationship keys: ${snap.debugRelKeys || "NONE"}\n`;
|
|
317
|
-
for (const c of comparisonsWithImages) {
|
|
318
|
-
output += ` [DBG] tag="${c.tagName}" w=${c.width} h=${c.height} os="${c.osName}" browser="${c.browserName}"\n`;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
for (const comp of comparisonsWithImages) {
|
|
322
|
-
// Download screenshot
|
|
323
|
-
const base64 = await fetchImageAsBase64(comp.imageUrl);
|
|
324
|
-
if (!base64) {
|
|
325
|
-
output += ` ⚠ Could not download image for ${comp.tagName} ${comp.width}px\n`;
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
const imageBuffer = Buffer.from(base64, "base64");
|
|
329
|
-
const sha = createHash("sha256").update(imageBuffer).digest("hex");
|
|
330
|
-
try {
|
|
331
|
-
// Create comparison with tile — must match JSON:API format with type fields
|
|
332
|
-
const tagAttributes = {
|
|
333
|
-
name: comp.tagName,
|
|
334
|
-
width: comp.width,
|
|
335
|
-
height: comp.height,
|
|
336
|
-
};
|
|
337
|
-
if (comp.osName)
|
|
338
|
-
tagAttributes["os-name"] = comp.osName;
|
|
339
|
-
if (comp.browserName)
|
|
340
|
-
tagAttributes["browser-name"] = comp.browserName;
|
|
341
|
-
const compResult = await targetClient.post(`/snapshots/${newSnapId}/comparisons`, {
|
|
342
|
-
data: {
|
|
343
|
-
type: "comparisons",
|
|
344
|
-
relationships: {
|
|
345
|
-
tag: {
|
|
346
|
-
data: {
|
|
347
|
-
type: "tag",
|
|
348
|
-
attributes: tagAttributes,
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
tiles: {
|
|
352
|
-
data: [
|
|
353
|
-
{
|
|
354
|
-
type: "tiles",
|
|
355
|
-
attributes: {
|
|
356
|
-
sha,
|
|
357
|
-
"status-bar-height": 0,
|
|
358
|
-
"nav-bar-height": 0,
|
|
359
|
-
},
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
const newCompId = compResult?.id || (compResult?.data || compResult)?.id;
|
|
367
|
-
if (newCompId) {
|
|
368
|
-
// Upload tile
|
|
369
|
-
await targetClient.post(`/comparisons/${newCompId}/tiles`, {
|
|
370
|
-
data: {
|
|
371
|
-
attributes: { "base64-content": base64 },
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
// Finalize comparison
|
|
375
|
-
await targetClient.post(`/comparisons/${newCompId}/finalize`, {});
|
|
376
|
-
compCloned++;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch (compErr) {
|
|
380
|
-
output += ` ⚠ ${comp.tagName} ${comp.width}px: ${compErr.message}\n`;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
clonedCount++;
|
|
384
|
-
output += `- ✓ **${snap.name}** — ${compCloned}/${comparisonsWithImages.length} comparisons\n`;
|
|
385
|
-
}
|
|
386
|
-
catch (e) {
|
|
387
|
-
output += `- ✗ **${snap.name}** — ${e.message}\n`;
|
|
388
|
-
failedCount++;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
// ── Step 7: Finalize ──────────────────────────────────────────────────
|
|
392
|
-
output += "\n";
|
|
393
|
-
try {
|
|
394
|
-
await targetClient.post(`/builds/${targetBuildId}/finalize`, {});
|
|
395
|
-
output += `### Build finalized ✓\n\n`;
|
|
396
|
-
}
|
|
397
|
-
catch (e) {
|
|
398
|
-
output += `### Build finalize failed: ${e.message}\n\n`;
|
|
399
|
-
}
|
|
400
|
-
// Summary
|
|
401
|
-
output += `### Summary\n\n`;
|
|
402
|
-
output += `| | Count |\n|---|---|\n`;
|
|
403
|
-
output += `| Snapshots cloned | ${clonedCount} |\n`;
|
|
404
|
-
output += `| Failed/skipped | ${failedCount} |\n`;
|
|
405
|
-
output += `| Target build | #${targetBuildId} |\n`;
|
|
406
|
-
if (targetBuildUrl)
|
|
407
|
-
output += `| View results | ${targetBuildUrl} |\n`;
|
|
408
|
-
if (allSnapshotIds.length > 20) {
|
|
409
|
-
output += `\n> Note: Cloned first 20 of ${allSnapshotIds.length} snapshots.\n`;
|
|
410
|
-
}
|
|
411
|
-
// Restore token
|
|
412
|
-
process.env.PERCY_TOKEN = originalToken || "";
|
|
413
|
-
return { content: [{ type: "text", text: output }] };
|
|
414
|
-
}
|
|
@@ -1,32 +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 { BrowserStackConfig } from "../../../lib/types.js";
|
|
16
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
-
interface CreatePercyBuildArgs {
|
|
18
|
-
project_name: string;
|
|
19
|
-
urls?: string;
|
|
20
|
-
screenshots_dir?: string;
|
|
21
|
-
screenshot_files?: string;
|
|
22
|
-
test_command?: string;
|
|
23
|
-
clone_build_id?: string;
|
|
24
|
-
branch?: string;
|
|
25
|
-
commit_sha?: string;
|
|
26
|
-
widths?: string;
|
|
27
|
-
snapshot_names?: string;
|
|
28
|
-
test_case?: string;
|
|
29
|
-
type?: string;
|
|
30
|
-
}
|
|
31
|
-
export declare function percyCreatePercyBuild(args: CreatePercyBuildArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
32
|
-
export {};
|