@browserstack/mcp-server 1.2.15-beta.2 → 1.2.16

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.
Files changed (182) hide show
  1. package/dist/server-factory.js +0 -4
  2. package/dist/tools/percy-sdk.js +20 -11
  3. package/dist/tools/testmanagement-utils/get-testplan.d.ts +16 -0
  4. package/dist/tools/testmanagement-utils/get-testplan.js +99 -0
  5. package/dist/tools/testmanagement-utils/list-folders.d.ts +16 -0
  6. package/dist/tools/testmanagement-utils/list-folders.js +77 -0
  7. package/dist/tools/testmanagement-utils/list-testcases.js +1 -1
  8. package/dist/tools/testmanagement-utils/list-testplans.d.ts +15 -0
  9. package/dist/tools/testmanagement-utils/list-testplans.js +75 -0
  10. package/dist/tools/testmanagement-utils/update-testcase.d.ts +16 -0
  11. package/dist/tools/testmanagement-utils/update-testcase.js +133 -10
  12. package/dist/tools/testmanagement.d.ts +15 -0
  13. package/dist/tools/testmanagement.js +73 -2
  14. package/package.json +2 -3
  15. package/dist/lib/percy-api/auth.d.ts +0 -41
  16. package/dist/lib/percy-api/auth.js +0 -96
  17. package/dist/lib/percy-api/cache.d.ts +0 -28
  18. package/dist/lib/percy-api/cache.js +0 -48
  19. package/dist/lib/percy-api/client.d.ts +0 -69
  20. package/dist/lib/percy-api/client.js +0 -275
  21. package/dist/lib/percy-api/errors.d.ts +0 -15
  22. package/dist/lib/percy-api/errors.js +0 -52
  23. package/dist/lib/percy-api/formatter.d.ts +0 -16
  24. package/dist/lib/percy-api/formatter.js +0 -344
  25. package/dist/lib/percy-api/percy-auth.d.ts +0 -43
  26. package/dist/lib/percy-api/percy-auth.js +0 -137
  27. package/dist/lib/percy-api/percy-error-handler.d.ts +0 -24
  28. package/dist/lib/percy-api/percy-error-handler.js +0 -302
  29. package/dist/lib/percy-api/percy-session.d.ts +0 -42
  30. package/dist/lib/percy-api/percy-session.js +0 -87
  31. package/dist/lib/percy-api/polling.d.ts +0 -26
  32. package/dist/lib/percy-api/polling.js +0 -42
  33. package/dist/lib/percy-api/types.d.ts +0 -56
  34. package/dist/lib/percy-api/types.js +0 -76
  35. package/dist/tools/percy-mcp/advanced/branchline-operations.d.ts +0 -16
  36. package/dist/tools/percy-mcp/advanced/branchline-operations.js +0 -81
  37. package/dist/tools/percy-mcp/advanced/manage-variants.d.ts +0 -16
  38. package/dist/tools/percy-mcp/advanced/manage-variants.js +0 -155
  39. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.d.ts +0 -16
  40. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.js +0 -171
  41. package/dist/tools/percy-mcp/auth/auth-status.d.ts +0 -3
  42. package/dist/tools/percy-mcp/auth/auth-status.js +0 -131
  43. package/dist/tools/percy-mcp/core/approve-build.d.ts +0 -14
  44. package/dist/tools/percy-mcp/core/approve-build.js +0 -97
  45. package/dist/tools/percy-mcp/core/get-build-items.d.ts +0 -13
  46. package/dist/tools/percy-mcp/core/get-build-items.js +0 -65
  47. package/dist/tools/percy-mcp/core/get-build.d.ts +0 -10
  48. package/dist/tools/percy-mcp/core/get-build.js +0 -16
  49. package/dist/tools/percy-mcp/core/get-comparison.d.ts +0 -11
  50. package/dist/tools/percy-mcp/core/get-comparison.js +0 -59
  51. package/dist/tools/percy-mcp/core/get-snapshot.d.ts +0 -10
  52. package/dist/tools/percy-mcp/core/get-snapshot.js +0 -40
  53. package/dist/tools/percy-mcp/core/list-builds.d.ts +0 -14
  54. package/dist/tools/percy-mcp/core/list-builds.js +0 -45
  55. package/dist/tools/percy-mcp/core/list-projects.d.ts +0 -12
  56. package/dist/tools/percy-mcp/core/list-projects.js +0 -51
  57. package/dist/tools/percy-mcp/creation/create-app-snapshot.d.ts +0 -12
  58. package/dist/tools/percy-mcp/creation/create-app-snapshot.js +0 -29
  59. package/dist/tools/percy-mcp/creation/create-build.d.ts +0 -19
  60. package/dist/tools/percy-mcp/creation/create-build.js +0 -68
  61. package/dist/tools/percy-mcp/creation/create-comparison.d.ts +0 -18
  62. package/dist/tools/percy-mcp/creation/create-comparison.js +0 -90
  63. package/dist/tools/percy-mcp/creation/create-snapshot.d.ts +0 -17
  64. package/dist/tools/percy-mcp/creation/create-snapshot.js +0 -99
  65. package/dist/tools/percy-mcp/creation/finalize-build.d.ts +0 -12
  66. package/dist/tools/percy-mcp/creation/finalize-build.js +0 -33
  67. package/dist/tools/percy-mcp/creation/finalize-comparison.d.ts +0 -10
  68. package/dist/tools/percy-mcp/creation/finalize-comparison.js +0 -16
  69. package/dist/tools/percy-mcp/creation/finalize-snapshot.d.ts +0 -12
  70. package/dist/tools/percy-mcp/creation/finalize-snapshot.js +0 -33
  71. package/dist/tools/percy-mcp/creation/upload-resource.d.ts +0 -15
  72. package/dist/tools/percy-mcp/creation/upload-resource.js +0 -43
  73. package/dist/tools/percy-mcp/creation/upload-tile.d.ts +0 -11
  74. package/dist/tools/percy-mcp/creation/upload-tile.js +0 -53
  75. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.d.ts +0 -13
  76. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.js +0 -65
  77. package/dist/tools/percy-mcp/diagnostics/get-build-logs.d.ts +0 -17
  78. package/dist/tools/percy-mcp/diagnostics/get-build-logs.js +0 -74
  79. package/dist/tools/percy-mcp/diagnostics/get-network-logs.d.ts +0 -5
  80. package/dist/tools/percy-mcp/diagnostics/get-network-logs.js +0 -21
  81. package/dist/tools/percy-mcp/diagnostics/get-suggestions.d.ts +0 -7
  82. package/dist/tools/percy-mcp/diagnostics/get-suggestions.js +0 -24
  83. package/dist/tools/percy-mcp/index.d.ts +0 -36
  84. package/dist/tools/percy-mcp/index.js +0 -1137
  85. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.d.ts +0 -15
  86. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.js +0 -166
  87. package/dist/tools/percy-mcp/intelligence/get-ai-quota.d.ts +0 -9
  88. package/dist/tools/percy-mcp/intelligence/get-ai-quota.js +0 -73
  89. package/dist/tools/percy-mcp/intelligence/get-build-summary.d.ts +0 -11
  90. package/dist/tools/percy-mcp/intelligence/get-build-summary.js +0 -78
  91. package/dist/tools/percy-mcp/intelligence/get-rca.d.ts +0 -6
  92. package/dist/tools/percy-mcp/intelligence/get-rca.js +0 -153
  93. package/dist/tools/percy-mcp/intelligence/suggest-prompt.d.ts +0 -15
  94. package/dist/tools/percy-mcp/intelligence/suggest-prompt.js +0 -86
  95. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.d.ts +0 -16
  96. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.js +0 -64
  97. package/dist/tools/percy-mcp/management/create-project.d.ts +0 -14
  98. package/dist/tools/percy-mcp/management/create-project.js +0 -52
  99. package/dist/tools/percy-mcp/management/get-usage-stats.d.ts +0 -12
  100. package/dist/tools/percy-mcp/management/get-usage-stats.js +0 -61
  101. package/dist/tools/percy-mcp/management/manage-browser-targets.d.ts +0 -12
  102. package/dist/tools/percy-mcp/management/manage-browser-targets.js +0 -136
  103. package/dist/tools/percy-mcp/management/manage-comments.d.ts +0 -14
  104. package/dist/tools/percy-mcp/management/manage-comments.js +0 -147
  105. package/dist/tools/percy-mcp/management/manage-ignored-regions.d.ts +0 -18
  106. package/dist/tools/percy-mcp/management/manage-ignored-regions.js +0 -182
  107. package/dist/tools/percy-mcp/management/manage-project-settings.d.ts +0 -16
  108. package/dist/tools/percy-mcp/management/manage-project-settings.js +0 -97
  109. package/dist/tools/percy-mcp/management/manage-tokens.d.ts +0 -14
  110. package/dist/tools/percy-mcp/management/manage-tokens.js +0 -90
  111. package/dist/tools/percy-mcp/management/manage-webhooks.d.ts +0 -15
  112. package/dist/tools/percy-mcp/management/manage-webhooks.js +0 -180
  113. package/dist/tools/percy-mcp/v2/auth-status.d.ts +0 -3
  114. package/dist/tools/percy-mcp/v2/auth-status.js +0 -80
  115. package/dist/tools/percy-mcp/v2/clone-build.d.ts +0 -24
  116. package/dist/tools/percy-mcp/v2/clone-build.js +0 -539
  117. package/dist/tools/percy-mcp/v2/create-app-build.d.ts +0 -28
  118. package/dist/tools/percy-mcp/v2/create-app-build.js +0 -442
  119. package/dist/tools/percy-mcp/v2/create-build.d.ts +0 -16
  120. package/dist/tools/percy-mcp/v2/create-build.js +0 -601
  121. package/dist/tools/percy-mcp/v2/create-project.d.ts +0 -8
  122. package/dist/tools/percy-mcp/v2/create-project.js +0 -33
  123. package/dist/tools/percy-mcp/v2/discover-urls.d.ts +0 -7
  124. package/dist/tools/percy-mcp/v2/discover-urls.js +0 -38
  125. package/dist/tools/percy-mcp/v2/figma-baseline.d.ts +0 -7
  126. package/dist/tools/percy-mcp/v2/figma-baseline.js +0 -18
  127. package/dist/tools/percy-mcp/v2/figma-build.d.ts +0 -7
  128. package/dist/tools/percy-mcp/v2/figma-build.js +0 -39
  129. package/dist/tools/percy-mcp/v2/figma-link.d.ts +0 -6
  130. package/dist/tools/percy-mcp/v2/figma-link.js +0 -27
  131. package/dist/tools/percy-mcp/v2/get-ai-summary.d.ts +0 -5
  132. package/dist/tools/percy-mcp/v2/get-ai-summary.js +0 -109
  133. package/dist/tools/percy-mcp/v2/get-build-detail.d.ts +0 -22
  134. package/dist/tools/percy-mcp/v2/get-build-detail.js +0 -567
  135. package/dist/tools/percy-mcp/v2/get-builds.d.ts +0 -8
  136. package/dist/tools/percy-mcp/v2/get-builds.js +0 -63
  137. package/dist/tools/percy-mcp/v2/get-comparison.d.ts +0 -5
  138. package/dist/tools/percy-mcp/v2/get-comparison.js +0 -94
  139. package/dist/tools/percy-mcp/v2/get-devices.d.ts +0 -5
  140. package/dist/tools/percy-mcp/v2/get-devices.js +0 -33
  141. package/dist/tools/percy-mcp/v2/get-insights.d.ts +0 -7
  142. package/dist/tools/percy-mcp/v2/get-insights.js +0 -52
  143. package/dist/tools/percy-mcp/v2/get-projects.d.ts +0 -6
  144. package/dist/tools/percy-mcp/v2/get-projects.js +0 -41
  145. package/dist/tools/percy-mcp/v2/get-snapshot.d.ts +0 -5
  146. package/dist/tools/percy-mcp/v2/get-snapshot.js +0 -96
  147. package/dist/tools/percy-mcp/v2/get-test-case-history.d.ts +0 -5
  148. package/dist/tools/percy-mcp/v2/get-test-case-history.js +0 -20
  149. package/dist/tools/percy-mcp/v2/get-test-cases.d.ts +0 -6
  150. package/dist/tools/percy-mcp/v2/get-test-cases.js +0 -36
  151. package/dist/tools/percy-mcp/v2/index.d.ts +0 -35
  152. package/dist/tools/percy-mcp/v2/index.js +0 -544
  153. package/dist/tools/percy-mcp/v2/list-integrations.d.ts +0 -5
  154. package/dist/tools/percy-mcp/v2/list-integrations.js +0 -41
  155. package/dist/tools/percy-mcp/v2/manage-domains.d.ts +0 -8
  156. package/dist/tools/percy-mcp/v2/manage-domains.js +0 -33
  157. package/dist/tools/percy-mcp/v2/manage-insights-email.d.ts +0 -8
  158. package/dist/tools/percy-mcp/v2/manage-insights-email.js +0 -49
  159. package/dist/tools/percy-mcp/v2/manage-usage-alerts.d.ts +0 -10
  160. package/dist/tools/percy-mcp/v2/manage-usage-alerts.js +0 -43
  161. package/dist/tools/percy-mcp/v2/migrate-integrations.d.ts +0 -6
  162. package/dist/tools/percy-mcp/v2/migrate-integrations.js +0 -20
  163. package/dist/tools/percy-mcp/v2/preview-comparison.d.ts +0 -5
  164. package/dist/tools/percy-mcp/v2/preview-comparison.js +0 -17
  165. package/dist/tools/percy-mcp/v2/search-build-items.d.ts +0 -12
  166. package/dist/tools/percy-mcp/v2/search-build-items.js +0 -45
  167. package/dist/tools/percy-mcp/workflows/auto-triage.d.ts +0 -7
  168. package/dist/tools/percy-mcp/workflows/auto-triage.js +0 -82
  169. package/dist/tools/percy-mcp/workflows/clone-build.d.ts +0 -22
  170. package/dist/tools/percy-mcp/workflows/clone-build.js +0 -414
  171. package/dist/tools/percy-mcp/workflows/create-percy-build.d.ts +0 -32
  172. package/dist/tools/percy-mcp/workflows/create-percy-build.js +0 -434
  173. package/dist/tools/percy-mcp/workflows/debug-failed-build.d.ts +0 -5
  174. package/dist/tools/percy-mcp/workflows/debug-failed-build.js +0 -122
  175. package/dist/tools/percy-mcp/workflows/diff-explain.d.ts +0 -6
  176. package/dist/tools/percy-mcp/workflows/diff-explain.js +0 -147
  177. package/dist/tools/percy-mcp/workflows/pr-visual-report.d.ts +0 -8
  178. package/dist/tools/percy-mcp/workflows/pr-visual-report.js +0 -184
  179. package/dist/tools/percy-mcp/workflows/run-tests.d.ts +0 -17
  180. package/dist/tools/percy-mcp/workflows/run-tests.js +0 -107
  181. package/dist/tools/percy-mcp/workflows/snapshot-urls.d.ts +0 -18
  182. 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 {};