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