@bugbug-io/sdk 13.39.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 (85) hide show
  1. package/README.md +576 -0
  2. package/dist/constants/sdk-error-codes.d.ts +35 -0
  3. package/dist/constants/sdk-error-codes.d.ts.map +1 -0
  4. package/dist/index.d.ts +87 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +3844 -0
  7. package/dist/modules/auth/auth.d.ts +11 -0
  8. package/dist/modules/auth/auth.d.ts.map +1 -0
  9. package/dist/modules/auth/auth.types.d.ts +34 -0
  10. package/dist/modules/auth/auth.types.d.ts.map +1 -0
  11. package/dist/modules/auth/auth.utils.d.ts +4 -0
  12. package/dist/modules/auth/auth.utils.d.ts.map +1 -0
  13. package/dist/modules/components/components.d.ts +43 -0
  14. package/dist/modules/components/components.d.ts.map +1 -0
  15. package/dist/modules/config/config.d.ts +48 -0
  16. package/dist/modules/config/config.d.ts.map +1 -0
  17. package/dist/modules/groups/groups.d.ts +62 -0
  18. package/dist/modules/groups/groups.d.ts.map +1 -0
  19. package/dist/modules/profiles/profiles.d.ts +71 -0
  20. package/dist/modules/profiles/profiles.d.ts.map +1 -0
  21. package/dist/modules/project/project.d.ts +58 -0
  22. package/dist/modules/project/project.d.ts.map +1 -0
  23. package/dist/modules/projectArtifacts/projectArtifacts.d.ts +8 -0
  24. package/dist/modules/projectArtifacts/projectArtifacts.d.ts.map +1 -0
  25. package/dist/modules/projectArtifacts/projectArtifacts.types.d.ts +14 -0
  26. package/dist/modules/projectArtifacts/projectArtifacts.types.d.ts.map +1 -0
  27. package/dist/modules/projects/projects.d.ts +36 -0
  28. package/dist/modules/projects/projects.d.ts.map +1 -0
  29. package/dist/modules/projects/projects.types.d.ts +6 -0
  30. package/dist/modules/projects/projects.types.d.ts.map +1 -0
  31. package/dist/modules/stepRuns/stepRuns.d.ts +22 -0
  32. package/dist/modules/stepRuns/stepRuns.d.ts.map +1 -0
  33. package/dist/modules/steps/steps.d.ts +47 -0
  34. package/dist/modules/steps/steps.d.ts.map +1 -0
  35. package/dist/modules/steps/steps.utils.d.ts +11 -0
  36. package/dist/modules/steps/steps.utils.d.ts.map +1 -0
  37. package/dist/modules/suites/suites.d.ts +153 -0
  38. package/dist/modules/suites/suites.d.ts.map +1 -0
  39. package/dist/modules/suites/suites.types.d.ts +31 -0
  40. package/dist/modules/suites/suites.types.d.ts.map +1 -0
  41. package/dist/modules/tests/tests.d.ts +253 -0
  42. package/dist/modules/tests/tests.d.ts.map +1 -0
  43. package/dist/modules/tests/tests.types.d.ts +34 -0
  44. package/dist/modules/tests/tests.types.d.ts.map +1 -0
  45. package/dist/modules/tests/tests.utils.d.ts +11 -0
  46. package/dist/modules/tests/tests.utils.d.ts.map +1 -0
  47. package/dist/modules/variables/variables.d.ts +14 -0
  48. package/dist/modules/variables/variables.d.ts.map +1 -0
  49. package/dist/modules/variables/variables.types.d.ts +7 -0
  50. package/dist/modules/variables/variables.types.d.ts.map +1 -0
  51. package/dist/modules/visualRegression/visualRegression.constants.d.ts +7 -0
  52. package/dist/modules/visualRegression/visualRegression.constants.d.ts.map +1 -0
  53. package/dist/modules/visualRegression/visualRegression.d.ts +69 -0
  54. package/dist/modules/visualRegression/visualRegression.d.ts.map +1 -0
  55. package/dist/modules/visualRegression/visualRegression.types.d.ts +12 -0
  56. package/dist/modules/visualRegression/visualRegression.types.d.ts.map +1 -0
  57. package/dist/sdk.d.ts +126 -0
  58. package/dist/sdk.d.ts.map +1 -0
  59. package/dist/services/apiClient/apiClient.d.ts +167 -0
  60. package/dist/services/apiClient/apiClient.d.ts.map +1 -0
  61. package/dist/services/apiClient/apiClient.types.d.ts +19 -0
  62. package/dist/services/apiClient/apiClient.types.d.ts.map +1 -0
  63. package/dist/services/apiClient/apiClient.utils.d.ts +32 -0
  64. package/dist/services/apiClient/apiClient.utils.d.ts.map +1 -0
  65. package/dist/services/rateLimiter/rateLimiter.constants.d.ts +5 -0
  66. package/dist/services/rateLimiter/rateLimiter.constants.d.ts.map +1 -0
  67. package/dist/services/rateLimiter/rateLimiter.d.ts +49 -0
  68. package/dist/services/rateLimiter/rateLimiter.d.ts.map +1 -0
  69. package/dist/services/rateLimiter/rateLimiter.types.d.ts +34 -0
  70. package/dist/services/rateLimiter/rateLimiter.types.d.ts.map +1 -0
  71. package/dist/testUtils/fakeFactory.d.ts +15 -0
  72. package/dist/testUtils/fakeFactory.d.ts.map +1 -0
  73. package/dist/testUtils/setup.d.ts +2 -0
  74. package/dist/testUtils/setup.d.ts.map +1 -0
  75. package/dist/types/config.d.ts +121 -0
  76. package/dist/types/config.d.ts.map +1 -0
  77. package/dist/types/errors.d.ts +162 -0
  78. package/dist/types/errors.d.ts.map +1 -0
  79. package/dist/types/sdk.d.ts +28 -0
  80. package/dist/types/sdk.d.ts.map +1 -0
  81. package/dist/utils/pagination.d.ts +10 -0
  82. package/dist/utils/pagination.d.ts.map +1 -0
  83. package/dist/utils/polling.d.ts +20 -0
  84. package/dist/utils/polling.d.ts.map +1 -0
  85. package/package.json +83 -0
package/dist/index.js ADDED
@@ -0,0 +1,3844 @@
1
+ // src/modules/auth/auth.ts
2
+ var AuthModule = class {
3
+ #client;
4
+ constructor(client) {
5
+ this.#client = client;
6
+ }
7
+ getAuth = async (params, options = {}) => {
8
+ const response = await this.#client.getAppRoot("/auth/authorize-url", {
9
+ ...options,
10
+ authenticated: false,
11
+ acceptRedirect: true,
12
+ redirect: "manual",
13
+ queryParams: {
14
+ client_id: params.clientId,
15
+ redirect_uri: params.redirectUri,
16
+ code_challenge: params.codeChallenge,
17
+ code_challenge_method: params.codeChallengeMethod,
18
+ state: params.state
19
+ }
20
+ });
21
+ const authorizeUrl = response instanceof Response ? response.headers.get("location") : void 0;
22
+ if (!authorizeUrl) {
23
+ throw new Error("BugBug OAuth authorize response did not include a redirect URL.");
24
+ }
25
+ return { authorizeUrl: new URL(authorizeUrl, response.url).toString() };
26
+ };
27
+ exchangeCode = async (payload, options = {}) => this.#client.postAppRoot("/auth/oauth/token/", {
28
+ ...options,
29
+ authenticated: false,
30
+ body: {
31
+ grant_type: payload.grantType,
32
+ client_id: payload.clientId,
33
+ redirect_uri: payload.redirectUri,
34
+ code: payload.code,
35
+ code_verifier: payload.codeVerifier
36
+ }
37
+ });
38
+ identity = async (options = {}) => this.#client.getAppRoot("/auth/identity", options);
39
+ };
40
+
41
+ // src/modules/components/components.ts
42
+ var ComponentsModule = class {
43
+ #client;
44
+ constructor(client) {
45
+ this.#client = client;
46
+ }
47
+ /**
48
+ * List all components with optional filtering and pagination
49
+ *
50
+ * @param options - Query options including search query, page, and page size
51
+ * @returns Paginated list of components with nested structure
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * // List all components
56
+ * const components = await sdk.components.list();
57
+ *
58
+ * // Search for specific components
59
+ * const loginComponents = await sdk.components.list({
60
+ * query: 'login',
61
+ * cursor: nextToken,
62
+ * pageSize: 50
63
+ * });
64
+ * ```
65
+ */
66
+ list = async (options = {}) => {
67
+ const { query: searchQuery, cursor, pageSize, ordering, ...requestOptions } = options;
68
+ return this.#client.get("/components/", {
69
+ queryParams: {
70
+ query: searchQuery,
71
+ cursor,
72
+ page_size: pageSize,
73
+ ordering
74
+ },
75
+ ...requestOptions
76
+ });
77
+ };
78
+ /**
79
+ * Fetch component usage — how many tests reference this component and which.
80
+ *
81
+ * @param componentId - UUID of the component.
82
+ * @returns `{ usage, testsIds }` from `GET /components/{id}/usage/`.
83
+ */
84
+ getUsage = async (componentId, options = {}) => this.#client.get("/components/{id}/usage/", {
85
+ pathParams: { id: componentId },
86
+ ...options
87
+ });
88
+ };
89
+
90
+ // ../core/dist/utils/time.js
91
+ var hoursAgo = (hours) => new Date(Date.now() - hours * 60 * 60 * 1e3).toISOString();
92
+ var now = () => (/* @__PURE__ */ new Date()).toISOString();
93
+
94
+ // src/modules/config/config.ts
95
+ var ConfigModule = class {
96
+ #client;
97
+ constructor(client) {
98
+ this.#client = client;
99
+ }
100
+ /**
101
+ * Fetch the BugBug infrastructure IP ranges that should be allowlisted for
102
+ * outbound runner traffic.
103
+ */
104
+ getIpAddresses = async (options = {}) => this.#client.get("/config/ips/", options);
105
+ /**
106
+ * Liveness probe + token check.
107
+ *
108
+ * `GET /config/status/` is unauthenticated. If the SDK's `apiToken` is sent,
109
+ * the server validates it and reports the result via `authenticated`; an
110
+ * invalid token still returns 200, so this method only throws on
111
+ * transport-level failures.
112
+ */
113
+ getStatus = async (options = {}) => this.#client.get("/config/status/", options);
114
+ /**
115
+ * Probe API connectivity and authentication by making one lightweight call.
116
+ *
117
+ * Never throws — transport failures are returned as `connected: false` with
118
+ * the error message. When the call succeeds, `authenticated` reflects whether
119
+ * the configured API token is currently valid.
120
+ *
121
+ * @returns A `ConnectivityStatus` snapshot with response time and message.
122
+ */
123
+ testConnectivity = async (options = {}) => {
124
+ const startTime = Date.now();
125
+ try {
126
+ const { authenticated } = await this.getStatus(options);
127
+ return {
128
+ connected: true,
129
+ responseTime: Date.now() - startTime,
130
+ timestamp: now(),
131
+ message: "API connectivity successful",
132
+ authenticated
133
+ };
134
+ } catch (error) {
135
+ return {
136
+ connected: false,
137
+ responseTime: Date.now() - startTime,
138
+ timestamp: now(),
139
+ error: error instanceof Error ? error.message : "Unknown error",
140
+ message: "API connectivity failed"
141
+ };
142
+ }
143
+ };
144
+ /**
145
+ * Single-shot system summary: IP ranges + connectivity probe.
146
+ *
147
+ * Internally issues one IP-fetch and reuses it as the connectivity probe to
148
+ * avoid duplicate requests.
149
+ *
150
+ * @returns A {@link SdkSystemInfo} snapshot with `{ ipAddresses, connectivity, timestamp }`.
151
+ */
152
+ getSystemInfo = async (options = {}) => {
153
+ const startTime = Date.now();
154
+ let ipAddresses = null;
155
+ let connectivity;
156
+ try {
157
+ ipAddresses = await this.getIpAddresses(options);
158
+ connectivity = {
159
+ connected: true,
160
+ responseTime: Date.now() - startTime,
161
+ timestamp: now(),
162
+ message: "API connectivity successful"
163
+ };
164
+ } catch (error) {
165
+ connectivity = {
166
+ connected: false,
167
+ responseTime: Date.now() - startTime,
168
+ timestamp: now(),
169
+ error: error instanceof Error ? error.message : "Unknown error",
170
+ message: "API connectivity failed"
171
+ };
172
+ }
173
+ return {
174
+ ipAddresses,
175
+ connectivity,
176
+ timestamp: now()
177
+ };
178
+ };
179
+ };
180
+
181
+ // src/utils/pagination.ts
182
+ var DEFAULT_PAGE_SIZE = 100;
183
+ var getAllPages = async (listPage, options = {}) => {
184
+ const items = [];
185
+ const baseOptions = options;
186
+ let { cursor } = baseOptions;
187
+ do {
188
+ const response = await listPage({
189
+ ...baseOptions,
190
+ cursor,
191
+ pageSize: baseOptions.pageSize ?? DEFAULT_PAGE_SIZE
192
+ });
193
+ items.push(...response.results ?? []);
194
+ cursor = response.nextToken ?? void 0;
195
+ } while (cursor);
196
+ return items;
197
+ };
198
+
199
+ // src/modules/groups/groups.ts
200
+ var GroupsModule = class {
201
+ #client;
202
+ constructor(client) {
203
+ this.#client = client;
204
+ }
205
+ /**
206
+ * List groups with pagination, search, and ordering.
207
+ *
208
+ * @param options.query - Search query matched against group name.
209
+ * @param options.ordering - Sort key; prefix with `-` for descending.
210
+ * @returns Paginated `GroupsListResponse`.
211
+ */
212
+ list = async (options = {}) => {
213
+ const { query: searchQuery, cursor, pageSize, ordering, ...requestOptions } = options;
214
+ return this.#client.get("/groups/", {
215
+ queryParams: {
216
+ query: searchQuery,
217
+ cursor,
218
+ page_size: pageSize,
219
+ ordering
220
+ },
221
+ ...requestOptions
222
+ });
223
+ };
224
+ /**
225
+ * Fetch every group across all pages.
226
+ */
227
+ getAll = async (options = {}) => getAllPages((pageOptions) => this.list({ ...options, ...pageOptions }));
228
+ /**
229
+ * Fetch a single group by ID (with nested steps).
230
+ *
231
+ * @throws {NotFoundError} When no group matches `groupId`.
232
+ */
233
+ get = async (groupId, options = {}) => this.#client.get("/groups/{id}/", {
234
+ pathParams: { id: groupId },
235
+ ...options
236
+ });
237
+ /**
238
+ * Create a new (empty) group.
239
+ *
240
+ * @throws {ValidationError} On invalid payload.
241
+ */
242
+ create = async (data, options = {}) => this.#client.post("/groups/", {
243
+ body: data,
244
+ ...options
245
+ });
246
+ /**
247
+ * Fully replace a group via PUT. Use {@link partialUpdate} for PATCH semantics.
248
+ *
249
+ * @throws {NotFoundError} When the group does not exist.
250
+ * @throws {ValidationError} On invalid payload.
251
+ */
252
+ update = async (groupId, data, options = {}) => this.#client.put("/groups/{id}/", {
253
+ pathParams: { id: groupId },
254
+ body: data,
255
+ ...options
256
+ });
257
+ /**
258
+ * Patch selected fields on a group (PATCH semantics).
259
+ *
260
+ * @throws {NotFoundError} When the group does not exist.
261
+ * @throws {ValidationError} On invalid payload.
262
+ */
263
+ partialUpdate = async (groupId, data, options = {}) => this.#client.patch("/groups/{id}/", {
264
+ pathParams: { id: groupId },
265
+ body: data,
266
+ ...options
267
+ });
268
+ /**
269
+ * Delete a group by ID. Tests that reference this group will lose those steps.
270
+ *
271
+ * @throws {NotFoundError} When the group does not exist.
272
+ */
273
+ delete = async (groupId, options = {}) => this.#client.delete("/groups/{id}/", {
274
+ pathParams: { id: groupId },
275
+ ...options
276
+ });
277
+ };
278
+
279
+ // ../core/dist/utils/validation.js
280
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
281
+ var isUuid = (value) => UUID_REGEX.test(value);
282
+
283
+ // src/constants/sdk-error-codes.ts
284
+ var SDK_ERROR_CODES = {
285
+ AUTHENTICATION_ERROR: "AUTHENTICATION_ERROR",
286
+ AUTHORIZATION_ERROR: "AUTHORIZATION_ERROR",
287
+ VALIDATION_ERROR: "VALIDATION_ERROR",
288
+ CONFIGURATION_ERROR: "CONFIGURATION_ERROR",
289
+ MISSING_PROJECT_ID_ERROR: "MISSING_PROJECT_ID_ERROR",
290
+ NOT_FOUND_ERROR: "NOT_FOUND_ERROR",
291
+ RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
292
+ TIMEOUT_ERROR: "TIMEOUT_ERROR",
293
+ NETWORK_ERROR: "NETWORK_ERROR",
294
+ CANCELLATION_ERROR: "CANCELLATION_ERROR",
295
+ INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
296
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
297
+ UNEXPECTED_RESPONSE: "UNEXPECTED_RESPONSE",
298
+ HTTP_ERROR: "HTTP_ERROR"
299
+ };
300
+ var STATUS_NO_RESPONSE = 0;
301
+ var STATUS_CLIENT_TIMEOUT = 408;
302
+ var STATUS_CLIENT_CANCELLED = 499;
303
+
304
+ // src/types/errors.ts
305
+ var HTTP_STATUS_INTERNAL_SERVER_ERROR = 500;
306
+ var HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
307
+ var BugBugError = class _BugBugError extends Error {
308
+ code;
309
+ statusCode;
310
+ response;
311
+ request;
312
+ cause;
313
+ constructor(code, message, statusCode, response, cause, request) {
314
+ super(message);
315
+ this.name = "BugBugError";
316
+ this.code = code;
317
+ this.statusCode = statusCode;
318
+ this.response = response;
319
+ this.request = request ?? (response ? { url: response.url } : void 0);
320
+ this.cause = cause;
321
+ if (Error.captureStackTrace) {
322
+ Error.captureStackTrace(this, _BugBugError);
323
+ }
324
+ }
325
+ toJSON() {
326
+ return {
327
+ name: this.name,
328
+ code: this.code,
329
+ message: this.message,
330
+ statusCode: this.statusCode,
331
+ request: this.request,
332
+ stack: this.stack
333
+ };
334
+ }
335
+ };
336
+ var AuthenticationError = class extends BugBugError {
337
+ constructor(message = "Authentication failed", response, request) {
338
+ super(
339
+ SDK_ERROR_CODES.AUTHENTICATION_ERROR,
340
+ message,
341
+ response?.status ?? 401,
342
+ response,
343
+ void 0,
344
+ request
345
+ );
346
+ this.name = "AuthenticationError";
347
+ }
348
+ };
349
+ var AuthorizationError = class extends BugBugError {
350
+ constructor(message = "Access denied", response, request) {
351
+ super(
352
+ SDK_ERROR_CODES.AUTHORIZATION_ERROR,
353
+ message,
354
+ response?.status ?? 403,
355
+ response,
356
+ void 0,
357
+ request
358
+ );
359
+ this.name = "AuthorizationError";
360
+ }
361
+ };
362
+ var ValidationError = class extends BugBugError {
363
+ fields;
364
+ constructor(message, response, fields, request) {
365
+ super(
366
+ SDK_ERROR_CODES.VALIDATION_ERROR,
367
+ message,
368
+ response?.status ?? 400,
369
+ response,
370
+ void 0,
371
+ request
372
+ );
373
+ this.name = "ValidationError";
374
+ this.fields = fields;
375
+ }
376
+ };
377
+ var ConfigurationError = class extends BugBugError {
378
+ constructor(message) {
379
+ super(SDK_ERROR_CODES.CONFIGURATION_ERROR, message, STATUS_NO_RESPONSE);
380
+ this.name = "ConfigurationError";
381
+ }
382
+ };
383
+ var MissingProjectIdError = class extends BugBugError {
384
+ constructor(message = "No project selected. Provide a project id (--project-id) or switch project.", response, request) {
385
+ super(
386
+ SDK_ERROR_CODES.MISSING_PROJECT_ID_ERROR,
387
+ message,
388
+ response?.status ?? 400,
389
+ response,
390
+ void 0,
391
+ request
392
+ );
393
+ this.name = "MissingProjectIdError";
394
+ }
395
+ };
396
+ var NotFoundError = class extends BugBugError {
397
+ constructor(message = "Resource not found", response, request) {
398
+ super(
399
+ SDK_ERROR_CODES.NOT_FOUND_ERROR,
400
+ message,
401
+ response?.status ?? 404,
402
+ response,
403
+ void 0,
404
+ request
405
+ );
406
+ this.name = "NotFoundError";
407
+ }
408
+ };
409
+ var RateLimitError = class extends BugBugError {
410
+ retryAfter;
411
+ constructor(retryAfter, message = "Rate limit exceeded", response, request) {
412
+ super(
413
+ SDK_ERROR_CODES.RATE_LIMIT_ERROR,
414
+ message,
415
+ response?.status ?? 429,
416
+ response,
417
+ void 0,
418
+ request
419
+ );
420
+ this.name = "RateLimitError";
421
+ this.retryAfter = retryAfter;
422
+ }
423
+ };
424
+ var TimeoutError = class extends BugBugError {
425
+ timeout;
426
+ constructor(timeout, message, request) {
427
+ const defaultMessage = `Request timed out after ${timeout}ms`;
428
+ super(
429
+ SDK_ERROR_CODES.TIMEOUT_ERROR,
430
+ message || defaultMessage,
431
+ STATUS_CLIENT_TIMEOUT,
432
+ void 0,
433
+ void 0,
434
+ request
435
+ );
436
+ this.name = "TimeoutError";
437
+ this.timeout = timeout;
438
+ }
439
+ };
440
+ var NetworkError = class extends BugBugError {
441
+ constructor(message, cause, request) {
442
+ super(SDK_ERROR_CODES.NETWORK_ERROR, message, STATUS_NO_RESPONSE, void 0, cause, request);
443
+ this.name = "NetworkError";
444
+ }
445
+ };
446
+ var CancellationError = class extends BugBugError {
447
+ constructor(message = "Request was cancelled", request) {
448
+ super(
449
+ SDK_ERROR_CODES.CANCELLATION_ERROR,
450
+ message,
451
+ STATUS_CLIENT_CANCELLED,
452
+ void 0,
453
+ void 0,
454
+ request
455
+ );
456
+ this.name = "CancellationError";
457
+ }
458
+ };
459
+ var InternalServerError = class extends BugBugError {
460
+ constructor(message = "Internal server error", response, request) {
461
+ super(
462
+ SDK_ERROR_CODES.INTERNAL_SERVER_ERROR,
463
+ message,
464
+ response?.status ?? HTTP_STATUS_INTERNAL_SERVER_ERROR,
465
+ response,
466
+ void 0,
467
+ request
468
+ );
469
+ this.name = "InternalServerError";
470
+ }
471
+ };
472
+ var ServiceUnavailableError = class extends BugBugError {
473
+ constructor(message = "Service temporarily unavailable", response, request) {
474
+ super(
475
+ SDK_ERROR_CODES.SERVICE_UNAVAILABLE,
476
+ message,
477
+ response?.status ?? HTTP_STATUS_SERVICE_UNAVAILABLE,
478
+ response,
479
+ void 0,
480
+ request
481
+ );
482
+ this.name = "ServiceUnavailableError";
483
+ }
484
+ };
485
+ var UnexpectedResponseError = class extends BugBugError {
486
+ constructor(message, cause, request) {
487
+ super(
488
+ SDK_ERROR_CODES.UNEXPECTED_RESPONSE,
489
+ message,
490
+ STATUS_NO_RESPONSE,
491
+ void 0,
492
+ cause,
493
+ request
494
+ );
495
+ this.name = "UnexpectedResponseError";
496
+ }
497
+ };
498
+ var HttpError = class extends BugBugError {
499
+ constructor(message, statusCode, response, request) {
500
+ super(SDK_ERROR_CODES.HTTP_ERROR, message, statusCode, response, void 0, request);
501
+ this.name = "HttpError";
502
+ }
503
+ };
504
+ function isBugBugError(error) {
505
+ return error instanceof BugBugError;
506
+ }
507
+ function isAuthenticationError(error) {
508
+ return error instanceof AuthenticationError;
509
+ }
510
+ function isAuthorizationError(error) {
511
+ return error instanceof AuthorizationError;
512
+ }
513
+ function isValidationError(error) {
514
+ return error instanceof ValidationError;
515
+ }
516
+ function isConfigurationError(error) {
517
+ return error instanceof ConfigurationError;
518
+ }
519
+ function isMissingProjectIdError(error) {
520
+ return error instanceof MissingProjectIdError;
521
+ }
522
+ function isNotFoundError(error) {
523
+ return error instanceof NotFoundError;
524
+ }
525
+ function isRateLimitError(error) {
526
+ return error instanceof RateLimitError;
527
+ }
528
+ function isTimeoutError(error) {
529
+ return error instanceof TimeoutError;
530
+ }
531
+ function isNetworkError(error) {
532
+ return error instanceof NetworkError;
533
+ }
534
+ function isCancellationError(error) {
535
+ return error instanceof CancellationError;
536
+ }
537
+ function isInternalServerError(error) {
538
+ return error instanceof InternalServerError;
539
+ }
540
+ function isServiceUnavailableError(error) {
541
+ return error instanceof ServiceUnavailableError;
542
+ }
543
+ function isUnexpectedResponseError(error) {
544
+ return error instanceof UnexpectedResponseError;
545
+ }
546
+ function isHttpError(error) {
547
+ return error instanceof HttpError;
548
+ }
549
+
550
+ // src/modules/profiles/profiles.ts
551
+ var ProfilesModule = class {
552
+ #client;
553
+ constructor(client) {
554
+ this.#client = client;
555
+ }
556
+ /**
557
+ * List run profiles, one page at a time.
558
+ *
559
+ * @param options.cursor - Opaque cursor returned as `nextToken` by the previous page.
560
+ * @param options.pageSize - Items per page.
561
+ * @returns Paginated `ProfilesListResponse` (validated via Zod).
562
+ * @throws {UnexpectedResponseError} On Zod validation failure.
563
+ */
564
+ list = async (options = {}) => {
565
+ const { cursor, pageSize, ...requestOptions } = options;
566
+ return this.#client.get("/profiles/", {
567
+ queryParams: {
568
+ cursor,
569
+ page_size: pageSize
570
+ },
571
+ ...requestOptions
572
+ });
573
+ };
574
+ /**
575
+ * Fetch a single profile by ID.
576
+ *
577
+ * @throws {NotFoundError} When no profile matches `profileId`.
578
+ */
579
+ get = async (profileId, options = {}) => this.#client.get("/profiles/{id}/", {
580
+ pathParams: { id: profileId },
581
+ ...options
582
+ });
583
+ /**
584
+ * Find a profile by name across all pages.
585
+ *
586
+ * @returns The matching profile, or `null` when no profile has that name.
587
+ */
588
+ findByName = async (profileName, options = {}) => {
589
+ const profiles = await this.getAll(options);
590
+ return profiles.find((profile) => profile.name === profileName) || null;
591
+ };
592
+ /**
593
+ * Fetch every profile across all pages.
594
+ *
595
+ * Internally follows `nextToken` until the API signals exhaustion.
596
+ * For large tenants this can issue multiple sequential requests; cache the
597
+ * result where possible.
598
+ */
599
+ getAll = async (options = {}) => getAllPages((pageOptions) => this.list({ ...options, ...pageOptions }));
600
+ /**
601
+ * Return the profile flagged `isDefault`, falling back to the first profile.
602
+ *
603
+ * @returns The default `Profile`, or `null` when the tenant has no profiles.
604
+ */
605
+ getDefault = async (options = {}) => {
606
+ const profiles = await this.getAll(options);
607
+ return profiles.find((profile) => profile.isDefault) || profiles[0] || null;
608
+ };
609
+ /**
610
+ * Resolve a profile name **or** UUID to a profile UUID suitable for the
611
+ * `runProfileId` field on a run request.
612
+ *
613
+ * - UUID input → returned unchanged with **no** API call.
614
+ * - Name input → looked up via {@link findByName}.
615
+ *
616
+ * @param profileNameOrId - Human-readable profile name (e.g. `"Production"`) or UUID.
617
+ * @returns The matching profile UUID.
618
+ * @throws {NotFoundError} When `profileNameOrId` is not a UUID and no profile has that name.
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * const profileId = await sdk.profiles.resolveId('Production');
623
+ * await sdk.tests.startRun(testId, { profileName: profileId });
624
+ * ```
625
+ */
626
+ resolveId = async (profileNameOrId, options = {}) => {
627
+ if (isUuid(profileNameOrId)) {
628
+ return profileNameOrId;
629
+ }
630
+ const profile = await this.findByName(profileNameOrId, options);
631
+ if (!profile) {
632
+ throw new NotFoundError(`Profile '${profileNameOrId}' not found`);
633
+ }
634
+ return profile.id;
635
+ };
636
+ };
637
+
638
+ // src/modules/project/project.ts
639
+ var ProjectModule = class {
640
+ #client;
641
+ constructor(client) {
642
+ this.#client = client;
643
+ }
644
+ /**
645
+ * Fetch project settings (e.g. retention, integrations).
646
+ *
647
+ * @returns The `ProjectSettings` object for the project.
648
+ */
649
+ getSettings = async (options = {}) => this.#client.get("/settings/", options);
650
+ /**
651
+ * Export the entire project as a ZIP archive (tests, suites, components,
652
+ * profiles, settings).
653
+ *
654
+ * @returns Raw ZIP bytes ready to be written to disk or uploaded elsewhere.
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * import { writeFile } from 'node:fs/promises';
659
+ * const zipBytes = await sdk.project.export();
660
+ * await writeFile('project.zip', zipBytes);
661
+ * ```
662
+ */
663
+ export = async (options = {}) => this.#client.getBinary("/project/export/", options);
664
+ /**
665
+ * Import a project from a ZIP archive.
666
+ *
667
+ * Performs the two-step server flow:
668
+ * 1. **Validate** the upload — surfaces server-side errors (malformed ZIP,
669
+ * naming conflicts, …) as {@link ValidationError}.
670
+ * 2. **Run** the import asynchronously using the validated upload id.
671
+ *
672
+ * @param zipBytes - Raw ZIP archive bytes.
673
+ * @returns The response from the import run endpoint (typically a 202 with
674
+ * empty body — kept as `unknown` until the API stabilizes the shape).
675
+ * @throws {UnexpectedResponseError} When validation succeeds but the server
676
+ * omits the `uploadId` needed to trigger the run step.
677
+ * @throws {ValidationError} When the ZIP is rejected by validate.
678
+ *
679
+ * @example
680
+ * ```typescript
681
+ * import { readFile } from 'node:fs/promises';
682
+ * const bytes = await readFile('project.zip');
683
+ * await sdk.project.import(bytes);
684
+ * ```
685
+ */
686
+ import = async (zipBytes, options = {}) => {
687
+ const validation = await this.#validateImport(zipBytes, options);
688
+ const uploadId = typeof validation.uploadId === "string" ? validation.uploadId : void 0;
689
+ if (!uploadId) {
690
+ throw new UnexpectedResponseError(
691
+ "Import validation succeeded but did not return an uploadId."
692
+ );
693
+ }
694
+ return this.#client.post("/project/import/run/", {
695
+ ...options,
696
+ body: { uploadId }
697
+ });
698
+ };
699
+ #validateImport = async (zipBytes, options) => {
700
+ const form = new FormData();
701
+ form.append(
702
+ "file",
703
+ new Blob([zipBytes], { type: "application/zip" }),
704
+ "project.zip"
705
+ );
706
+ return this.#client.postForm(
707
+ "/project/import/validate/",
708
+ form,
709
+ options
710
+ );
711
+ };
712
+ };
713
+
714
+ // src/modules/projectArtifacts/projectArtifacts.ts
715
+ var ProjectArtifactsModule = class {
716
+ #client;
717
+ constructor(client) {
718
+ this.#client = client;
719
+ }
720
+ upload = async ({
721
+ projectId,
722
+ file,
723
+ fileName,
724
+ contentType = "application/octet-stream",
725
+ stepId,
726
+ ...options
727
+ }) => {
728
+ const form = new FormData();
729
+ const blob = file instanceof Blob ? file : new Blob([file], { type: contentType });
730
+ form.append("file", blob, fileName);
731
+ if (stepId) {
732
+ form.append("stepId", stepId);
733
+ }
734
+ return this.#client.postForm("/projects/{projectId}/artifacts/", form, {
735
+ ...options,
736
+ pathParams: { projectId },
737
+ useAppRoot: true
738
+ });
739
+ };
740
+ };
741
+
742
+ // src/modules/projects/projects.ts
743
+ var ProjectsModule = class {
744
+ #client;
745
+ constructor(client) {
746
+ this.#client = client;
747
+ }
748
+ /**
749
+ * List projects accessible with the current token, one page at a time.
750
+ *
751
+ * @param options.cursor - Opaque cursor returned as `nextToken` by the previous page.
752
+ * @param options.pageSize - Items per page.
753
+ * @param options.query - Case-insensitive name filter.
754
+ * @returns Paginated `ProjectsListResponse`.
755
+ */
756
+ list = async (options = {}) => {
757
+ const { cursor, pageSize, query, ...requestOptions } = options;
758
+ return this.#client.get("/projects/", {
759
+ ...requestOptions,
760
+ queryParams: {
761
+ cursor,
762
+ page_size: pageSize,
763
+ query
764
+ },
765
+ projectScoped: false
766
+ });
767
+ };
768
+ /**
769
+ * Fetch every project across all pages.
770
+ *
771
+ * Internally follows `nextToken` until the API signals exhaustion.
772
+ */
773
+ getAll = async (options = {}) => getAllPages((pageOptions) => this.list({ ...options, ...pageOptions }));
774
+ /**
775
+ * Find a project by its id across all pages.
776
+ *
777
+ * @returns The matching project, or `null` when no project has that id.
778
+ */
779
+ findById = async (projectId, options = {}) => {
780
+ const projects = await this.getAll(options);
781
+ return projects.find((project) => project.id === projectId) ?? null;
782
+ };
783
+ };
784
+
785
+ // src/modules/stepRuns/stepRuns.ts
786
+ var StepRunsModule = class {
787
+ #client;
788
+ constructor(client) {
789
+ this.#client = client;
790
+ }
791
+ /**
792
+ * Fetch full details for a single step run.
793
+ *
794
+ * @param stepRunId - UUID of the step run (from a `TestRun.stepsRuns` entry).
795
+ * @returns The step-run record including error code and timing.
796
+ * @throws {NotFoundError} When no step run matches `stepRunId`.
797
+ */
798
+ get = async (stepRunId, options = {}) => this.#client.get("/stepruns/{id}/", {
799
+ pathParams: { id: stepRunId },
800
+ ...options
801
+ });
802
+ };
803
+
804
+ // src/modules/steps/steps.ts
805
+ var StepsModule = class {
806
+ #client;
807
+ constructor(client) {
808
+ this.#client = client;
809
+ }
810
+ /**
811
+ * Fetch full details of a single step.
812
+ *
813
+ * @throws {NotFoundError} When no step matches `stepId`.
814
+ */
815
+ get = async (stepId, options = {}) => this.#client.get("/steps/{id}/", {
816
+ pathParams: { id: stepId },
817
+ ...options
818
+ });
819
+ /**
820
+ * Create a new step inside an existing group.
821
+ *
822
+ * @param data - Step payload; `type` discriminates the validation schema.
823
+ * @throws {ValidationError} On invalid payload (DRF field errors on `error.fields`).
824
+ */
825
+ create = async (data, options = {}) => this.#client.post("/steps/", {
826
+ body: data,
827
+ ...options
828
+ });
829
+ /**
830
+ * Fully replace a step via PUT. Use {@link partialUpdate} for PATCH semantics.
831
+ *
832
+ * @throws {NotFoundError} When the step does not exist.
833
+ * @throws {ValidationError} On invalid payload.
834
+ */
835
+ update = async (stepId, data, options = {}) => this.#client.put("/steps/{id}/", {
836
+ pathParams: { id: stepId },
837
+ body: data,
838
+ ...options
839
+ });
840
+ /**
841
+ * Patch selected fields on a step (PATCH semantics).
842
+ *
843
+ * @throws {NotFoundError} When the step does not exist.
844
+ * @throws {ValidationError} On invalid payload.
845
+ */
846
+ partialUpdate = async (stepId, data, options = {}) => this.#client.patch("/steps/{id}/", {
847
+ pathParams: { id: stepId },
848
+ body: data,
849
+ ...options
850
+ });
851
+ /**
852
+ * Delete a step by ID.
853
+ *
854
+ * @throws {NotFoundError} When the step does not exist.
855
+ */
856
+ delete = async (stepId, options = {}) => this.#client.delete("/steps/{id}/", {
857
+ pathParams: { id: stepId },
858
+ ...options
859
+ });
860
+ };
861
+
862
+ // src/types/sdk.ts
863
+ var isRunningStatus = (status) => ["initialized", "queued", "running", "auto_retrying"].includes(status ?? "");
864
+ var isEditingStatus = (status) => ["recording", "paused"].includes(status ?? "");
865
+ var isFailedStatus = (status) => ["failed", "error"].includes(status ?? "");
866
+ var isPassedStatus = (status) => ["passed"].includes(status ?? "");
867
+ var isCompletedStatus = (status) => ["passed", "failed", "error", "stopped"].includes(status ?? "");
868
+
869
+ // ../core/dist/utils/async.js
870
+ var sleep = (ms) => new Promise((resolve4) => {
871
+ setTimeout(resolve4, ms);
872
+ });
873
+
874
+ // src/utils/polling.ts
875
+ var pollRunUntilComplete = async (getRun, isComplete, options) => {
876
+ const { pollInterval = 2e3, timeout = options.defaultTimeout, onProgress } = options;
877
+ const endTime = Date.now() + timeout;
878
+ const sleepWithinTimeout = async () => {
879
+ const remainingMs = endTime - Date.now();
880
+ if (remainingMs <= 0) throw new TimeoutError(timeout, options.timeoutMessage(timeout));
881
+ await sleep(Math.min(pollInterval, remainingMs));
882
+ };
883
+ while (Date.now() < endTime) {
884
+ try {
885
+ const run = await getRun();
886
+ await onProgress?.(run);
887
+ if (isComplete(run)) {
888
+ return run;
889
+ }
890
+ await sleepWithinTimeout();
891
+ } catch (error) {
892
+ if (!isNetworkError(error)) {
893
+ throw error;
894
+ }
895
+ await sleepWithinTimeout();
896
+ }
897
+ }
898
+ throw new TimeoutError(timeout, options.timeoutMessage(timeout));
899
+ };
900
+
901
+ // src/modules/suites/suites.ts
902
+ var SuitesModule = class {
903
+ #client;
904
+ #profiles;
905
+ constructor(client) {
906
+ this.#client = client;
907
+ this.#profiles = new ProfilesModule(client);
908
+ }
909
+ /**
910
+ * List suites with pagination, search, and ordering.
911
+ *
912
+ * @param options.cursor - Opaque cursor returned as `nextToken` by the previous page.
913
+ * @param options.pageSize - Items per page.
914
+ * @param options.query - Search query matched against suite name.
915
+ * @param options.ordering - Sort key; prefix with `-` for descending.
916
+ * @returns Paginated `SuitesListResponse` envelope (validated via Zod).
917
+ * @throws {UnexpectedResponseError} On Zod validation failure.
918
+ */
919
+ list = async (options = {}) => {
920
+ const { cursor, pageSize, query: searchQuery, ordering, ...requestOptions } = options;
921
+ return this.#client.get("/suites/", {
922
+ queryParams: {
923
+ cursor,
924
+ page_size: pageSize,
925
+ query: searchQuery,
926
+ ordering
927
+ },
928
+ ...requestOptions
929
+ });
930
+ };
931
+ /**
932
+ * Fetch every suite across all pages.
933
+ */
934
+ getAll = async (options = {}) => getAllPages((pageOptions) => this.list(pageOptions), options);
935
+ /**
936
+ * Fetch a single suite by ID.
937
+ *
938
+ * @throws {NotFoundError} When no suite matches `suiteId`.
939
+ */
940
+ get = async (suiteId, options = {}) => this.#client.get("/suites/{id}/", {
941
+ pathParams: { id: suiteId },
942
+ ...options
943
+ });
944
+ /**
945
+ * Create a new suite.
946
+ *
947
+ * @throws {ValidationError} On invalid payload.
948
+ */
949
+ create = async (data, options = {}) => this.#client.post("/suites/", {
950
+ body: data,
951
+ ...options
952
+ });
953
+ /**
954
+ * Fully replace a suite via PUT (all fields required).
955
+ * Use {@link partialUpdate} for sparse PATCH semantics.
956
+ *
957
+ * @throws {NotFoundError} When the suite does not exist.
958
+ * @throws {ValidationError} On invalid payload.
959
+ */
960
+ update = async (suiteId, data, options = {}) => this.#client.put("/suites/{id}/", {
961
+ pathParams: { id: suiteId },
962
+ body: data,
963
+ ...options
964
+ });
965
+ /**
966
+ * Patch selected fields on a suite (PATCH semantics).
967
+ *
968
+ * @throws {NotFoundError} When the suite does not exist.
969
+ * @throws {ValidationError} On invalid payload.
970
+ */
971
+ partialUpdate = async (suiteId, data, options = {}) => this.#client.patch("/suites/{id}/", {
972
+ pathParams: { id: suiteId },
973
+ body: data,
974
+ ...options
975
+ });
976
+ /**
977
+ * Delete a suite by ID.
978
+ *
979
+ * @throws {NotFoundError} When the suite does not exist.
980
+ */
981
+ delete = async (suiteId, options = {}) => this.#client.delete("/suites/{id}/", {
982
+ pathParams: { id: suiteId },
983
+ ...options
984
+ });
985
+ async startRun(suiteId, options = {}) {
986
+ const {
987
+ watchProgress,
988
+ pollInterval,
989
+ timeout,
990
+ onProgress,
991
+ profileName,
992
+ variables,
993
+ ...runOptions
994
+ } = options;
995
+ const runProfileId = profileName ? await this.#profiles.resolveId(profileName, runOptions) : void 0;
996
+ const runResult = await this.#client.post("/suiteruns/", {
997
+ body: {
998
+ suiteId,
999
+ runProfileId,
1000
+ variables: variables ?? [],
1001
+ triggeredBy: this.#client.getConfig().triggeredBy
1002
+ },
1003
+ ...runOptions
1004
+ });
1005
+ if (watchProgress) {
1006
+ return this.watchRunProgress(runResult.id, onProgress ?? (() => {
1007
+ }), {
1008
+ pollInterval,
1009
+ timeout
1010
+ });
1011
+ }
1012
+ return runResult;
1013
+ }
1014
+ /**
1015
+ * Fetch full suite-run details including child test runs.
1016
+ *
1017
+ * @throws {NotFoundError} When the run does not exist.
1018
+ */
1019
+ getRun = async (runId, options = {}) => this.#client.get("/suiteruns/{id}/", {
1020
+ pathParams: { id: runId },
1021
+ ...options
1022
+ });
1023
+ /**
1024
+ * Get list of suite runs.
1025
+ *
1026
+ */
1027
+ listRuns = async (options = {}) => {
1028
+ const {
1029
+ cursor,
1030
+ pageSize,
1031
+ ordering,
1032
+ startedAfter,
1033
+ startedBefore,
1034
+ status,
1035
+ suiteId,
1036
+ ...requestOptions
1037
+ } = options;
1038
+ return this.#client.get("/suiteruns/", {
1039
+ queryParams: {
1040
+ cursor,
1041
+ page_size: pageSize,
1042
+ ordering,
1043
+ started_after: startedAfter,
1044
+ started_before: startedBefore,
1045
+ status,
1046
+ suiteId
1047
+ },
1048
+ ...requestOptions
1049
+ });
1050
+ };
1051
+ /**
1052
+ * Get recent suite runs within a time window
1053
+ */
1054
+ getRecentRuns = async (options = {}) => {
1055
+ const {
1056
+ hours = 24,
1057
+ pageSize = 50,
1058
+ ordering = "-started",
1059
+ suiteId,
1060
+ ...requestOptions
1061
+ } = options;
1062
+ const startedAfter = hoursAgo(hours);
1063
+ return this.listRuns({
1064
+ pageSize,
1065
+ ordering,
1066
+ startedAfter,
1067
+ suiteId,
1068
+ ...requestOptions
1069
+ });
1070
+ };
1071
+ /**
1072
+ * Fetch only the lightweight status of a suite run. Prefer this over
1073
+ * {@link getRun} when polling.
1074
+ */
1075
+ getRunProgress = async (runId, options = {}) => this.#client.get("/suiteruns/{id}/status/", {
1076
+ pathParams: { id: runId },
1077
+ ...options
1078
+ });
1079
+ /**
1080
+ * Request cancellation of an in-progress suite run.
1081
+ *
1082
+ * @returns The updated status; the run may still take a moment to settle.
1083
+ */
1084
+ stopRun = async (runId, options = {}) => this.#client.post("/suiteruns/{id}/stop/", {
1085
+ pathParams: { id: runId },
1086
+ ...options
1087
+ });
1088
+ /**
1089
+ * Fetch screenshots captured across all child test runs of a suite run.
1090
+ */
1091
+ getRunScreenshots = async (runId, options = {}) => {
1092
+ const screenshots = await this.#client.get(
1093
+ "/suiteruns/{id}/screenshots/",
1094
+ {
1095
+ pathParams: { id: runId },
1096
+ ...options
1097
+ }
1098
+ );
1099
+ return screenshots.testsRuns;
1100
+ };
1101
+ /**
1102
+ * Download the combined JUnit XML report for a suite run, aggregating all
1103
+ * child test runs.
1104
+ *
1105
+ * @returns The JUnit report body as a string.
1106
+ */
1107
+ downloadRunJunitReport = async (runId, options = {}) => this.#client.getText("/suiteruns/{id}/report/junit/", {
1108
+ pathParams: { id: runId },
1109
+ ...options
1110
+ });
1111
+ /**
1112
+ * List artifacts attached to a suite run. The current public API exposes the
1113
+ * combined JUnit report artifact here.
1114
+ */
1115
+ listRunDebugArtifacts = async (runId, options = {}) => {
1116
+ const { signal, timeout, headers } = options;
1117
+ return this.#client.get("/suiteruns/{id}/debug-artifacts/", {
1118
+ pathParams: { id: runId },
1119
+ signal,
1120
+ timeout,
1121
+ headers
1122
+ });
1123
+ };
1124
+ /**
1125
+ * Poll a suite run's lightweight `/status/` endpoint until it reaches a
1126
+ * terminal status, then return the full `SuiteRun` once.
1127
+ *
1128
+ * Transient network errors are retried; non-network errors propagate.
1129
+ *
1130
+ * @param runId - UUID of the suite run.
1131
+ * @param onProgress - Invoked on every successful poll with the latest `SuiteRunState`
1132
+ * (including the terminal one). Receives `finishedTests`/`totalTests`/`webappUrl`
1133
+ * for in-flight progress UI.
1134
+ * @param options.pollInterval - Poll interval in ms (default 2000).
1135
+ * @param options.timeout - Wait timeout in ms (default 600_000 = 10 minutes; suites typically take longer than single tests).
1136
+ * @throws {TimeoutError} When the run does not complete in time.
1137
+ */
1138
+ watchRunProgress = async (runId, onProgress, options = {}) => {
1139
+ const { pollInterval, timeout, ...requestOptions } = options;
1140
+ await pollRunUntilComplete(
1141
+ () => this.getRunProgress(runId, requestOptions),
1142
+ (status) => isCompletedStatus(status.status),
1143
+ {
1144
+ pollInterval,
1145
+ timeout,
1146
+ onProgress,
1147
+ defaultTimeout: 6e5,
1148
+ // 10 minutes (suites typically take longer)
1149
+ timeoutMessage: (t) => `Suite run ${runId} did not complete within ${t}ms`
1150
+ }
1151
+ );
1152
+ return this.getRun(runId, requestOptions);
1153
+ };
1154
+ };
1155
+
1156
+ // src/modules/tests/tests.utils.ts
1157
+ function buildTestImportFormData(payload, fmt) {
1158
+ const form = new FormData();
1159
+ const filename = fmt === "yaml" ? "test.yaml" : "test.zip";
1160
+ const mime = fmt === "yaml" ? "application/x-yaml" : "application/zip";
1161
+ const blob = typeof payload === "string" ? new Blob([payload], { type: mime }) : new Blob([payload], { type: mime });
1162
+ form.append("file", blob, filename);
1163
+ return form;
1164
+ }
1165
+
1166
+ // src/modules/tests/tests.ts
1167
+ var TestsModule = class {
1168
+ #client;
1169
+ #profiles;
1170
+ constructor(client) {
1171
+ this.#client = client;
1172
+ this.#profiles = new ProfilesModule(client);
1173
+ }
1174
+ /**
1175
+ * List tests with pagination, search, and ordering.
1176
+ *
1177
+ * @param options.cursor - Opaque cursor returned as `nextToken` by the previous page.
1178
+ * @param options.pageSize - Number of items per page.
1179
+ * @param options.query - Search query matched against test name.
1180
+ * @param options.ordering - Sort key; prefix with `-` for descending.
1181
+ * @returns Paginated `TestsListResponse` envelope (validated via Zod).
1182
+ * @throws {UnexpectedResponseError} When the server response fails Zod validation.
1183
+ */
1184
+ list = async (options = {}) => {
1185
+ const { cursor, pageSize, query: searchQuery, ordering, ...requestOptions } = options;
1186
+ return this.#client.get("/tests/", {
1187
+ queryParams: {
1188
+ cursor,
1189
+ page_size: pageSize,
1190
+ query: searchQuery,
1191
+ ordering
1192
+ },
1193
+ ...requestOptions
1194
+ });
1195
+ };
1196
+ /**
1197
+ * Fetch every test across all pages.
1198
+ */
1199
+ getAll = async (options = {}) => getAllPages((pageOptions) => this.list(pageOptions), options);
1200
+ /**
1201
+ * Fetch a single test by ID.
1202
+ *
1203
+ * @param testId - UUID of the test.
1204
+ * @returns The full `Test` resource.
1205
+ * @throws {NotFoundError} When no test matches `testId`.
1206
+ */
1207
+ get = async (testId, options = {}) => this.#client.get("/tests/{id}/", {
1208
+ pathParams: { id: testId },
1209
+ ...options
1210
+ });
1211
+ /**
1212
+ * Create a new empty test.
1213
+ *
1214
+ * @param data - Test creation payload (at minimum, `name` and `screenSizeType`).
1215
+ * @returns The newly-created `Test`.
1216
+ * @throws {ValidationError} On invalid payload (DRF field errors on `error.fields`).
1217
+ */
1218
+ create = async (data, options = {}) => this.#client.post("/tests/", {
1219
+ body: data,
1220
+ ...options
1221
+ });
1222
+ /**
1223
+ * Update a test. The tests endpoint accepts partial payloads via PATCH, so
1224
+ * this and {@link partialUpdate} share the same semantics.
1225
+ *
1226
+ * @param testId - UUID of the test to update.
1227
+ * @param data - Fields to patch.
1228
+ * @throws {NotFoundError} When the test does not exist.
1229
+ * @throws {ValidationError} When the payload fails server-side validation.
1230
+ */
1231
+ update = async (testId, data, options = {}) => this.#client.patch("/tests/{id}/", {
1232
+ pathParams: { id: testId },
1233
+ body: data,
1234
+ ...options
1235
+ });
1236
+ /** Alias for {@link update}. The tests endpoint already uses PATCH semantics. */
1237
+ partialUpdate = async (testId, data, options = {}) => this.update(testId, data, options);
1238
+ /**
1239
+ * Delete a test by ID.
1240
+ *
1241
+ * @throws {NotFoundError} When the test does not exist.
1242
+ */
1243
+ delete = async (testId, options = {}) => this.#client.delete("/tests/{id}/", {
1244
+ pathParams: { id: testId },
1245
+ ...options
1246
+ });
1247
+ /**
1248
+ * Link an existing component/group into a test at an optional position.
1249
+ *
1250
+ * @param testId - UUID of the test to insert into.
1251
+ * @param data - `{ groupId, atIndex? }` insertion descriptor.
1252
+ * @returns Insert response with the resulting step layout.
1253
+ */
1254
+ linkComponent = async (testId, data, options = {}) => this.#client.post("/tests/{id}/insert-component/", {
1255
+ pathParams: { id: testId },
1256
+ body: data,
1257
+ ...options
1258
+ });
1259
+ /**
1260
+ * Unlink a component/group from a test.
1261
+ *
1262
+ * @param testId - UUID of the test to unlink from.
1263
+ * @param data - Unlink descriptor identifying which component to remove.
1264
+ */
1265
+ unlinkComponent = async (testId, data, options = {}) => this.#client.post("/tests/{id}/unlink-component/", {
1266
+ pathParams: { id: testId },
1267
+ body: data,
1268
+ ...options
1269
+ });
1270
+ /**
1271
+ * Run a test by ID
1272
+ */
1273
+ #run = async (testId, options = {}) => {
1274
+ const { profileName, variables, ...requestOptions } = options;
1275
+ const runProfileId = profileName ? await this.#profiles.resolveId(profileName, requestOptions) : void 0;
1276
+ return this.#client.post("/testruns/", {
1277
+ body: {
1278
+ testId,
1279
+ runProfileId,
1280
+ variables: variables ?? [],
1281
+ triggeredBy: this.#client.getConfig().triggeredBy
1282
+ },
1283
+ ...requestOptions
1284
+ });
1285
+ };
1286
+ /**
1287
+ * Run a test by name (finds test by name first)
1288
+ */
1289
+ #runByName = async (testName, options = {}) => {
1290
+ const test = await this.#findByName(testName, options);
1291
+ if (!test) {
1292
+ throw new NotFoundError(`Test '${testName}' not found`);
1293
+ }
1294
+ return this.#run(test.id, options);
1295
+ };
1296
+ #findByName = async (testName, options = {}) => {
1297
+ let cursor;
1298
+ const pageSize = 100;
1299
+ while (true) {
1300
+ const response = await this.list({ ...options, query: testName, cursor, pageSize });
1301
+ const results = response.results ?? [];
1302
+ const exactMatch = results.find((testSummary) => testSummary.name === testName);
1303
+ if (exactMatch) return exactMatch;
1304
+ cursor = response.nextToken ?? void 0;
1305
+ if (!cursor) {
1306
+ return null;
1307
+ }
1308
+ }
1309
+ };
1310
+ async startRun(testNameOrId, options = {}) {
1311
+ const { watchProgress, pollInterval, timeout, onProgress, ...runOptions } = options;
1312
+ const callRun = isUuid(testNameOrId) ? this.#run : this.#runByName;
1313
+ const runResult = await callRun(testNameOrId, runOptions);
1314
+ if (!watchProgress) return runResult;
1315
+ return this.watchRunProgress(runResult.id, onProgress ?? (() => {
1316
+ }), {
1317
+ pollInterval,
1318
+ timeout
1319
+ });
1320
+ }
1321
+ /**
1322
+ * Fetch full test-run details including step runs.
1323
+ *
1324
+ * @param runId - UUID of the test run.
1325
+ * @throws {NotFoundError} When the run does not exist.
1326
+ */
1327
+ getRun = async (runId, options = {}) => this.#client.get("/testruns/{id}/", {
1328
+ pathParams: { id: runId },
1329
+ ...options
1330
+ });
1331
+ /**
1332
+ * Fetch only the lightweight status of a test run (no step detail).
1333
+ * Prefer this over {@link getRun} when polling.
1334
+ */
1335
+ getRunProgress = async (runId, options = {}) => this.#client.get("/testruns/{id}/status/", {
1336
+ pathParams: { id: runId },
1337
+ ...options
1338
+ });
1339
+ /**
1340
+ * Request cancellation of an in-progress test run.
1341
+ *
1342
+ * @param runId - UUID of the run to stop.
1343
+ * @returns The updated status; the run may still take a moment to settle.
1344
+ */
1345
+ stopRun = async (runId, options = {}) => this.#client.post("/testruns/{id}/stop/", {
1346
+ pathParams: { id: runId },
1347
+ ...options
1348
+ });
1349
+ /**
1350
+ * Fetch step-level screenshots captured during a run.
1351
+ *
1352
+ * @returns Per-step-run screenshot entries; empty array when none available.
1353
+ */
1354
+ getRunScreenshots = async (runId, options = {}) => {
1355
+ const screenshots = await this.#client.get(
1356
+ "/testruns/{id}/screenshots/",
1357
+ {
1358
+ pathParams: { id: runId },
1359
+ ...options
1360
+ }
1361
+ );
1362
+ return screenshots.stepsRuns || [];
1363
+ };
1364
+ /**
1365
+ * List test runs filtered by status, time window, and optional test ID.
1366
+ *
1367
+ * @param options.testId - When set, restricts to runs of that test (UUID).
1368
+ * @param options.startedAfter - ISO timestamp; only runs started after this point.
1369
+ * @param options.startedBefore - ISO timestamp; only runs started before this point.
1370
+ * @param options.status - Filter by `TestRunState['status']`.
1371
+ * @returns Paginated `TestRunsListResponse`.
1372
+ */
1373
+ listRuns = async (options = {}) => {
1374
+ const {
1375
+ cursor,
1376
+ pageSize,
1377
+ ordering,
1378
+ startedAfter,
1379
+ startedBefore,
1380
+ status,
1381
+ testId,
1382
+ ...requestOptions
1383
+ } = options;
1384
+ return this.#client.get("/testruns/", {
1385
+ queryParams: {
1386
+ cursor,
1387
+ page_size: pageSize,
1388
+ ordering,
1389
+ started_after: startedAfter,
1390
+ started_before: startedBefore,
1391
+ status,
1392
+ testId
1393
+ },
1394
+ ...requestOptions
1395
+ });
1396
+ };
1397
+ /**
1398
+ * Move a step to a new position within a test's step list.
1399
+ *
1400
+ * @param testId - UUID of the test to reorder.
1401
+ * @param data - `{ stepId, atIndex }` move descriptor.
1402
+ * @returns The updated `Test` with the new step ordering.
1403
+ */
1404
+ updateStepPosition = async (testId, data, options = {}) => this.#client.postNoContent("/tests/{id}/update-step-position/", {
1405
+ pathParams: { id: testId },
1406
+ body: data,
1407
+ ...options
1408
+ });
1409
+ /**
1410
+ * Convenience wrapper around {@link listRuns} that returns runs started in
1411
+ * the last N hours, newest first by default.
1412
+ *
1413
+ * @param options.hours - Look-back window in hours (default 24).
1414
+ * @param options.pageSize - Max runs per page (default 50).
1415
+ * @param options.ordering - Sort order; defaults to most-recent first.
1416
+ */
1417
+ getRecentRuns = async (options = {}) => {
1418
+ const { hours = 24, pageSize = 50, ordering = "-started", ...requestOptions } = options;
1419
+ const startedAfter = hoursAgo(hours);
1420
+ return this.listRuns({
1421
+ pageSize,
1422
+ ordering,
1423
+ startedAfter,
1424
+ ...requestOptions
1425
+ });
1426
+ };
1427
+ /**
1428
+ * Download the JUnit XML report for a test run. Useful for CI integrations
1429
+ * that consume JUnit (e.g. Jenkins, GitLab CI, GitHub Actions test reporters).
1430
+ *
1431
+ * @returns The JUnit report body as a string.
1432
+ */
1433
+ downloadRunJunitReport = async (runId, options = {}) => this.#client.getText("/testruns/{id}/report/junit/", {
1434
+ pathParams: { id: runId },
1435
+ ...options
1436
+ });
1437
+ /**
1438
+ * Fetch test-run log output. The server may return either log content
1439
+ * inline or a redirect/URL string — both are returned verbatim as text.
1440
+ */
1441
+ getRunLogs = async (runId, options = {}) => this.#client.getText("/testruns/{id}/logs/", {
1442
+ pathParams: { id: runId },
1443
+ ...options
1444
+ });
1445
+ /**
1446
+ * Poll a test run's lightweight `/status/` endpoint until it reaches a
1447
+ * terminal status.
1448
+ *
1449
+ * Transient network errors are retried; non-network errors propagate.
1450
+ *
1451
+ * @param runId - UUID of the test run to watch.
1452
+ * @param onProgress - Invoked on every successful poll with the latest `TestRunState`
1453
+ * (including the terminal one). Receives `finishedSteps`/`totalSteps`/`webappUrl`
1454
+ * for in-flight progress UI.
1455
+ * @param options.pollInterval - Poll interval in ms (default 2000).
1456
+ * @param options.timeout - Wait timeout in ms (default 300_000 = 5 minutes).
1457
+ * @throws {TimeoutError} When the run does not complete within the timeout.
1458
+ *
1459
+ * @example
1460
+ * ```typescript
1461
+ * const run = await sdk.tests.watchRunProgress(
1462
+ * runId,
1463
+ * (s) => console.log(`${s.status} ${s.finishedSteps}/${s.totalSteps}`),
1464
+ * { pollInterval: 3000, timeout: 600_000 },
1465
+ * );
1466
+ * ```
1467
+ */
1468
+ watchRunProgress = async (runId, onProgress, options = {}) => {
1469
+ const { pollInterval, timeout, ...requestOptions } = options;
1470
+ await pollRunUntilComplete(
1471
+ () => this.getRunProgress(runId, requestOptions),
1472
+ (status) => isCompletedStatus(status.status),
1473
+ {
1474
+ pollInterval,
1475
+ timeout,
1476
+ onProgress,
1477
+ defaultTimeout: 3e5,
1478
+ // 5 minutes
1479
+ timeoutMessage: (t) => `Test run ${runId} did not complete within ${t}ms`
1480
+ }
1481
+ );
1482
+ return this.getRun(runId, requestOptions);
1483
+ };
1484
+ /**
1485
+ * List debug artifacts (HARs, traces, etc.) produced by a test run.
1486
+ */
1487
+ listRunDebugArtifacts = async (runId, options = {}) => {
1488
+ const { signal, timeout, headers } = options;
1489
+ return this.#client.get("/testruns/{id}/debug-artifacts/", {
1490
+ pathParams: { id: runId },
1491
+ signal,
1492
+ timeout,
1493
+ headers
1494
+ });
1495
+ };
1496
+ /**
1497
+ * Fetch a single debug artifact (e.g. HAR, network trace) for a run.
1498
+ *
1499
+ * @param runId - UUID of the run.
1500
+ * @param artifactType - Artifact discriminator (e.g. `'har'`).
1501
+ */
1502
+ getRunDebugArtifact = async (runId, artifactType, options = {}) => this.#client.get("/testruns/{id}/debug-artifacts/{artifact_type}/", {
1503
+ pathParams: { id: runId, artifact_type: artifactType },
1504
+ ...options
1505
+ });
1506
+ export(testId, fmt, options = {}) {
1507
+ if (fmt === "yaml") {
1508
+ return this.#client.getText("/tests/{id}/export/{fmt}/", {
1509
+ pathParams: { id: testId, fmt },
1510
+ ...options
1511
+ });
1512
+ }
1513
+ return this.#client.getBinary("/tests/{id}/export/{fmt}/", {
1514
+ pathParams: { id: testId, fmt },
1515
+ ...options
1516
+ });
1517
+ }
1518
+ /**
1519
+ * Import a single test from YAML (string) or ZIP (Uint8Array). Returns the
1520
+ * created test summary.
1521
+ *
1522
+ * @param payload - YAML text or raw ZIP bytes.
1523
+ * @param fmt - Format matching `payload` (`'yaml'` or `'zip'`).
1524
+ * @param options.conflictMode - How to resolve naming collisions on the server
1525
+ * (e.g. `'rename'`). Forwarded as a query string parameter.
1526
+ * @throws {ValidationError} When the server rejects the payload.
1527
+ *
1528
+ * @example
1529
+ * ```typescript
1530
+ * const yaml = await readFile('test.yaml', 'utf8');
1531
+ * const test = await sdk.tests.import(yaml, 'yaml', { conflictMode: 'rename' });
1532
+ * ```
1533
+ */
1534
+ import = async (payload, fmt, options = {}) => {
1535
+ const { conflictMode, ...requestOptions } = options;
1536
+ const form = buildTestImportFormData(payload, fmt);
1537
+ return this.#client.postForm("/tests/import/", form, {
1538
+ ...requestOptions,
1539
+ queryParams: {
1540
+ conflictMode
1541
+ }
1542
+ });
1543
+ };
1544
+ };
1545
+
1546
+ // src/modules/variables/variables.ts
1547
+ var VariablesModule = class {
1548
+ #client;
1549
+ constructor(client) {
1550
+ this.#client = client;
1551
+ }
1552
+ list = async (options = {}) => this.#client.get("/variables/", options);
1553
+ get = async (key, options = {}) => this.#client.get("/variables/{key}/", {
1554
+ pathParams: { key },
1555
+ ...options
1556
+ });
1557
+ create = async (options) => {
1558
+ const { key, value, type, isSecret, description, ...requestOptions } = options;
1559
+ return this.#client.post("/variables/", {
1560
+ body: {
1561
+ key,
1562
+ value,
1563
+ type,
1564
+ isSecret,
1565
+ description
1566
+ },
1567
+ ...requestOptions
1568
+ });
1569
+ };
1570
+ update = async (key, options) => {
1571
+ const { value, type, isSecret, description, ...requestOptions } = options;
1572
+ return this.#client.patch("/variables/{key}/", {
1573
+ pathParams: { key },
1574
+ body: {
1575
+ value,
1576
+ type,
1577
+ isSecret,
1578
+ description
1579
+ },
1580
+ ...requestOptions
1581
+ });
1582
+ };
1583
+ };
1584
+
1585
+ // src/modules/visualRegression/visualRegression.ts
1586
+ var VisualRegressionModule = class {
1587
+ #client;
1588
+ constructor(client) {
1589
+ this.#client = client;
1590
+ }
1591
+ /**
1592
+ * List reference screenshots for a given step.
1593
+ *
1594
+ * @param options.stepId - UUID of the visual-regression step (required).
1595
+ * @param options.cursor - Opaque cursor returned as `nextToken` by the previous page.
1596
+ * @param options.pageSize - Items per page.
1597
+ */
1598
+ listReferenceScreenshots = async (options) => {
1599
+ const { stepId, cursor, pageSize, ...requestOptions } = options;
1600
+ return this.#client.get(
1601
+ "/visual-regression/reference-screenshots/",
1602
+ {
1603
+ queryParams: { step_id: stepId, cursor, page_size: pageSize },
1604
+ ...requestOptions
1605
+ }
1606
+ );
1607
+ };
1608
+ /**
1609
+ * Fetch a single reference screenshot.
1610
+ *
1611
+ * @throws {NotFoundError} When no reference screenshot matches `id`.
1612
+ */
1613
+ getReferenceScreenshot = async (id, options = {}) => this.#client.get(
1614
+ "/visual-regression/reference-screenshots/{id}/",
1615
+ { pathParams: { id }, ...options }
1616
+ );
1617
+ /**
1618
+ * Create a new reference screenshot for a visual-regression step.
1619
+ *
1620
+ * @throws {ValidationError} On invalid payload.
1621
+ */
1622
+ createReferenceScreenshot = async (data, options = {}) => this.#client.post("/visual-regression/reference-screenshots/", {
1623
+ body: data,
1624
+ ...options
1625
+ });
1626
+ /**
1627
+ * Patch a reference screenshot.
1628
+ *
1629
+ * @throws {NotFoundError} When the reference screenshot does not exist.
1630
+ * @throws {ValidationError} On invalid payload.
1631
+ */
1632
+ updateReferenceScreenshot = async (id, data, options = {}) => this.#client.patch(
1633
+ "/visual-regression/reference-screenshots/{id}/",
1634
+ { pathParams: { id }, body: data, ...options }
1635
+ );
1636
+ /**
1637
+ * Delete a reference screenshot.
1638
+ *
1639
+ * @throws {NotFoundError} When the reference screenshot does not exist.
1640
+ */
1641
+ deleteReferenceScreenshot = async (id, options = {}) => this.#client.delete("/visual-regression/reference-screenshots/{id}/", {
1642
+ pathParams: { id },
1643
+ ...options
1644
+ });
1645
+ /**
1646
+ * Resolve failed visual-regression checks.
1647
+ *
1648
+ * @param options.type - `set_new_reference` accepts the observed screenshot as the new
1649
+ * baseline; `set_new_max_difference` raises the step's allowed difference to just above
1650
+ * the observed value. The value is computed server-side and cannot be supplied.
1651
+ * @param options.stepRunId - Resolve a single step run.
1652
+ * @param options.testRunId - Resolve every failed visual-regression step in a test run.
1653
+ *
1654
+ * Provide exactly one of `stepRunId` or `testRunId`.
1655
+ */
1656
+ resolveReview = async (options) => {
1657
+ const { type, testRunId, stepRunId, ...requestOptions } = options;
1658
+ return this.#client.post("/visual-regression/resolve/", {
1659
+ body: {
1660
+ type,
1661
+ ...stepRunId !== void 0 ? { stepRunId } : {},
1662
+ ...testRunId !== void 0 ? { testRunId } : {}
1663
+ },
1664
+ ...requestOptions
1665
+ });
1666
+ };
1667
+ };
1668
+
1669
+ // ../core/dist/utils/envConfig.js
1670
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1671
+ import { dirname as dirname3, resolve as resolve3 } from "path";
1672
+ import { parse as parseDotenv } from "dotenv";
1673
+
1674
+ // ../core/dist/constants/defaults.js
1675
+ var DEFAULT_CONFIG_FILE_NAME = "bugbug.yaml";
1676
+ var DEFAULT_API_URL = "https://app.bugbug.io/api/v2";
1677
+ var DEFAULT_YAML_SCHEMA_URL = `${DEFAULT_API_URL}/schema/yaml/v1/`;
1678
+ var DEFAULT_MCP_URL = "https://mcp.bugbug.io";
1679
+ var DEFAULT_MCP_ACCESS_PATH = "/mcp";
1680
+ var DEFAULT_MCP_ACCESS_URL = `${DEFAULT_MCP_URL}${DEFAULT_MCP_ACCESS_PATH}`;
1681
+ var DOCS_BASE_URL = "https://docs.bugbug.io";
1682
+ var DOCS_ASK_URL = `${DOCS_BASE_URL}/master.md`;
1683
+ var CREDENTIALS_DIR_NAME = ".bugbug";
1684
+ var GLOBAL_CONFIG_FILE_NAME = "config.yaml";
1685
+ var DEFAULT_ROOT_CONFIG_FILE_NAME = `~/${CREDENTIALS_DIR_NAME}/${GLOBAL_CONFIG_FILE_NAME}`;
1686
+ var DEFAULT_ROOT_CONFIG_TEMP = `~/${CREDENTIALS_DIR_NAME}/tmp`;
1687
+
1688
+ // ../core/dist/utils/globalConfigFile.js
1689
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1690
+ import { homedir } from "os";
1691
+ import { dirname, resolve } from "path";
1692
+
1693
+ // ../core/dist/utils/yaml.js
1694
+ import { parse, stringify } from "yaml";
1695
+
1696
+ // ../core/dist/utils/globalConfigFile.js
1697
+ var globalConfigFilePath = (home = homedir()) => resolve(home, CREDENTIALS_DIR_NAME, GLOBAL_CONFIG_FILE_NAME);
1698
+ var readGlobalConfig = (home = homedir()) => {
1699
+ const path = globalConfigFilePath(home);
1700
+ if (!existsSync(path))
1701
+ return {};
1702
+ try {
1703
+ const parsed = parse(readFileSync(path, "utf8"));
1704
+ if (!parsed || typeof parsed !== "object")
1705
+ return {};
1706
+ return extractKnownFields(parsed);
1707
+ } catch {
1708
+ return {};
1709
+ }
1710
+ };
1711
+ var resolveTokenForProject = (projectId, home = homedir()) => readGlobalConfig(home).credentials?.[projectId]?.token;
1712
+ var getUserToken = (home = homedir()) => readGlobalConfig(home).userToken;
1713
+ var getCurrentProject = (home = homedir()) => readGlobalConfig(home).currentProject;
1714
+ var extractKnownFields = (obj) => {
1715
+ const out = {};
1716
+ if (obj.credentials && typeof obj.credentials === "object") {
1717
+ const creds = {};
1718
+ for (const [key, raw] of Object.entries(obj.credentials)) {
1719
+ const entry = toCredentialEntry(key, raw);
1720
+ if (entry)
1721
+ creds[key] = entry;
1722
+ }
1723
+ out.credentials = creds;
1724
+ }
1725
+ if (typeof obj.userToken === "string" && obj.userToken.length > 0)
1726
+ out.userToken = obj.userToken;
1727
+ if (typeof obj.currentProject === "string" && obj.currentProject.length > 0) {
1728
+ out.currentProject = obj.currentProject;
1729
+ }
1730
+ return out;
1731
+ };
1732
+ var toCredentialEntry = (key, raw) => {
1733
+ if (!raw || typeof raw !== "object")
1734
+ return void 0;
1735
+ const r = raw;
1736
+ const id = typeof r.id === "string" && r.id.length > 0 ? r.id : key;
1737
+ if (typeof r.token !== "string" || r.token.length === 0)
1738
+ return void 0;
1739
+ return { id, token: r.token };
1740
+ };
1741
+
1742
+ // ../core/dist/utils/localConfigFile.js
1743
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1744
+ import { dirname as dirname2, resolve as resolve2 } from "path";
1745
+
1746
+ // ../core/dist/utils/userCwd.js
1747
+ var userCwd = () => process.env.INIT_CWD ?? process.cwd();
1748
+
1749
+ // ../core/dist/utils/localConfigFile.js
1750
+ var localConfigFilePath = (cwd = userCwd()) => resolve2(cwd, DEFAULT_CONFIG_FILE_NAME);
1751
+ var findLocalConfigFile = (cwd = userCwd()) => {
1752
+ let dir = resolve2(cwd);
1753
+ while (true) {
1754
+ const candidate = localConfigFilePath(dir);
1755
+ if (existsSync2(candidate))
1756
+ return candidate;
1757
+ const parent = dirname2(dir);
1758
+ if (parent === dir)
1759
+ return void 0;
1760
+ dir = parent;
1761
+ }
1762
+ };
1763
+ var readLocalConfigFile = (cwd = userCwd()) => {
1764
+ const path = findLocalConfigFile(cwd);
1765
+ if (!path)
1766
+ return {};
1767
+ const raw = readFileSync2(path, "utf8");
1768
+ try {
1769
+ const parsed = parse(raw);
1770
+ if (!parsed || typeof parsed !== "object")
1771
+ return {};
1772
+ return extractKnownFields2(parsed);
1773
+ } catch (cause) {
1774
+ throw new Error(`Failed to parse ${DEFAULT_CONFIG_FILE_NAME} at ${path}: ${cause.message}`);
1775
+ }
1776
+ };
1777
+ var extractKnownFields2 = (obj) => {
1778
+ const out = {};
1779
+ if (typeof obj.projectId === "string")
1780
+ out.projectId = obj.projectId;
1781
+ if (typeof obj.token === "string")
1782
+ out.token = obj.token;
1783
+ if (typeof obj.apiUrl === "string")
1784
+ out.apiUrl = obj.apiUrl;
1785
+ if (typeof obj.telemetry === "boolean")
1786
+ out.telemetry = obj.telemetry;
1787
+ if (typeof obj.logLevel === "string")
1788
+ out.logLevel = obj.logLevel;
1789
+ return out;
1790
+ };
1791
+
1792
+ // ../core/dist/utils/envConfig.js
1793
+ var resolveEnvConfig = (opts = {}) => {
1794
+ const cwd = opts.cwd ?? userCwd();
1795
+ const overrides = opts.overrides ?? {};
1796
+ const localConfigFile = readLocalConfigFile(cwd);
1797
+ const envFile = readEnvFiles(cwd);
1798
+ const projectId = overrides.projectId ?? process.env.BUGBUG_PROJECT_ID ?? localConfigFile.projectId ?? envFile.projectId ?? getCurrentProject(opts.home);
1799
+ const globalToken = (projectId ? resolveTokenForProject(projectId, opts.home) : void 0) ?? getUserToken(opts.home);
1800
+ const logLevel = process.env.LOG_LEVEL ?? localConfigFile.logLevel ?? envFile.logLevel ?? "info";
1801
+ return {
1802
+ token: overrides.token ?? process.env.BUGBUG_API_TOKEN ?? localConfigFile.token ?? envFile.token ?? globalToken,
1803
+ apiUrl: overrides.apiUrl ?? process.env.BUGBUG_API_URL ?? localConfigFile.apiUrl ?? envFile.apiUrl ?? DEFAULT_API_URL,
1804
+ projectId,
1805
+ verbose: overrides.verbose ?? logLevel === "debug",
1806
+ nodeEnv: process.env.NODE_ENV ?? envFile.nodeEnv,
1807
+ sentryDsn: process.env.SENTRY_DSN ?? envFile.sentryDsn
1808
+ };
1809
+ };
1810
+ var readEnvFiles = (cwd) => {
1811
+ const currentNodeEnv = process.env.NODE_ENV;
1812
+ const fileNames = [".env"];
1813
+ if (currentNodeEnv)
1814
+ fileNames.push(`.env.${currentNodeEnv}`);
1815
+ let token;
1816
+ let apiUrl;
1817
+ let projectId;
1818
+ let logLevel;
1819
+ let nodeEnv;
1820
+ let sentryDsn;
1821
+ let dir = resolve3(cwd);
1822
+ while (true) {
1823
+ for (const name of fileNames) {
1824
+ const path = resolve3(dir, name);
1825
+ if (!existsSync3(path))
1826
+ continue;
1827
+ try {
1828
+ const parsed = parseDotenv(readFileSync3(path, "utf8"));
1829
+ token ??= typeof parsed.BUGBUG_API_TOKEN === "string" ? parsed.BUGBUG_API_TOKEN : void 0;
1830
+ apiUrl ??= typeof parsed.BUGBUG_API_URL === "string" ? parsed.BUGBUG_API_URL : void 0;
1831
+ projectId ??= typeof parsed.BUGBUG_PROJECT_ID === "string" ? parsed.BUGBUG_PROJECT_ID : void 0;
1832
+ logLevel ??= typeof parsed.BUGBUG_LOG_LEVEL === "string" ? parsed.BUGBUG_LOG_LEVEL : void 0;
1833
+ nodeEnv ??= typeof parsed.NODE_ENV === "string" ? parsed.NODE_ENV : void 0;
1834
+ sentryDsn ??= typeof parsed.SENTRY_DSN === "string" ? parsed.SENTRY_DSN : void 0;
1835
+ } catch {
1836
+ }
1837
+ }
1838
+ if (token && apiUrl && projectId)
1839
+ break;
1840
+ const parent = dirname3(dir);
1841
+ if (parent === dir)
1842
+ break;
1843
+ dir = parent;
1844
+ }
1845
+ return { token, apiUrl, projectId, logLevel, nodeEnv, sentryDsn };
1846
+ };
1847
+
1848
+ // ../core/dist/utils/userAgent.js
1849
+ var buildUserAgent = (clientType, version) => `BugBug ${clientType.toUpperCase()} ${version}`;
1850
+
1851
+ // ../core/package.json
1852
+ var package_default = {
1853
+ name: "@bugbug-io/core",
1854
+ version: "13.39.1",
1855
+ private: true,
1856
+ type: "module",
1857
+ main: "dist/index.js",
1858
+ types: "dist/index.d.ts",
1859
+ exports: {
1860
+ ".": {
1861
+ development: "./src/index.ts",
1862
+ import: "./dist/index.js",
1863
+ types: "./dist/index.d.ts"
1864
+ },
1865
+ "./constants/defaults": {
1866
+ development: "./src/constants/defaults.ts",
1867
+ import: "./dist/constants/defaults.js",
1868
+ types: "./dist/constants/defaults.d.ts"
1869
+ },
1870
+ "./constants/status": {
1871
+ development: "./src/constants/status.ts",
1872
+ import: "./dist/constants/status.js",
1873
+ types: "./dist/constants/status.d.ts"
1874
+ },
1875
+ "./utils/async": {
1876
+ development: "./src/utils/async.ts",
1877
+ import: "./dist/utils/async.js",
1878
+ types: "./dist/utils/async.d.ts"
1879
+ },
1880
+ "./utils/collections": {
1881
+ development: "./src/utils/collections.ts",
1882
+ import: "./dist/utils/collections.js",
1883
+ types: "./dist/utils/collections.d.ts"
1884
+ },
1885
+ "./utils/localConfigFile": {
1886
+ development: "./src/utils/localConfigFile.ts",
1887
+ import: "./dist/utils/localConfigFile.js",
1888
+ types: "./dist/utils/localConfigFile.d.ts"
1889
+ },
1890
+ "./utils/env": {
1891
+ development: "./src/utils/env.ts",
1892
+ import: "./dist/utils/env.js",
1893
+ types: "./dist/utils/env.d.ts"
1894
+ },
1895
+ "./utils/envConfig": {
1896
+ development: "./src/utils/envConfig.ts",
1897
+ import: "./dist/utils/envConfig.js",
1898
+ types: "./dist/utils/envConfig.d.ts"
1899
+ },
1900
+ "./utils/globalConfigFile": {
1901
+ development: "./src/utils/globalConfigFile.ts",
1902
+ import: "./dist/utils/globalConfigFile.js",
1903
+ types: "./dist/utils/globalConfigFile.d.ts"
1904
+ },
1905
+ "./utils/duration": {
1906
+ development: "./src/utils/duration.ts",
1907
+ import: "./dist/utils/duration.js",
1908
+ types: "./dist/utils/duration.d.ts"
1909
+ },
1910
+ "./utils/logger": {
1911
+ development: "./src/utils/logger.ts",
1912
+ import: "./dist/utils/logger.js",
1913
+ types: "./dist/utils/logger.d.ts"
1914
+ },
1915
+ "./utils/strings": {
1916
+ development: "./src/utils/strings.ts",
1917
+ import: "./dist/utils/strings.js",
1918
+ types: "./dist/utils/strings.d.ts"
1919
+ },
1920
+ "./utils/time": {
1921
+ development: "./src/utils/time.ts",
1922
+ import: "./dist/utils/time.js",
1923
+ types: "./dist/utils/time.d.ts"
1924
+ },
1925
+ "./utils/sentry": {
1926
+ development: "./src/utils/sentry.ts",
1927
+ import: "./dist/utils/sentry.js",
1928
+ types: "./dist/utils/sentry.d.ts"
1929
+ },
1930
+ "./utils/userAgent": {
1931
+ development: "./src/utils/userAgent.ts",
1932
+ import: "./dist/utils/userAgent.js",
1933
+ types: "./dist/utils/userAgent.d.ts"
1934
+ },
1935
+ "./utils/userCwd": {
1936
+ development: "./src/utils/userCwd.ts",
1937
+ import: "./dist/utils/userCwd.js",
1938
+ types: "./dist/utils/userCwd.d.ts"
1939
+ },
1940
+ "./utils/version": {
1941
+ development: "./src/utils/version.ts",
1942
+ import: "./dist/utils/version.js",
1943
+ types: "./dist/utils/version.d.ts"
1944
+ },
1945
+ "./utils/validation": {
1946
+ development: "./src/utils/validation.ts",
1947
+ import: "./dist/utils/validation.js",
1948
+ types: "./dist/utils/validation.d.ts"
1949
+ },
1950
+ "./utils/variables": {
1951
+ development: "./src/utils/variables.ts",
1952
+ import: "./dist/utils/variables.js",
1953
+ types: "./dist/utils/variables.d.ts"
1954
+ },
1955
+ "./utils/yaml": {
1956
+ development: "./src/utils/yaml.ts",
1957
+ import: "./dist/utils/yaml.js",
1958
+ types: "./dist/utils/yaml.d.ts"
1959
+ },
1960
+ "./services/telemetry": {
1961
+ development: "./src/services/telemetry/index.ts",
1962
+ import: "./dist/services/telemetry/index.js",
1963
+ types: "./dist/services/telemetry/index.d.ts"
1964
+ },
1965
+ "./services/telemetry/analytics": {
1966
+ development: "./src/services/telemetry/analytics/index.ts",
1967
+ import: "./dist/services/telemetry/analytics/index.js",
1968
+ types: "./dist/services/telemetry/analytics/index.d.ts"
1969
+ },
1970
+ "./services/telemetry/sentry": {
1971
+ development: "./src/services/telemetry/sentry.ts",
1972
+ import: "./dist/services/telemetry/sentry.js",
1973
+ types: "./dist/services/telemetry/sentry.d.ts"
1974
+ },
1975
+ "./install/install.types": {
1976
+ development: "./src/install/install.types.ts",
1977
+ import: "./dist/install/install.types.js",
1978
+ types: "./dist/install/install.types.d.ts"
1979
+ },
1980
+ "./install/clients": {
1981
+ development: "./src/install/clients.ts",
1982
+ import: "./dist/install/clients.js",
1983
+ types: "./dist/install/clients.d.ts"
1984
+ },
1985
+ "./install/installPlugin": {
1986
+ development: "./src/install/installPlugin.ts",
1987
+ import: "./dist/install/installPlugin.js",
1988
+ types: "./dist/install/installPlugin.d.ts"
1989
+ },
1990
+ "./install/detect": {
1991
+ development: "./src/install/detect.ts",
1992
+ import: "./dist/install/detect.js",
1993
+ types: "./dist/install/detect.d.ts"
1994
+ },
1995
+ "./install/installContext": {
1996
+ development: "./src/install/installContext.ts",
1997
+ import: "./dist/install/installContext.js",
1998
+ types: "./dist/install/installContext.d.ts"
1999
+ },
2000
+ "./install/external": {
2001
+ development: "./src/install/external.ts",
2002
+ import: "./dist/install/external.js",
2003
+ types: "./dist/install/external.d.ts"
2004
+ },
2005
+ "./install/jsonConfig": {
2006
+ development: "./src/install/jsonConfig.ts",
2007
+ import: "./dist/install/jsonConfig.js",
2008
+ types: "./dist/install/jsonConfig.d.ts"
2009
+ },
2010
+ "./install/writers": {
2011
+ development: "./src/install/writers.ts",
2012
+ import: "./dist/install/writers.js",
2013
+ types: "./dist/install/writers.d.ts"
2014
+ },
2015
+ "./install/skills": {
2016
+ development: "./src/install/skills.ts",
2017
+ import: "./dist/install/skills.js",
2018
+ types: "./dist/install/skills.d.ts"
2019
+ },
2020
+ "./install/pluginRef": {
2021
+ development: "./src/install/pluginRef.ts",
2022
+ import: "./dist/install/pluginRef.js",
2023
+ types: "./dist/install/pluginRef.d.ts"
2024
+ },
2025
+ "./install/pluginsCli": {
2026
+ development: "./src/install/pluginsCli.ts",
2027
+ import: "./dist/install/pluginsCli.js",
2028
+ types: "./dist/install/pluginsCli.d.ts"
2029
+ },
2030
+ "./install/localCatalog": {
2031
+ development: "./src/install/localCatalog.ts",
2032
+ import: "./dist/install/localCatalog.js",
2033
+ types: "./dist/install/localCatalog.d.ts"
2034
+ },
2035
+ "./testUtils/mockRequest": {
2036
+ development: "./src/testUtils/mockRequest.ts",
2037
+ import: "./dist/testUtils/mockRequest.js",
2038
+ types: "./dist/testUtils/mockRequest.d.ts"
2039
+ },
2040
+ "./package.json": "./package.json"
2041
+ },
2042
+ files: [
2043
+ "dist/**/*"
2044
+ ],
2045
+ scripts: {
2046
+ build: "tsc -b --force",
2047
+ dev: "tsc -b --watch",
2048
+ test: "vitest run",
2049
+ "test:watch": "vitest --watch",
2050
+ "format:check": "oxfmt --check",
2051
+ "oxlint:check": "oxlint .",
2052
+ "oxlint:ci": "oxlint . --max-warnings=0",
2053
+ lint: "npm run format:check && npm run oxlint:check",
2054
+ "lint:fix": "oxfmt && oxlint . --fix",
2055
+ "lint:ci": "npm run format:check && npm run oxlint:ci",
2056
+ typecheck: "tsc --noEmit",
2057
+ clean: "rm -rf dist node_modules/.tmp/tsconfig.tsbuildinfo"
2058
+ },
2059
+ dependencies: {
2060
+ "@sentry/node": "^10.66.0",
2061
+ dotenv: "^16.6.1",
2062
+ yaml: "^2.8.4"
2063
+ },
2064
+ devDependencies: {
2065
+ "@bugbug-io/config": "*",
2066
+ "@bugbug-io/oxlint-config": "*",
2067
+ nock: "^14.0.9"
2068
+ },
2069
+ engines: {
2070
+ node: ">=24"
2071
+ }
2072
+ };
2073
+
2074
+ // ../core/dist/utils/version.js
2075
+ var getVersion = () => package_default.version;
2076
+
2077
+ // src/services/apiClient/apiClient.ts
2078
+ import createClient from "openapi-fetch";
2079
+
2080
+ // src/services/rateLimiter/rateLimiter.constants.ts
2081
+ var RATE_LIMIT_MIN_REQUESTS = 1;
2082
+ var RATE_LIMIT_MAX_REQUESTS = 100;
2083
+ var RATE_LIMIT_MIN_WINDOW_MS = 1e3;
2084
+ var RATE_LIMIT_MAX_WINDOW_MS = 6e4;
2085
+
2086
+ // src/services/rateLimiter/rateLimiter.ts
2087
+ var RateLimiter = class {
2088
+ #requests = [];
2089
+ #config;
2090
+ constructor(config) {
2091
+ this.#warnIfUnsupportedConfig(config);
2092
+ this.#config = {
2093
+ maxRequests: config.maxRequests,
2094
+ windowMs: config.windowMs,
2095
+ retryAfter: config.retryAfter || 1e3,
2096
+ retryServer429: config.retryServer429 ?? true,
2097
+ exponentialBackoff: {
2098
+ baseDelay: config.exponentialBackoff.baseDelay,
2099
+ maxDelay: config.exponentialBackoff.maxDelay,
2100
+ maxRetries: config.exponentialBackoff.maxRetries
2101
+ }
2102
+ };
2103
+ }
2104
+ #warnIfUnsupportedConfig(config) {
2105
+ if (config.maxRequests < RATE_LIMIT_MIN_REQUESTS || config.maxRequests > RATE_LIMIT_MAX_REQUESTS) {
2106
+ console.warn(
2107
+ `BugBug SDK rateLimit.maxRequests should be between ${RATE_LIMIT_MIN_REQUESTS} and ${RATE_LIMIT_MAX_REQUESTS}.`
2108
+ );
2109
+ }
2110
+ if (config.windowMs < RATE_LIMIT_MIN_WINDOW_MS || config.windowMs > RATE_LIMIT_MAX_WINDOW_MS) {
2111
+ console.warn(
2112
+ `BugBug SDK rateLimit.windowMs should be between ${RATE_LIMIT_MIN_WINDOW_MS} and ${RATE_LIMIT_MAX_WINDOW_MS}.`
2113
+ );
2114
+ }
2115
+ }
2116
+ /**
2117
+ * Execute a function with rate limiting and exponential backoff.
2118
+ *
2119
+ * The retry loop fires when:
2120
+ * 1. The **client-side** gate trips (`canMakeRequest()` returns false) — `fn`
2121
+ * was never called.
2122
+ * 2. The function throws `RateLimitError` (typically a server 429) AND
2123
+ * `retryServer429` is enabled. Retrying server 429s on non-idempotent
2124
+ * requests (POST startRun, POST startRun) can produce duplicate
2125
+ * resources — set `retryServer429: false` to disable.
2126
+ *
2127
+ * @param fn - The function to execute under rate limiting.
2128
+ * @returns The function's resolved value.
2129
+ * @throws {RateLimitError} When retries are exhausted or `retryServer429` is
2130
+ * disabled and the server returned 429.
2131
+ */
2132
+ async execute(fn) {
2133
+ let lastError = null;
2134
+ for (let attempt = 0; attempt <= this.#config.exponentialBackoff.maxRetries; attempt++) {
2135
+ if (!this.#canMakeRequest()) {
2136
+ const waitTime = this.#getWaitTime();
2137
+ const gateError = new RateLimitError(
2138
+ waitTime,
2139
+ `Rate limit exceeded. Wait ${waitTime}ms before retrying.`
2140
+ );
2141
+ lastError = gateError;
2142
+ if (attempt === this.#config.exponentialBackoff.maxRetries) {
2143
+ throw gateError;
2144
+ }
2145
+ await this.#sleep(this.#computeBackoff(attempt, gateError.retryAfter));
2146
+ continue;
2147
+ }
2148
+ const stamp = this.#recordRequest();
2149
+ try {
2150
+ return await fn();
2151
+ } catch (fnError) {
2152
+ this.#releaseRequest(stamp);
2153
+ if (!(fnError instanceof RateLimitError)) {
2154
+ throw fnError;
2155
+ }
2156
+ lastError = fnError;
2157
+ if (!this.#config.retryServer429) {
2158
+ throw fnError;
2159
+ }
2160
+ if (attempt === this.#config.exponentialBackoff.maxRetries) {
2161
+ throw fnError;
2162
+ }
2163
+ await this.#sleep(this.#computeBackoff(attempt, fnError.retryAfter));
2164
+ }
2165
+ }
2166
+ throw lastError || new Error("Max retries exceeded");
2167
+ }
2168
+ #computeBackoff = (attempt, retryAfter) => {
2169
+ const baseDelay = retryAfter || this.#config.exponentialBackoff.baseDelay;
2170
+ const delay = Math.min(
2171
+ baseDelay * Math.pow(2, attempt),
2172
+ this.#config.exponentialBackoff.maxDelay
2173
+ );
2174
+ const jitter = Math.random() * 0.1 * delay;
2175
+ return delay + jitter;
2176
+ };
2177
+ /**
2178
+ * Sleep utility
2179
+ */
2180
+ #sleep(ms) {
2181
+ return new Promise((resolve4) => {
2182
+ setTimeout(resolve4, ms);
2183
+ });
2184
+ }
2185
+ /**
2186
+ * Check if we can make a request within the rate limit
2187
+ */
2188
+ #canMakeRequest() {
2189
+ this.#cleanOldRequests();
2190
+ return this.#requests.length < this.#config.maxRequests;
2191
+ }
2192
+ /**
2193
+ * Record a new request. Returns the timestamp pushed so callers can later
2194
+ * release **this specific** stamp via {@link releaseRequest}, avoiding the
2195
+ * pop()-the-wrong-stamp race when multiple `execute()` calls overlap.
2196
+ */
2197
+ #recordRequest() {
2198
+ const stamp = Date.now();
2199
+ this.#requests.push(stamp);
2200
+ return stamp;
2201
+ }
2202
+ /**
2203
+ * Remove a previously-recorded stamp by identity (first occurrence). Safe to
2204
+ * call when the stamp has already been cleaned by the window-trim sweep —
2205
+ * the lookup simply finds no match and the array is left untouched.
2206
+ */
2207
+ #releaseRequest = (stamp) => {
2208
+ const index = this.#requests.indexOf(stamp);
2209
+ if (index !== -1) {
2210
+ this.#requests.splice(index, 1);
2211
+ }
2212
+ };
2213
+ /**
2214
+ * Clean up old requests outside the time window
2215
+ */
2216
+ #cleanOldRequests() {
2217
+ const now2 = Date.now();
2218
+ const cutoff = now2 - this.#config.windowMs;
2219
+ this.#requests = this.#requests.filter((timestamp) => timestamp > cutoff);
2220
+ }
2221
+ /**
2222
+ * Get the time to wait before the next request can be made
2223
+ */
2224
+ #getWaitTime() {
2225
+ if (this.#requests.length === 0) {
2226
+ return 0;
2227
+ }
2228
+ const oldestRequest = Math.min(...this.#requests);
2229
+ const waitTime = this.#config.windowMs - (Date.now() - oldestRequest);
2230
+ return Math.max(0, waitTime);
2231
+ }
2232
+ /**
2233
+ * Snapshot of the current rate-limiter state.
2234
+ *
2235
+ * @returns `requestsInWindow`, `remainingRequests`, `canMakeRequest`,
2236
+ * `resetTime` (epoch ms when the oldest tracked request leaves the
2237
+ * window — `null` when no requests are tracked).
2238
+ */
2239
+ getStatus() {
2240
+ this.#cleanOldRequests();
2241
+ return {
2242
+ requestsInWindow: this.#requests.length,
2243
+ maxRequests: this.#config.maxRequests,
2244
+ remainingRequests: Math.max(0, this.#config.maxRequests - this.#requests.length),
2245
+ windowMs: this.#config.windowMs,
2246
+ resetTime: this.#requests.length > 0 ? Math.min(...this.#requests) + this.#config.windowMs : null,
2247
+ canMakeRequest: this.#canMakeRequest()
2248
+ };
2249
+ }
2250
+ /**
2251
+ * Discard all tracked request timestamps. Useful in tests or after a clock
2252
+ * jump. Not normally called in production.
2253
+ */
2254
+ reset() {
2255
+ this.#requests = [];
2256
+ }
2257
+ };
2258
+
2259
+ // src/services/apiClient/apiClient.utils.ts
2260
+ function formatApiErrorMessage(error) {
2261
+ if (isHtmlErrorBody(error)) return "";
2262
+ return formatApiErrorValue(error) ?? "";
2263
+ }
2264
+ function isHtmlErrorBody(error) {
2265
+ if (typeof error !== "string") return false;
2266
+ const trimmed = error.trimStart().toLowerCase();
2267
+ return trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html");
2268
+ }
2269
+ function formatNetworkErrorMessage(error) {
2270
+ const { cause } = error;
2271
+ const causeMessage = formatNetworkErrorCause(cause);
2272
+ return causeMessage ? `Network error: ${error.message} (${causeMessage})` : `Network error: ${error.message}`;
2273
+ }
2274
+ function formatUnexpectedResponseMessage(error, method, url) {
2275
+ return `Unexpected non-JSON response from ${method} ${url}: ${error.message}. Check BUGBUG_API_URL; this usually means the server returned an HTML page instead of the BugBug API.`;
2276
+ }
2277
+ function formatNetworkErrorCause(cause) {
2278
+ if (!cause) return void 0;
2279
+ if (cause instanceof Error && cause.message) return cause.message;
2280
+ if (typeof cause === "string") return cause;
2281
+ if (typeof cause !== "object") return String(cause);
2282
+ const obj = cause;
2283
+ const code = typeof obj.code === "string" ? obj.code : void 0;
2284
+ const address = typeof obj.address === "string" ? obj.address : void 0;
2285
+ const port = typeof obj.port === "number" || typeof obj.port === "string" ? String(obj.port) : void 0;
2286
+ if (code && address && port) return `${code} ${address}:${port}`;
2287
+ if (code && address) return `${code} ${address}`;
2288
+ if (code) return code;
2289
+ try {
2290
+ return JSON.stringify(obj);
2291
+ } catch {
2292
+ return String(cause);
2293
+ }
2294
+ }
2295
+ function parseErrorBody(error) {
2296
+ if (typeof error !== "string") return error;
2297
+ const trimmed = error.trim();
2298
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return void 0;
2299
+ try {
2300
+ return JSON.parse(trimmed);
2301
+ } catch {
2302
+ return void 0;
2303
+ }
2304
+ }
2305
+ function extractFieldErrors(body) {
2306
+ if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
2307
+ const out = {};
2308
+ const walk = (value, path) => {
2309
+ if (value === null || value === void 0) return;
2310
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2311
+ if (path) (out[path] ??= []).push(String(value));
2312
+ return;
2313
+ }
2314
+ if (Array.isArray(value)) {
2315
+ const stringEntries = value.filter(
2316
+ (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean"
2317
+ );
2318
+ if (stringEntries.length === value.length && path) {
2319
+ out[path] = [...out[path] ?? [], ...stringEntries.map(String)];
2320
+ return;
2321
+ }
2322
+ value.forEach((item, index) => walk(item, path ? `${path}[${index}]` : `[${index}]`));
2323
+ return;
2324
+ }
2325
+ if (typeof value === "object") {
2326
+ for (const [key, nested] of Object.entries(value)) {
2327
+ if (!path && (key === "detail" || key === "message" || key === "error")) continue;
2328
+ walk(nested, path ? `${path}.${key}` : key);
2329
+ }
2330
+ }
2331
+ };
2332
+ walk(body, "");
2333
+ return Object.keys(out).length > 0 ? out : void 0;
2334
+ }
2335
+ function formatApiErrorValue(value, path = "") {
2336
+ if (typeof value === "string") return value;
2337
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2338
+ if (Array.isArray(value)) {
2339
+ const parts2 = value.map((item) => formatApiErrorValue(item, path)).filter((item) => Boolean(item));
2340
+ return parts2.length ? parts2.join(", ") : void 0;
2341
+ }
2342
+ if (!value || typeof value !== "object") return void 0;
2343
+ const obj = value;
2344
+ for (const key of ["message", "detail", "error"]) {
2345
+ const message = formatApiErrorValue(obj[key], path);
2346
+ if (message) return message;
2347
+ }
2348
+ const parts = Object.entries(obj).map(([key, nested]) => {
2349
+ const nestedPath = path ? `${path}.${key}` : key;
2350
+ const formatted = formatApiErrorValue(nested, nestedPath);
2351
+ if (!formatted) return void 0;
2352
+ return formatted.includes(":") ? formatted : `${nestedPath}: ${formatted}`;
2353
+ }).filter((item) => Boolean(item));
2354
+ return parts.length ? parts.join("; ") : void 0;
2355
+ }
2356
+ function parseJsonOrText(text) {
2357
+ if (!text) return void 0;
2358
+ try {
2359
+ return JSON.parse(text);
2360
+ } catch {
2361
+ return text;
2362
+ }
2363
+ }
2364
+
2365
+ // src/services/apiClient/apiClient.ts
2366
+ var BugBugApiClient = class {
2367
+ #config;
2368
+ #client;
2369
+ #rateLimiter;
2370
+ constructor(config) {
2371
+ const resolved = resolveEnvConfig({
2372
+ overrides: { token: config.apiToken, apiUrl: config.apiUrl, projectId: config.projectId }
2373
+ });
2374
+ this.#config = {
2375
+ apiToken: resolved.token ?? "",
2376
+ projectId: resolved.projectId ?? "",
2377
+ triggeredBy: config.triggeredBy || "api",
2378
+ apiUrl: this.#normalizeApiUrl(resolved.apiUrl),
2379
+ verbose: config.verbose || false,
2380
+ logLevel: config.logLevel || "info",
2381
+ timeout: config.timeout || 3e4,
2382
+ rateLimit: config.rateLimit || {
2383
+ maxRequests: 100,
2384
+ windowMs: 6e4,
2385
+ exponentialBackoff: {
2386
+ baseDelay: 1e3,
2387
+ maxDelay: 3e4,
2388
+ maxRetries: 3
2389
+ }
2390
+ },
2391
+ requestHeaders: {
2392
+ "User-Agent": buildUserAgent("sdk", getVersion()),
2393
+ ...config.requestHeaders
2394
+ },
2395
+ onBeforeRequest: config.onBeforeRequest || (() => {
2396
+ }),
2397
+ onAfterRequest: config.onAfterRequest || (() => {
2398
+ }),
2399
+ onRequestError: config.onRequestError || (() => {
2400
+ })
2401
+ };
2402
+ this.#validateConfig();
2403
+ this.#client = this.#createApiClient();
2404
+ this.#rateLimiter = new RateLimiter(this.#config.rateLimit);
2405
+ }
2406
+ #createApiClient = () => createClient({
2407
+ baseUrl: this.#config.apiUrl,
2408
+ headers: {
2409
+ ...this.#identityHeaders(),
2410
+ "Content-Type": "application/json"
2411
+ }
2412
+ });
2413
+ /** Auth + identification headers shared by the openapi client and raw fetch paths. */
2414
+ #identityHeaders = () => ({
2415
+ ...this.#config.requestHeaders,
2416
+ Authorization: `Token ${this.#config.apiToken}`
2417
+ });
2418
+ /**
2419
+ * Project-scoping query param for organization/user tokens. Project tokens don't
2420
+ * need it (their project is implied), so it's only sent when a project id is
2421
+ * configured. The backend public API reads `projectId` from query params,
2422
+ * request body, or URL kwargs.
2423
+ */
2424
+ #scopedParams = (params, options = {}) => {
2425
+ const { projectScoped = true } = options;
2426
+ if (!projectScoped || !this.#config.projectId) return params;
2427
+ const query = params?.query ?? {};
2428
+ if (query.projectId !== void 0 || query.project_id !== void 0) return params;
2429
+ return {
2430
+ ...params?.path ? { path: params.path } : {},
2431
+ query: {
2432
+ ...query,
2433
+ projectId: this.#config.projectId
2434
+ }
2435
+ };
2436
+ };
2437
+ /**
2438
+ * Detect the "project id required" 400 the backend returns when an
2439
+ * organization/user token hits a project-scoped endpoint without a project.
2440
+ *
2441
+ * STUB: keyed off message/field text until the backend exposes a stable
2442
+ * error code for this case.
2443
+ */
2444
+ #isMissingProjectId = (message, fields) => {
2445
+ const haystacks = [
2446
+ message,
2447
+ ...Object.keys(fields ?? {}),
2448
+ ...Object.values(fields ?? {}).flat()
2449
+ ];
2450
+ return haystacks.some((value) => /project[\s_-]*id/i.test(value) && /requir/i.test(value)) || Object.keys(fields ?? {}).some((key) => /^project[\s_-]*id$/i.test(key));
2451
+ };
2452
+ /**
2453
+ * Validate the configuration
2454
+ */
2455
+ #validateConfig = () => {
2456
+ if (!this.#config.apiToken) {
2457
+ throw new ConfigurationError("API token is required");
2458
+ }
2459
+ if (!this.#config.apiUrl) {
2460
+ throw new ConfigurationError("API URL is required");
2461
+ }
2462
+ if (!URL.canParse(this.#config.apiUrl)) {
2463
+ throw new ConfigurationError("Invalid API URL format");
2464
+ }
2465
+ };
2466
+ /**
2467
+ * Make an HTTP request with rate limiting using openapi-fetch
2468
+ */
2469
+ #makeRequest = async (method, path, options = {}) => {
2470
+ const { body, params, signal, timeout, headers: customHeaders, projectScoped = true } = options;
2471
+ const scopedParams = this.#scopedParams(params, { projectScoped });
2472
+ const requestUrl = this.#buildRequestUrl(path, scopedParams);
2473
+ const requestOrigin = { method, url: requestUrl };
2474
+ const requestData = {
2475
+ method,
2476
+ path,
2477
+ body,
2478
+ params: scopedParams,
2479
+ signal,
2480
+ timeout,
2481
+ headers: customHeaders
2482
+ };
2483
+ this.#config.onBeforeRequest(requestData);
2484
+ return this.#rateLimiter.execute(async () => {
2485
+ const abortContext = this.#createAbortContext(signal, timeout);
2486
+ try {
2487
+ if (this.#config.verbose) {
2488
+ this.#log("debug", `\u2192 ${method} ${path}`, { params, body });
2489
+ }
2490
+ const requestOptions = {
2491
+ signal: abortContext.signal,
2492
+ headers: {
2493
+ ...customHeaders,
2494
+ ...this.#identityHeaders()
2495
+ }
2496
+ };
2497
+ if (scopedParams) {
2498
+ requestOptions.params = scopedParams;
2499
+ }
2500
+ if (body !== void 0) {
2501
+ requestOptions.body = body;
2502
+ }
2503
+ let response;
2504
+ switch (method) {
2505
+ case "GET":
2506
+ response = await this.#client.GET(path, requestOptions);
2507
+ break;
2508
+ case "POST":
2509
+ response = await this.#client.POST(path, requestOptions);
2510
+ break;
2511
+ case "PUT":
2512
+ response = await this.#client.PUT(path, requestOptions);
2513
+ break;
2514
+ case "DELETE":
2515
+ response = await this.#client.DELETE(path, requestOptions);
2516
+ break;
2517
+ case "PATCH":
2518
+ response = await this.#client.PATCH(path, requestOptions);
2519
+ break;
2520
+ default:
2521
+ throw new ValidationError(`Unsupported HTTP method: ${method}`);
2522
+ }
2523
+ abortContext.clear();
2524
+ if (this.#config.verbose) {
2525
+ this.#log("debug", `\u2190 ${response.response?.status} ${method} ${path}`, {
2526
+ status: response.response?.status,
2527
+ statusText: response.response?.statusText
2528
+ });
2529
+ }
2530
+ if (response.error) {
2531
+ this.#config.onRequestError(requestData, response.response, response.error);
2532
+ await this.#handleOpenApiError(response.error, response.response, requestOrigin);
2533
+ }
2534
+ const result = response.data;
2535
+ this.#config.onAfterRequest(requestData);
2536
+ return result;
2537
+ } catch (error) {
2538
+ abortContext.clear();
2539
+ if (error instanceof SyntaxError) {
2540
+ throw new UnexpectedResponseError(
2541
+ formatUnexpectedResponseMessage(error, method, requestUrl),
2542
+ error,
2543
+ requestOrigin
2544
+ );
2545
+ }
2546
+ throw this.#toTransportError(error, requestOrigin, abortContext);
2547
+ }
2548
+ });
2549
+ };
2550
+ /**
2551
+ * Handle OpenAPI fetch errors. Always reads the real `response.status` so
2552
+ * "no response" never masquerades as 500.
2553
+ */
2554
+ #handleOpenApiError = async (error, response, request) => {
2555
+ const status = response?.status;
2556
+ const errorMessage = formatApiErrorMessage(error);
2557
+ const errorBody = parseErrorBody(error);
2558
+ const fields = extractFieldErrors(errorBody);
2559
+ switch (status) {
2560
+ case 401:
2561
+ throw new AuthenticationError(
2562
+ errorMessage || "Invalid API token or authentication failed",
2563
+ response,
2564
+ request
2565
+ );
2566
+ case 403:
2567
+ throw new AuthorizationError(
2568
+ errorMessage || "Access denied. Check your permissions.",
2569
+ response,
2570
+ request
2571
+ );
2572
+ case 404:
2573
+ throw new NotFoundError(errorMessage || "Resource not found", response, request);
2574
+ case 400:
2575
+ case 422:
2576
+ if (status === 400 && this.#isMissingProjectId(errorMessage, fields)) {
2577
+ throw new MissingProjectIdError(void 0, response, request);
2578
+ }
2579
+ throw new ValidationError(
2580
+ errorMessage || "Validation failed. Check your request data.",
2581
+ response,
2582
+ fields,
2583
+ request
2584
+ );
2585
+ case 429: {
2586
+ const retryAfter = response?.headers.get("Retry-After");
2587
+ const retryAfterMs = retryAfter ? parseInt(retryAfter) * 1e3 : void 0;
2588
+ throw new RateLimitError(
2589
+ retryAfterMs,
2590
+ errorMessage || "Rate limit exceeded",
2591
+ response,
2592
+ request
2593
+ );
2594
+ }
2595
+ case 500:
2596
+ throw new InternalServerError(
2597
+ errorMessage || "Internal server error. Please try again later.",
2598
+ response,
2599
+ request
2600
+ );
2601
+ case 502:
2602
+ case 503:
2603
+ case 504:
2604
+ throw new ServiceUnavailableError(
2605
+ errorMessage || "Service temporarily unavailable. Please try again later.",
2606
+ response,
2607
+ request
2608
+ );
2609
+ default:
2610
+ if (status === void 0) {
2611
+ throw new NetworkError(
2612
+ errorMessage || "No response received from BugBug API",
2613
+ void 0,
2614
+ request
2615
+ );
2616
+ }
2617
+ throw new HttpError(
2618
+ errorMessage || `HTTP ${status}: ${response?.statusText || "Unknown error"}`,
2619
+ status,
2620
+ response,
2621
+ request
2622
+ );
2623
+ }
2624
+ };
2625
+ /**
2626
+ * Issue a GET request through the openapi-fetch JSON path.
2627
+ *
2628
+ * @param path - API path template (e.g. `/tests/{id}/`).
2629
+ * @param options.queryParams - Mapped to URL search params; `undefined`/`null` values are dropped.
2630
+ * @param options.pathParams - Used to substitute `{name}` placeholders in `path`.
2631
+ * @returns Parsed JSON response body typed as `T`.
2632
+ */
2633
+ get = async (path, options = {}) => {
2634
+ const { queryParams, pathParams, ...requestOptions } = options;
2635
+ return this.#makeRequest("GET", path, {
2636
+ params: {
2637
+ query: queryParams,
2638
+ path: pathParams
2639
+ },
2640
+ ...requestOptions
2641
+ });
2642
+ };
2643
+ /**
2644
+ * Issue a POST request. The body is JSON-serialized by openapi-fetch.
2645
+ *
2646
+ * @param path - API path template.
2647
+ * @param options.body - Request body (JSON-serialized).
2648
+ * @param options.queryParams - URL search params.
2649
+ * @param options.pathParams - Path-segment values.
2650
+ */
2651
+ post = async (path, options = {}) => {
2652
+ const { body, queryParams, pathParams, ...requestOptions } = options;
2653
+ return this.#makeRequest("POST", path, {
2654
+ body,
2655
+ params: {
2656
+ query: queryParams,
2657
+ path: pathParams
2658
+ },
2659
+ ...requestOptions
2660
+ });
2661
+ };
2662
+ getAppRoot = async (path, options = {}) => {
2663
+ const { acceptRedirect = false, authenticated = true, redirect, ...requestOptions } = options;
2664
+ return this.#rawFetch(
2665
+ "GET",
2666
+ path,
2667
+ { ...requestOptions, acceptRedirect, authenticated, redirect, useAppRoot: true },
2668
+ async (response) => {
2669
+ if (acceptRedirect && response.status >= 300 && response.status < 400) {
2670
+ return response;
2671
+ }
2672
+ const text = await response.text();
2673
+ if (!text) return response;
2674
+ return parseJsonOrText(text);
2675
+ }
2676
+ );
2677
+ };
2678
+ postAppRoot = async (path, options = {}) => {
2679
+ const { authenticated = true, body, ...requestOptions } = options;
2680
+ return this.#rawFetch(
2681
+ "POST",
2682
+ path,
2683
+ {
2684
+ ...requestOptions,
2685
+ authenticated,
2686
+ useAppRoot: true,
2687
+ ...body !== void 0 ? { body: JSON.stringify(body), contentType: "application/json" } : {}
2688
+ },
2689
+ async (response) => parseJsonOrText(await response.text())
2690
+ );
2691
+ };
2692
+ /**
2693
+ * Issue a JSON POST request for endpoints that intentionally return no
2694
+ * response body even on 2xx success.
2695
+ */
2696
+ postNoContent = async (path, options = {}) => {
2697
+ const { body, queryParams, pathParams, ...requestOptions } = options;
2698
+ return this.#rawFetch(
2699
+ "POST",
2700
+ path,
2701
+ {
2702
+ ...requestOptions,
2703
+ queryParams,
2704
+ pathParams,
2705
+ ...body !== void 0 ? { body: JSON.stringify(body), contentType: "application/json" } : {}
2706
+ },
2707
+ async () => void 0
2708
+ );
2709
+ };
2710
+ /** Issue a PUT request (full-replace semantics). See {@link post} for parameters. */
2711
+ put = async (path, options = {}) => {
2712
+ const { body, queryParams, pathParams, ...requestOptions } = options;
2713
+ return this.#makeRequest("PUT", path, {
2714
+ body,
2715
+ params: {
2716
+ query: queryParams,
2717
+ path: pathParams
2718
+ },
2719
+ ...requestOptions
2720
+ });
2721
+ };
2722
+ /** Issue a PATCH request (partial-update semantics). See {@link post} for parameters. */
2723
+ patch = async (path, options = {}) => {
2724
+ const { body, queryParams, pathParams, ...requestOptions } = options;
2725
+ return this.#makeRequest("PATCH", path, {
2726
+ body,
2727
+ params: {
2728
+ query: queryParams,
2729
+ path: pathParams
2730
+ },
2731
+ ...requestOptions
2732
+ });
2733
+ };
2734
+ /**
2735
+ * Issue a DELETE request. An optional `body` is accepted for DRF endpoints
2736
+ * that require one, though `openapi-fetch` only sends it when the OpenAPI
2737
+ * spec declares it.
2738
+ */
2739
+ delete = async (path, options = {}) => {
2740
+ const { body, queryParams, pathParams, ...requestOptions } = options;
2741
+ return this.#makeRequest("DELETE", path, {
2742
+ body,
2743
+ params: {
2744
+ query: queryParams,
2745
+ path: pathParams
2746
+ },
2747
+ ...requestOptions
2748
+ });
2749
+ };
2750
+ /**
2751
+ * GET an endpoint that returns plain text (e.g. JUnit XML, logs).
2752
+ *
2753
+ * Bypasses the openapi-fetch JSON parser. Errors still flow through the
2754
+ * shared status→error mapping.
2755
+ *
2756
+ * @returns The response body as a string.
2757
+ */
2758
+ getText = async (path, options = {}) => this.#rawFetch("GET", path, options, async (response) => response.text());
2759
+ /**
2760
+ * GET an endpoint returning binary bytes (ZIP / octet-stream).
2761
+ *
2762
+ * @returns The response body as `Uint8Array`.
2763
+ */
2764
+ getBinary = async (path, options = {}) => this.#rawFetch(
2765
+ "GET",
2766
+ path,
2767
+ options,
2768
+ async (response) => new Uint8Array(await response.arrayBuffer())
2769
+ );
2770
+ /**
2771
+ * POST raw bytes or text with an explicit `Content-Type`.
2772
+ *
2773
+ * Used for binary (ZIP) and YAML uploads that don't fit the openapi-fetch
2774
+ * JSON path. The response is parsed as JSON when possible, otherwise the
2775
+ * raw text is returned.
2776
+ *
2777
+ * @param path - API path template.
2778
+ * @param body - Raw bytes or string to send as the request body.
2779
+ * @param options.contentType - MIME type sent in the `Content-Type` header.
2780
+ * @returns Parsed JSON body or raw text.
2781
+ */
2782
+ postRaw = async (path, body, options) => {
2783
+ const { contentType, ...rest } = options;
2784
+ return this.#rawFetch(
2785
+ "POST",
2786
+ path,
2787
+ { ...rest, body, contentType },
2788
+ async (response) => parseJsonOrText(await response.text())
2789
+ );
2790
+ };
2791
+ /**
2792
+ * POST a `multipart/form-data` body. The browser/fetch sets the
2793
+ * `Content-Type` boundary automatically.
2794
+ *
2795
+ * @param path - API path template.
2796
+ * @param form - The `FormData` instance to send.
2797
+ * @returns Parsed JSON body or raw text.
2798
+ */
2799
+ postForm = async (path, form, options = {}) => this.#rawFetch(
2800
+ "POST",
2801
+ path,
2802
+ { ...options, body: form },
2803
+ async (response) => parseJsonOrText(await response.text())
2804
+ );
2805
+ /**
2806
+ * Shared scaffolding for the raw-`fetch` paths (text/binary/multipart/custom
2807
+ * content type) that bypass openapi-fetch. Owns URL building, request hooks,
2808
+ * rate limiting, timeout/abort handling, verbose logging, error mapping, and
2809
+ * the transport-level catch — so each public method only supplies how to read
2810
+ * the successful response body.
2811
+ */
2812
+ #rawFetch = async (method, path, options, readBody) => {
2813
+ const {
2814
+ queryParams,
2815
+ pathParams,
2816
+ signal,
2817
+ timeout,
2818
+ headers: customHeaders,
2819
+ body,
2820
+ contentType,
2821
+ authenticated = true,
2822
+ acceptRedirect = false,
2823
+ redirect,
2824
+ useAppRoot = false,
2825
+ projectScoped = true
2826
+ } = options;
2827
+ const scopedParams = this.#scopedParams(
2828
+ { path: pathParams, query: queryParams },
2829
+ { projectScoped: projectScoped && authenticated && !useAppRoot }
2830
+ );
2831
+ const url = this.#buildRequestUrl(path, scopedParams, useAppRoot);
2832
+ const requestData = {
2833
+ method,
2834
+ path,
2835
+ params: scopedParams,
2836
+ signal,
2837
+ timeout,
2838
+ headers: customHeaders
2839
+ };
2840
+ const requestOrigin = { method, url };
2841
+ this.#config.onBeforeRequest(requestData);
2842
+ return this.#rateLimiter.execute(async () => {
2843
+ const abortContext = this.#createAbortContext(signal, timeout);
2844
+ try {
2845
+ if (this.#config.verbose) {
2846
+ this.#log("debug", `\u2192 ${method} ${path} (raw)`, { queryParams, pathParams });
2847
+ }
2848
+ const response = await fetch(url, {
2849
+ method,
2850
+ redirect,
2851
+ headers: {
2852
+ // Caller headers go first so the SDK's auth + identification
2853
+ // headers always win — a stray `Authorization` in caller-supplied
2854
+ // headers would otherwise silently replace the SDK token.
2855
+ ...customHeaders,
2856
+ ...authenticated ? this.#identityHeaders() : this.#requestHeaders(),
2857
+ ...contentType ? { "Content-Type": contentType } : {}
2858
+ },
2859
+ ...body !== void 0 ? { body } : {},
2860
+ signal: abortContext.signal
2861
+ });
2862
+ abortContext.clear();
2863
+ if (this.#config.verbose) {
2864
+ this.#log("debug", `\u2190 ${response.status} ${method} ${path}`, {
2865
+ status: response.status,
2866
+ statusText: response.statusText
2867
+ });
2868
+ }
2869
+ if (!response.ok && !(acceptRedirect && response.status >= 300 && response.status < 400)) {
2870
+ const errorText = await response.text();
2871
+ this.#config.onRequestError(requestData, response, new Error(errorText));
2872
+ await this.#handleOpenApiError(errorText, response, requestOrigin);
2873
+ }
2874
+ const result = await readBody(response);
2875
+ this.#config.onAfterRequest(requestData);
2876
+ return result;
2877
+ } catch (error) {
2878
+ abortContext.clear();
2879
+ throw this.#toTransportError(error, requestOrigin, abortContext);
2880
+ }
2881
+ });
2882
+ };
2883
+ #createAbortContext = (signal, timeout) => {
2884
+ const controller = new AbortController();
2885
+ const requestTimeout = timeout || this.#config.timeout;
2886
+ let timedOut = false;
2887
+ const timeoutId = setTimeout(() => {
2888
+ timedOut = true;
2889
+ controller.abort();
2890
+ }, requestTimeout);
2891
+ const abortFromCaller = () => {
2892
+ if (!controller.signal.aborted) {
2893
+ controller.abort(signal?.reason);
2894
+ }
2895
+ };
2896
+ if (signal) {
2897
+ if (signal.aborted) {
2898
+ abortFromCaller();
2899
+ } else {
2900
+ signal.addEventListener("abort", abortFromCaller, { once: true });
2901
+ }
2902
+ }
2903
+ return {
2904
+ signal: controller.signal,
2905
+ timeout: requestTimeout,
2906
+ isTimedOut: () => timedOut,
2907
+ clear: () => {
2908
+ clearTimeout(timeoutId);
2909
+ signal?.removeEventListener("abort", abortFromCaller);
2910
+ }
2911
+ };
2912
+ };
2913
+ /**
2914
+ * Map a thrown transport-level error to the matching SDK error, or rethrow
2915
+ * SDK errors (e.g. from `handleOpenApiError`) untouched.
2916
+ */
2917
+ #toTransportError = (error, requestOrigin, abortContext) => {
2918
+ if (error instanceof DOMException && error.name === "AbortError") {
2919
+ if (abortContext?.isTimedOut()) {
2920
+ return new TimeoutError(abortContext.timeout, void 0, requestOrigin);
2921
+ }
2922
+ return new CancellationError("Request was cancelled", requestOrigin);
2923
+ }
2924
+ if (error instanceof TypeError && error.message.includes("fetch")) {
2925
+ return new NetworkError(formatNetworkErrorMessage(error), error, requestOrigin);
2926
+ }
2927
+ return error;
2928
+ };
2929
+ /**
2930
+ * Return a frozen snapshot of the resolved configuration (defaults applied).
2931
+ */
2932
+ getConfig = () => ({ ...this.#config });
2933
+ /**
2934
+ * Patch configuration at runtime. Re-builds the openapi-fetch client when
2935
+ * `apiToken` or `apiUrl` changes, and rebuilds the rate limiter when
2936
+ * `rateLimit` changes — other fields take effect on the next request.
2937
+ *
2938
+ * @param updates - Partial configuration overrides to merge in.
2939
+ */
2940
+ updateConfig = (updates) => {
2941
+ this.#config = { ...this.#config, ...updates };
2942
+ this.#config.apiUrl = this.#normalizeApiUrl(this.#config.apiUrl);
2943
+ if (updates.apiToken || updates.apiUrl) {
2944
+ this.#client = this.#createApiClient();
2945
+ }
2946
+ if (updates.rateLimit) {
2947
+ this.#rateLimiter = new RateLimiter(this.#config.rateLimit);
2948
+ }
2949
+ };
2950
+ /**
2951
+ * Snapshot of the in-process rate-limiter state.
2952
+ *
2953
+ * @returns `{ requestsInWindow, remainingRequests, maxRequests, windowMs, resetTime, canMakeRequest }`.
2954
+ */
2955
+ getRateLimitStatus = () => this.#rateLimiter.getStatus();
2956
+ /**
2957
+ * Log messages based on log level
2958
+ */
2959
+ #log = (level, message, data) => {
2960
+ const levels = { debug: 0, info: 1, warn: 2, error: 3 };
2961
+ const currentLevel = levels[this.#config.logLevel];
2962
+ const messageLevel = levels[level];
2963
+ if (messageLevel >= currentLevel) {
2964
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2965
+ const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
2966
+ if (data) {
2967
+ console[level](logMessage, data);
2968
+ } else {
2969
+ console[level](logMessage);
2970
+ }
2971
+ }
2972
+ };
2973
+ #normalizeApiUrl = (apiUrl) => apiUrl.replace(/\/+$/, "");
2974
+ #requestHeaders = () => ({
2975
+ ...this.#config.requestHeaders
2976
+ });
2977
+ /**
2978
+ * Base URL for the few endpoints Django serves at its site root rather than
2979
+ * under `/api/v2` (`/auth/authorize-url`, `/auth/identity`,
2980
+ * `/auth/oauth/token/`).
2981
+ *
2982
+ * Stripping the `/api/v<n>` suffix is correct when talking straight to Django
2983
+ * (local dev, in-cluster `backend:8000`). Hosted BugBug sits behind nginx,
2984
+ * which forwards those routes only under its `/rest/` prefix — bare requests
2985
+ * fall through to the web app and return HTML — so those deployments set
2986
+ * `BUGBUG_APP_URL` to override this.
2987
+ */
2988
+ #appBaseUrl = () => this.#normalizeApiUrl(
2989
+ process.env.BUGBUG_APP_URL || this.#config.apiUrl.replace(/\/api\/v\d+\/?$/i, "")
2990
+ );
2991
+ #buildRequestUrl = (path, params, useAppRoot = false) => {
2992
+ let url = `${useAppRoot ? this.#appBaseUrl() : this.#config.apiUrl}${path}`;
2993
+ if (params?.path) {
2994
+ Object.entries(params.path).forEach(([key, value]) => {
2995
+ url = url.replace(`{${key}}`, encodeURIComponent(value));
2996
+ });
2997
+ }
2998
+ if (params?.query) {
2999
+ const searchParams = new URLSearchParams();
3000
+ Object.entries(params.query).forEach(([key, value]) => {
3001
+ if (value !== void 0 && value !== null) {
3002
+ searchParams.append(key, String(value));
3003
+ }
3004
+ });
3005
+ const query = searchParams.toString();
3006
+ if (query) url += `?${query}`;
3007
+ }
3008
+ return url;
3009
+ };
3010
+ };
3011
+
3012
+ // src/sdk.ts
3013
+ var BugBugSDK = class {
3014
+ #client;
3015
+ tests;
3016
+ suites;
3017
+ profiles;
3018
+ config;
3019
+ groups;
3020
+ components;
3021
+ steps;
3022
+ stepRuns;
3023
+ project;
3024
+ projectArtifacts;
3025
+ projects;
3026
+ variables;
3027
+ visualRegression;
3028
+ auth;
3029
+ /**
3030
+ * @param config - SDK configuration (API token, base URL, rate limits, etc.)
3031
+ */
3032
+ constructor(config) {
3033
+ this.#client = new BugBugApiClient(config);
3034
+ this.tests = new TestsModule(this.#client);
3035
+ this.suites = new SuitesModule(this.#client);
3036
+ this.profiles = new ProfilesModule(this.#client);
3037
+ this.config = new ConfigModule(this.#client);
3038
+ this.groups = new GroupsModule(this.#client);
3039
+ this.components = new ComponentsModule(this.#client);
3040
+ this.steps = new StepsModule(this.#client);
3041
+ this.stepRuns = new StepRunsModule(this.#client);
3042
+ this.project = new ProjectModule(this.#client);
3043
+ this.projectArtifacts = new ProjectArtifactsModule(this.#client);
3044
+ this.projects = new ProjectsModule(this.#client);
3045
+ this.variables = new VariablesModule(this.#client);
3046
+ this.visualRegression = new VisualRegressionModule(this.#client);
3047
+ this.auth = new AuthModule(this.#client);
3048
+ }
3049
+ /**
3050
+ * Get the current SDK configuration.
3051
+ *
3052
+ * @returns Readonly configuration object with all resolved defaults
3053
+ *
3054
+ * @example
3055
+ * ```typescript
3056
+ * const config = sdk.getConfig();
3057
+ * console.log(`API URL: ${config.apiUrl}`);
3058
+ * console.log(`Verbose: ${config.verbose}`);
3059
+ * ```
3060
+ */
3061
+ getConfig = () => this.#client.getConfig();
3062
+ /**
3063
+ * Update SDK configuration at runtime.
3064
+ *
3065
+ * @param updates - Partial configuration updates to apply
3066
+ *
3067
+ * @example
3068
+ * ```typescript
3069
+ * sdk.updateConfig({
3070
+ * verbose: true,
3071
+ * timeout: 60000
3072
+ * });
3073
+ * ```
3074
+ */
3075
+ updateConfig = (updates) => {
3076
+ this.#client.updateConfig(updates);
3077
+ };
3078
+ /**
3079
+ * Set the active project for subsequent project-scoped requests.
3080
+ * Shorthand for `updateConfig({ projectId })`.
3081
+ *
3082
+ * @param projectId - The project id to scope requests to.
3083
+ *
3084
+ * @example
3085
+ * ```typescript
3086
+ * sdk.setProject('00000000-0000-0000-0000-000000000001');
3087
+ * ```
3088
+ */
3089
+ setProject = (projectId) => {
3090
+ this.updateConfig({ projectId });
3091
+ };
3092
+ /**
3093
+ * Test API connectivity and authentication.
3094
+ *
3095
+ * @param options - Request options including timeout and signal
3096
+ * @returns Connectivity test result with timing and status
3097
+ *
3098
+ * @example
3099
+ * ```typescript
3100
+ * const result = await sdk.testConnection();
3101
+ * if (result.connected) {
3102
+ * console.log(`Connected in ${result.responseTime}ms`);
3103
+ * } else {
3104
+ * console.error(`Connection failed: ${result.error}`);
3105
+ * }
3106
+ * ```
3107
+ */
3108
+ testConnection = async (options = {}) => this.config.testConnectivity(options);
3109
+ };
3110
+
3111
+ // src/modules/visualRegression/visualRegression.constants.ts
3112
+ var VISUAL_REGRESSION_RESOLUTION_ACTIONS = [
3113
+ "acceptBaseline",
3114
+ "increaseMaxDifference"
3115
+ ];
3116
+ var VISUAL_REGRESSION_RESOLUTION_TYPE_BY_ACTION = {
3117
+ acceptBaseline: "set_new_reference",
3118
+ increaseMaxDifference: "set_new_max_difference"
3119
+ };
3120
+
3121
+ // src/modules/auth/auth.types.ts
3122
+ var BUGBUG_CLI_OAUTH_CLIENT_ID = "bugbug-cli";
3123
+ var BUGBUG_MCP_OAUTH_CLIENT_ID = "bugbug-mcp";
3124
+ var BUGBUG_OAUTH_CODE_CHALLENGE_METHOD = "S256";
3125
+
3126
+ // src/modules/auth/auth.utils.ts
3127
+ import { randomBytes, randomUUID, createHash } from "crypto";
3128
+ var createOAuthState = () => randomUUID();
3129
+ var createOAuthCodeVerifier = () => randomBytes(48).toString("base64url");
3130
+ var createOAuthCodeChallenge = (verifier) => createHash("sha256").update(verifier).digest("base64url");
3131
+
3132
+ // src/modules/steps/steps.utils.ts
3133
+ var ASSERTION_STEP_TYPES = [
3134
+ "assert",
3135
+ "ifCondition"
3136
+ ];
3137
+ var VISUAL_REGRESSION_STEP_TYPES = [
3138
+ "elementVisualRegression",
3139
+ "pageVisualRegression"
3140
+ ];
3141
+ var isAssertionStepType = (stepType) => typeof stepType === "string" && ASSERTION_STEP_TYPES.includes(stepType);
3142
+ var isVisualRegressionStepType = (stepType) => typeof stepType === "string" && VISUAL_REGRESSION_STEP_TYPES.includes(stepType);
3143
+
3144
+ // ../api-schema/dist/schemas/common.js
3145
+ import { z } from "zod";
3146
+ var UuidSchema = z.string().min(1).describe("UUID");
3147
+ var DateTimeSchema = z.string().describe("ISO 8601 date-time string");
3148
+ var CursorSchema = z.string().describe("Opaque cursor returned as next_token.");
3149
+ var PageSizeSchema = z.number().int().min(1).max(100).describe("Number of results per page.");
3150
+ var StepTimingSecondsSchema = z.number().min(0).max(300).nullable().optional();
3151
+
3152
+ // ../api-schema/dist/schemas/enums.js
3153
+ import { z as z2 } from "zod";
3154
+ var DEBUG_ARTIFACT_TYPES = [
3155
+ "network_logs",
3156
+ "logs",
3157
+ "dom_snapshot"
3158
+ ];
3159
+ var DebugArtifactByTypeSchema = z2.enum(DEBUG_ARTIFACT_TYPES);
3160
+ var RUN_MODES = [
3161
+ "server",
3162
+ "local",
3163
+ "recording"
3164
+ ];
3165
+ var RunModeSchema = z2.enum(RUN_MODES).describe("Run execution mode: server, local, recording.");
3166
+ var WAITING_CONDITION_TYPES = [
3167
+ "documentComplete",
3168
+ "elementIsVisible",
3169
+ "elementIsNotCovered",
3170
+ "elementIsNotAnimating",
3171
+ "elementIsNotDisabled",
3172
+ "elementHasFocus",
3173
+ "networkIdle",
3174
+ "pageNavigationAfterExecution",
3175
+ "elementHasAttribute"
3176
+ ];
3177
+ var WaitingConditionTypeSchema = z2.enum(WAITING_CONDITION_TYPES).describe("Waiting condition type evaluated by BugBug.");
3178
+ var StatusSchema = z2.enum([
3179
+ "initialized",
3180
+ "passed",
3181
+ "failed",
3182
+ "auto_retrying",
3183
+ "recording",
3184
+ "queued",
3185
+ "running",
3186
+ "stopped",
3187
+ "paused",
3188
+ "error",
3189
+ "skipped"
3190
+ ]);
3191
+ var StepTypeSchema = z2.enum([
3192
+ "answerPrompt",
3193
+ "assert",
3194
+ "change",
3195
+ "clear",
3196
+ "click",
3197
+ "dblClick",
3198
+ "rightClick",
3199
+ "closeTab",
3200
+ "dragAndDrop",
3201
+ "elementVisualRegression",
3202
+ "execute",
3203
+ "goto",
3204
+ "goBack",
3205
+ "goForward",
3206
+ "hover",
3207
+ "ifCondition",
3208
+ "mouseDown",
3209
+ "mouseUp",
3210
+ "newTab",
3211
+ "pageVisualRegression",
3212
+ "pasteFromClipboard",
3213
+ "reloadPage",
3214
+ "scroll",
3215
+ "select",
3216
+ "setLocalVariable",
3217
+ "switchContext",
3218
+ "type",
3219
+ "uploadFile"
3220
+ ]).describe("Available step types: answerPrompt, assert, change, clear, click, dblClick, rightClick, closeTab, dragAndDrop, elementVisualRegression, execute, goto, goBack, goForward, hover, ifCondition, mouseDown, mouseUp, newTab, pageVisualRegression, pasteFromClipboard, reloadPage, scroll, select, setLocalVariable, switchContext, type, uploadFile.");
3221
+ var AssertionTypeSchema = z2.enum([
3222
+ "contain",
3223
+ "equal",
3224
+ "greaterThan",
3225
+ "lessThan",
3226
+ "match",
3227
+ "notContain",
3228
+ "notEqual",
3229
+ "notMatch",
3230
+ "any"
3231
+ ]).describe("Assertion operators supported by BugBug: contain, equal, greaterThan, lessThan, match, notContain, notEqual, notMatch, any.");
3232
+ var AssertionPropertySchema = z2.enum([
3233
+ "checked",
3234
+ "count",
3235
+ "customJavaScript",
3236
+ "exist",
3237
+ "notChecked",
3238
+ "notExist",
3239
+ "notVisible",
3240
+ "textContent",
3241
+ "hasAttribute",
3242
+ "hasNotAttribute",
3243
+ "value",
3244
+ "visible",
3245
+ "pageTitle",
3246
+ "pageShowsText",
3247
+ "pageDoesNotShowText",
3248
+ "pageUrlIs",
3249
+ "downloadStarted",
3250
+ "variableValue",
3251
+ "clipboardValue",
3252
+ "disabled",
3253
+ "notDisabled"
3254
+ ]).describe("Assertion properties supported by BugBug, including element state, page-level checks, downloads, variables, clipboard, and disabled state.");
3255
+ var SelectorTypeSchema = z2.enum(["XPath", "customCSS", "customXPath", ""]).nullable().describe("Selector engines or strategies supported by BugBug: XPath, customCSS, customXPath.");
3256
+ var SelectorsRelationSchema = z2.enum(["descendant", "ancestor", "sibling"]).describe("How selectors inside a group are combined: descendant, ancestor, sibling.");
3257
+ var InteractionPositionSchema = z2.enum([
3258
+ "smart",
3259
+ "custom",
3260
+ "topLeft",
3261
+ "topCenter",
3262
+ "topRight",
3263
+ "middleLeft",
3264
+ "middleCenter",
3265
+ "middleRight",
3266
+ "bottomLeft",
3267
+ "bottomCenter",
3268
+ "bottomRight"
3269
+ ]).describe("Element interaction position used by BugBug.");
3270
+ var DragAndDropTargetSchema = z2.enum(["coords", "element"]).describe("Drag-and-drop target mode.");
3271
+ var ScrollEdgeSchema = InteractionPositionSchema.exclude(["smart", "custom"]).nullable().describe("Element edge to scroll toward.");
3272
+ var ScrollDirectionSchema = z2.enum(["down", "up", "right", "left"]).nullable();
3273
+ var ScrollToSchema = z2.enum(["coords", "edge", "untilNextStepElementIsVisible", "elementIntoView"]).describe("Scroll target strategy.");
3274
+ var ScrollInsideSchema = z2.enum(["window", "element"]).describe("Whether to scroll inside the window or target element.");
3275
+ var SelectTypeSchema = z2.enum(["text", "index", "value"]).describe("How the option should be matched.");
3276
+ var ScreenSizeTypeSchema = z2.enum(["desktop", "mobile", "custom"]).describe("Screen size preset used when running or recording the test.");
3277
+ var TriggeredBySchema = z2.enum(["user", "api", "webhook", "scheduler", "github", "cli", "agent"]).describe("Origin that triggered the run.");
3278
+
3279
+ // ../api-schema/dist/schemas/selectors.js
3280
+ import { z as z3 } from "zod";
3281
+ var SelectorSchema = z3.object({
3282
+ id: UuidSchema.optional().describe("Selector UUID when persisted by the API."),
3283
+ selector: z3.string().nullable().describe("Raw xpath or css selector expression used to find an element."),
3284
+ type: SelectorTypeSchema.describe("Selector engine or strategy name used by BugBug."),
3285
+ isActive: z3.boolean().describe("Whether this selector is enabled."),
3286
+ step: UuidSchema.optional().describe("Owning step UUID when returned by the API.")
3287
+ }).passthrough();
3288
+ var SelectorsGroupSchema = z3.object({
3289
+ id: UuidSchema.optional().describe("Selectors group UUID when persisted by the API."),
3290
+ relation: SelectorsRelationSchema.describe("How selectors groups inside the preset are combined."),
3291
+ selectors: z3.array(SelectorSchema).min(1, "Selectors group must contain at least one selector.").describe("Selectors included in this group. Must contain at least one selector.")
3292
+ }).passthrough();
3293
+ var SelectorsPresetSchema = z3.object({
3294
+ id: UuidSchema.optional().describe("Selectors preset UUID when persisted by the API."),
3295
+ isCustom: z3.boolean().describe("Whether the preset was manually customized."),
3296
+ isActive: z3.boolean().describe("Whether the preset is enabled for the step."),
3297
+ selectorsGroups: z3.array(SelectorsGroupSchema).min(1, "Selectors preset must contain at least one selectors group.").describe("Selector groups used to locate the target element. Must contain at least one group when provided.")
3298
+ }).passthrough();
3299
+
3300
+ // ../api-schema/dist/schemas/steps.js
3301
+ import { z as z4 } from "zod";
3302
+
3303
+ // ../api-schema/dist/step-error-codes.js
3304
+ var STEP_ERROR_CODES = [
3305
+ "ELEMENT_DOES_NOT_EXIST",
3306
+ "ELEMENT_REMOVED",
3307
+ "SELECTOR_REQUIRED",
3308
+ "INVALID_ELEMENT_SELECTOR",
3309
+ "INTERACTION_POSITION_OUT_OF_VIEWPORT",
3310
+ "FRAME_DOES_NOT_EXIST",
3311
+ "FRAME_LOADS_TOO_LONG",
3312
+ "WINDOW_OR_TAB_DOES_NOT_EXIST",
3313
+ "TAB_CLOSED",
3314
+ "WINDOW_CLOSED",
3315
+ "WINDOW_MINIMIZED",
3316
+ "UNEXPECTED_WINDOW_STATE_CHANGE",
3317
+ "UNCHANGABLE_ELEMENT",
3318
+ "TYPED_TEXT_DIFFERENT_THAN_EXPECTED",
3319
+ "INVALID_FIELD_VALUE",
3320
+ "INVALID_OPTION_INDEX",
3321
+ "MISSING_OPTION_VALUE",
3322
+ "MISSING_OPTION_TEXT",
3323
+ "MISSING_OPTION_INDEX",
3324
+ "SELECT_OPTION_FAILED",
3325
+ "MULTIPLE_OPTIONS_WITH_VALUE",
3326
+ "MISSING_GOTO_STEP",
3327
+ "INVALID_URL",
3328
+ "PAGE_LOADING_ERROR",
3329
+ "ELEMENT_VISUAL_REGRESSION_FAILED",
3330
+ "PAGE_VISUAL_REGRESSION_FAILED",
3331
+ "SCREENSHOT_CAPTURE_ERROR",
3332
+ "REFERENCE_SCREENSHOT_NOT_FOUND",
3333
+ "SCREENSHOT_COMPARISON_ERROR",
3334
+ "CODE_EXECUTION_ERROR",
3335
+ "INVALID_EXECUTION_RESULT",
3336
+ "VARIABLE_DOES_NOT_EXIST",
3337
+ "VARIABLE_NESTING_LIMIT_EXCEEDED",
3338
+ "EVENT_DISPATCHED_ON_INVALID_ELEMENT",
3339
+ "EVENT_DISPATCH_FAILED",
3340
+ "FAILED_WAITING_CONDITIONS",
3341
+ "PROMPT_DOES_NOT_EXIST",
3342
+ "UNHANDLED_PROMPT",
3343
+ "NEXT_STEP_ELEMENT_REQUIRED",
3344
+ "NEXT_ACTIVE_STEP_WITH_ELEMENT_REQUIRED",
3345
+ "ASSERT_FAILED",
3346
+ "SCROLL_FAILED",
3347
+ "TIMEOUT",
3348
+ "SINGLE_TEST_TIME_EXCEEDED",
3349
+ "UNSUPPORTED_BROWSER",
3350
+ "DEBUGGER_DETACHED",
3351
+ "BLOCKED_BY_BROWSER_POLICY",
3352
+ "INVALID_CUSTOM_HEADERS",
3353
+ "INVALID_DATA_FORMAT",
3354
+ "RUNTIME_ERROR",
3355
+ "INTERNAL_ERROR",
3356
+ "INITIALIZATION_ERROR",
3357
+ "AUTHENTICATION_ERROR",
3358
+ "STEP_RUN_INITIALIZATION_ERROR",
3359
+ "UNRECOGNIZED_STEP_TYPE",
3360
+ "FRAME_IS_NOT_INITIALIZED",
3361
+ "INVALID_MOUSE_INPUT_PARAMS",
3362
+ "FILE_DOES_NOT_EXIST",
3363
+ "FILE_UPLOAD_ERROR",
3364
+ "VALUE_COMPUTING_ERROR",
3365
+ "WEBSOCKET_ERROR",
3366
+ "WEBSOCKET_SETUP_ERROR",
3367
+ "REQUEST_ERROR",
3368
+ "LOGS_UPLOAD_TIMEOUT",
3369
+ "EXTENSION_DOES_NOT_RESPONSE",
3370
+ "EXTENSION_DISCONNECTED",
3371
+ "EXTENSION_DISCONNECTED_ERROR",
3372
+ "TAKING_SCREENSHOT_TIMEOUT",
3373
+ "MISSING_STEP_SCREENSHOT",
3374
+ "MISSING_ELEMENT_SCREENSHOT"
3375
+ ];
3376
+
3377
+ // ../api-schema/dist/schemas/steps.js
3378
+ var WaitingConditionSchema = z4.object({
3379
+ type: WaitingConditionTypeSchema,
3380
+ expected: z4.string().nullable().describe("Expected value for the waiting condition, if any.")
3381
+ }).passthrough();
3382
+ var StepErrorCodeSchema = z4.enum(STEP_ERROR_CODES);
3383
+ var StepSourceSchema = z4.enum(["api", "agent"]).describe("Creation source for public API step writes.");
3384
+ var StepBaseSchema = z4.object({
3385
+ id: UuidSchema.describe("Step UUID. Omit when creating a new step."),
3386
+ atIndex: z4.number().nullable().describe("Insert position within the group."),
3387
+ name: z4.string().nullable().optional().describe("Human-readable step name."),
3388
+ blockId: UuidSchema.nullable().optional().describe("Id of step representing a block start eg. ifCondition step"),
3389
+ groupId: UuidSchema.nullable().describe("Owning group UUID for the step."),
3390
+ originGroupId: UuidSchema.optional().describe("Original group UUID from the API response. Omit when creating a new step."),
3391
+ notes: z4.string().nullable().optional().describe("Optional step notes."),
3392
+ isActive: z4.boolean().optional().describe("Whether the step is enabled."),
3393
+ runTimeout: StepTimingSecondsSchema.describe("Maximum execution time in seconds before the step times out. If not defined, default project settings will be used."),
3394
+ sleep: StepTimingSecondsSchema.describe("Delay in seconds before running the step. If not defined, default project settings will be used."),
3395
+ continueOnFailure: z4.boolean().optional().describe("Whether the run should continue after a fail."),
3396
+ waitingConditions: z4.array(WaitingConditionSchema.extend({
3397
+ isActive: z4.boolean(),
3398
+ isOverridden: z4.boolean().default(true).describe("Required when overriding default waiting condition config."),
3399
+ timeout: z4.number().nullable().optional().default(null).describe("Custom waiting condition timeout")
3400
+ })).optional().describe("Optional waiting conditions evaluated around the step."),
3401
+ selectorsPresets: z4.array(SelectorsPresetSchema).optional().describe("Element selector presets required by element-targeting steps variants."),
3402
+ source: StepSourceSchema.optional().describe("Optional creation source. Direct API and SDK clients should use api; agent/MCP clients should use agent.")
3403
+ }).passthrough();
3404
+ var omitStepRuntimeFields = (schema) => schema.omit({
3405
+ id: true,
3406
+ originGroupId: true
3407
+ });
3408
+ var toStepPatchSchema = (schema) => {
3409
+ const createSchema = omitStepRuntimeFields(schema).omit({
3410
+ source: true
3411
+ });
3412
+ return createSchema.partial().extend({
3413
+ type: createSchema.shape.type
3414
+ });
3415
+ };
3416
+ var asNonEmptyDiscriminatedOptions = (schemas) => {
3417
+ const [first, ...rest] = schemas;
3418
+ if (!first) {
3419
+ throw new Error("Expected at least one step schema");
3420
+ }
3421
+ return [first, ...rest];
3422
+ };
3423
+ var AnswerPromptStepSchema = StepBaseSchema.extend({
3424
+ type: z4.literal("answerPrompt").describe("Respond to a browser prompt dialog."),
3425
+ value: z4.string().describe("Prompt response value.")
3426
+ });
3427
+ var AssertStepSchema = StepBaseSchema.extend({
3428
+ type: z4.literal("assert").describe("Create an assertion step."),
3429
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used by the element-related assertion."),
3430
+ assertionProperty: AssertionPropertySchema.describe("Assertion property to inspect, for example text or value."),
3431
+ assertionType: AssertionTypeSchema.describe("Assertion operator used by BugBug."),
3432
+ assertionExpectedValue: z4.string().nullable().describe("Expected assertion value when applicable."),
3433
+ assertionJavaScript: z4.string().nullable().describe("Custom JavaScript used by script-based assertions."),
3434
+ assertionVariableName: z4.string().nullable().describe("Variable name used by variable-based assertions.")
3435
+ });
3436
+ var PasteFromClipboardStepSchema = StepBaseSchema.extend({
3437
+ type: z4.literal("pasteFromClipboard").describe("Paste clipboard contents into the active element.")
3438
+ });
3439
+ var ChangeStepSchema = StepBaseSchema.extend({
3440
+ type: z4.literal("change").describe("Change the value of the targeted element."),
3441
+ value: z4.string().describe("Value to set on the target element.")
3442
+ });
3443
+ var ClearStepSchema = StepBaseSchema.extend({
3444
+ type: z4.literal("clear").describe("Clear the value of the targeted element.")
3445
+ });
3446
+ var ClickStepShape = {
3447
+ interactionPosition: InteractionPositionSchema.describe("Click position relative to the target element."),
3448
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used to locate the element.")
3449
+ };
3450
+ var ClickStepSchema = StepBaseSchema.extend({
3451
+ type: z4.literal("click").describe("Perform a click on the targeted element."),
3452
+ ...ClickStepShape
3453
+ });
3454
+ var DoubleClickStepSchema = StepBaseSchema.extend({
3455
+ type: z4.literal("dblClick").describe("Perform a double click on the targeted element."),
3456
+ ...ClickStepShape
3457
+ });
3458
+ var RightClickStepSchema = StepBaseSchema.extend({
3459
+ type: z4.literal("rightClick").describe("Perform a right click on the targeted element."),
3460
+ ...ClickStepShape
3461
+ });
3462
+ var CloseTabStepSchema = StepBaseSchema.extend({
3463
+ type: z4.literal("closeTab").describe("Close the current browser tab.")
3464
+ });
3465
+ var DragAndDropStepSchema = StepBaseSchema.extend({
3466
+ type: z4.literal("dragAndDrop").describe("Drag from one element and drop onto another."),
3467
+ dndDragOn: DragAndDropTargetSchema.describe("Whether dragging starts from coordinates or an element."),
3468
+ interactionPosition: InteractionPositionSchema.describe("Drag start position relative to the target element."),
3469
+ dndDragX: z4.number().nullable().optional().describe("Drag start X coordinate when dragging by coordinates."),
3470
+ dndDragY: z4.number().nullable().optional().describe("Drag start Y coordinate when dragging by coordinates."),
3471
+ dndDropOn: DragAndDropTargetSchema.describe("Whether dropping targets coordinates or an element."),
3472
+ dndDropInteractionPosition: InteractionPositionSchema.describe("Drop position relative to the target element."),
3473
+ dndDropX: z4.number().nullable().optional().describe("Drop X coordinate when dropping by coordinates."),
3474
+ dndDropY: z4.number().nullable().optional().describe("Drop Y coordinate when dropping by coordinates."),
3475
+ dndDropSelectorsPresets: z4.array(SelectorsPresetSchema).optional().describe("Target selectors used to locate the drop element.")
3476
+ });
3477
+ var ExecuteStepSchema = StepBaseSchema.extend({
3478
+ type: z4.literal("execute").describe("Execute custom JavaScript in the page context."),
3479
+ code: z4.string().describe("JavaScript source code to execute.")
3480
+ });
3481
+ var GotoStepSchema = StepBaseSchema.extend({
3482
+ type: z4.literal("goto").describe("Navigate the browser to a URL."),
3483
+ url: z4.string().describe("Absolute URL to open.")
3484
+ });
3485
+ var HoverStepSchema = StepBaseSchema.extend({
3486
+ type: z4.literal("hover").describe("Hover over the targeted element."),
3487
+ interactionPosition: InteractionPositionSchema.describe("Hover position relative to the target element."),
3488
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used to locate the element.")
3489
+ });
3490
+ var MouseDownStepSchema = StepBaseSchema.extend({
3491
+ type: z4.literal("mouseDown").describe("Press the mouse button on the targeted element."),
3492
+ interactionPosition: InteractionPositionSchema.describe("Mouse-down position relative to the target element."),
3493
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used to locate the element.")
3494
+ });
3495
+ var MouseUpStepSchema = StepBaseSchema.extend({
3496
+ type: z4.literal("mouseUp").describe("Release the mouse button on the targeted element."),
3497
+ interactionPosition: InteractionPositionSchema.describe("Mouse-up position relative to the target element."),
3498
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used to locate the element.")
3499
+ });
3500
+ var NewTabStepSchema = StepBaseSchema.extend({
3501
+ type: z4.literal("newTab").describe("Open a new browser tab."),
3502
+ url: z4.string().nullable().optional().describe("URL to open in the new tab."),
3503
+ username: z4.string().nullable().optional().describe("Optional username for basic authentication."),
3504
+ password: z4.string().nullable().optional().describe("Optional password for basic authentication.")
3505
+ });
3506
+ var ReloadPageStepSchema = StepBaseSchema.extend({
3507
+ type: z4.literal("reloadPage").describe("Reload the current page.")
3508
+ });
3509
+ var ScrollStepSchema = StepBaseSchema.extend({
3510
+ type: z4.literal("scroll").describe("Scroll the page or target element."),
3511
+ scrollX: z4.number().nullable().optional().describe("Horizontal scroll coordinate."),
3512
+ scrollY: z4.number().nullable().optional().describe("Vertical scroll coordinate."),
3513
+ scrollEdge: ScrollEdgeSchema.optional(),
3514
+ scrollDirection: ScrollDirectionSchema.optional().describe("Scroll direction."),
3515
+ scrollTo: ScrollToSchema,
3516
+ scrollInside: ScrollInsideSchema
3517
+ });
3518
+ var SelectStepSchema = StepBaseSchema.extend({
3519
+ type: z4.literal("select").describe("Select an option in the targeted select element."),
3520
+ value: z4.string().describe("Option value or label to select."),
3521
+ selectType: SelectTypeSchema,
3522
+ selectIsMultiple: z4.boolean().describe("Whether the target select allows multiple values.")
3523
+ });
3524
+ var SetLocalVariableStepSchema = StepBaseSchema.extend({
3525
+ type: z4.literal("setLocalVariable").describe("Store a value in a local BugBug variable."),
3526
+ value: z4.string().optional().describe("Literal value to store when using the value source."),
3527
+ code: z4.string().nullable().optional().describe("JavaScript source used when evaluating a variable value."),
3528
+ localVariableName: z4.string().describe("Variable name to assign."),
3529
+ localVariableSource: z4.enum(["element", "value", "evaluate"]).describe("Source used to populate the variable.")
3530
+ });
3531
+ var SwitchContextStepSchema = StepBaseSchema.extend({
3532
+ type: z4.literal("switchContext").describe("Switch execution context to another browser tab."),
3533
+ tabNo: z4.number().describe("Tab index to activate.")
3534
+ });
3535
+ var TypeStepSchema = StepBaseSchema.extend({
3536
+ type: z4.literal("type").describe("Type text into the targeted element."),
3537
+ value: z4.string().describe("Text to type.")
3538
+ });
3539
+ var UploadStepSchema = StepBaseSchema.extend({
3540
+ type: z4.literal("uploadFile").describe("Upload a project artifact into the targeted file input."),
3541
+ projectArtifactId: UuidSchema.describe("Project artifact UUID to upload.")
3542
+ });
3543
+ var GoBackStepSchema = StepBaseSchema.extend({
3544
+ type: z4.literal("goBack").describe("Navigate the browser back in history.")
3545
+ });
3546
+ var GoForwardStepSchema = StepBaseSchema.extend({
3547
+ type: z4.literal("goForward").describe("Navigate the browser forward in history.")
3548
+ });
3549
+ var IfConditionStepSchema = StepBaseSchema.extend({
3550
+ type: z4.literal("ifCondition").describe("Branch execution based on an assertion condition."),
3551
+ selectorsPresets: z4.array(SelectorsPresetSchema).optional().describe("Target selectors used to locate the element for the condition."),
3552
+ assertionProperty: AssertionPropertySchema.describe("Assertion property to inspect for the branch condition."),
3553
+ assertionType: AssertionTypeSchema.describe("Assertion operator used to evaluate the branch condition."),
3554
+ assertionExpectedValue: z4.string().nullable().describe("Expected assertion value when applicable."),
3555
+ assertionJavaScript: z4.string().nullable().describe("Custom JavaScript used by script-based conditions."),
3556
+ assertionVariableName: z4.string().nullable().describe("Variable name used by variable-based conditions.")
3557
+ });
3558
+ var ElementVisualRegressionStepSchema = StepBaseSchema.extend({
3559
+ type: z4.literal("elementVisualRegression").describe("Perform a visual regression check on a targeted element."),
3560
+ selectorsPresets: z4.array(SelectorsPresetSchema).describe("Target selectors used to locate the element for comparison."),
3561
+ visualRegressionMaxDiff: z4.number().nullable().optional().describe("Maximum allowed pixel difference percentage (0\u2013100).")
3562
+ });
3563
+ var PageVisualRegressionStepSchema = StepBaseSchema.extend({
3564
+ type: z4.literal("pageVisualRegression").describe("Perform a visual regression check on the full page."),
3565
+ visualRegressionMaxDiff: z4.number().nullable().optional().describe("Maximum allowed pixel difference percentage (0\u2013100).")
3566
+ });
3567
+ var allStepsSchemas = [
3568
+ AnswerPromptStepSchema,
3569
+ AssertStepSchema,
3570
+ PasteFromClipboardStepSchema,
3571
+ ChangeStepSchema,
3572
+ ClearStepSchema,
3573
+ ClickStepSchema,
3574
+ DoubleClickStepSchema,
3575
+ RightClickStepSchema,
3576
+ CloseTabStepSchema,
3577
+ DragAndDropStepSchema,
3578
+ ElementVisualRegressionStepSchema,
3579
+ ExecuteStepSchema,
3580
+ GotoStepSchema,
3581
+ GoBackStepSchema,
3582
+ GoForwardStepSchema,
3583
+ HoverStepSchema,
3584
+ IfConditionStepSchema,
3585
+ MouseDownStepSchema,
3586
+ MouseUpStepSchema,
3587
+ NewTabStepSchema,
3588
+ PageVisualRegressionStepSchema,
3589
+ ReloadPageStepSchema,
3590
+ ScrollStepSchema,
3591
+ SelectStepSchema,
3592
+ SetLocalVariableStepSchema,
3593
+ SwitchContextStepSchema,
3594
+ TypeStepSchema,
3595
+ UploadStepSchema
3596
+ ];
3597
+ var StepSchema = z4.discriminatedUnion("type", allStepsSchemas);
3598
+ var CREATE_STEP_SCHEMA_OPTIONS = asNonEmptyDiscriminatedOptions(allStepsSchemas.map((schema) => omitStepRuntimeFields(schema)));
3599
+ var UPDATE_STEP_SCHEMA_OPTIONS = asNonEmptyDiscriminatedOptions(allStepsSchemas.map((schema) => toStepPatchSchema(schema)));
3600
+ var StepCreateSchema = z4.discriminatedUnion("type", CREATE_STEP_SCHEMA_OPTIONS);
3601
+ var StepUpdateSchema = z4.discriminatedUnion("type", UPDATE_STEP_SCHEMA_OPTIONS);
3602
+ var StepRunSchema = z4.object({
3603
+ id: UuidSchema.describe("Step run UUID."),
3604
+ stepId: UuidSchema.nullable().optional().describe("Original step UUID associated with this run, if available."),
3605
+ type: z4.string().optional().describe("Executed step type."),
3606
+ status: StatusSchema.optional().describe("Execution status of the step run."),
3607
+ screenshotUrl: z4.string().optional().describe("Screenshot URL captured for this step run, when available."),
3608
+ errorCode: StepErrorCodeSchema.nullable().optional().describe("Structured BugBug error code for the failed step run, if any."),
3609
+ error: z4.string().nullable().optional().describe("Human-readable error message for the step run, if any."),
3610
+ duration: z4.string().nullable().optional().describe("Execution duration reported by the API."),
3611
+ waitingConditions: z4.array(WaitingConditionSchema.extend({
3612
+ isSuccess: z4.boolean().describe("Whether the condition passed."),
3613
+ isSoftSuccess: z4.boolean().optional().describe("Whether the condition passed softly with warnings."),
3614
+ isIgnored: z4.boolean().optional().describe("Whether the condition result is ignored."),
3615
+ current: z4.string().nullable().optional().describe("Observed value for the waiting condition, if any.")
3616
+ })).describe("Waiting conditions evaluated during the step run.")
3617
+ }).passthrough();
3618
+
3619
+ // ../api-schema/dist/schemas/tests.js
3620
+ import { z as z6 } from "zod";
3621
+
3622
+ // ../api-schema/dist/schemas/_paginated.js
3623
+ import { z as z5 } from "zod";
3624
+ var makePaginatedSchema = (itemSchema) => z5.object({
3625
+ count: z5.number().optional().describe("Total number of items matching the query."),
3626
+ nextToken: z5.string().nullable().optional().describe("Opaque cursor token for the next page, if one exists."),
3627
+ results: z5.array(itemSchema).describe("Items returned for the current page.")
3628
+ });
3629
+
3630
+ // ../api-schema/dist/schemas/tests.js
3631
+ var TestSchema = z6.object({
3632
+ id: UuidSchema.describe("Test UUID."),
3633
+ name: z6.string().optional().describe("Test name."),
3634
+ groups: z6.string().describe("Serialized group structure returned by the API."),
3635
+ notes: z6.string().nullable().describe("Optional test notes."),
3636
+ screenSizeType: ScreenSizeTypeSchema,
3637
+ webappUrl: z6.string().describe("BugBug web app URL for the test details page.")
3638
+ }).passthrough();
3639
+ var TestListItemSchema = z6.object({
3640
+ id: UuidSchema.describe("Test UUID."),
3641
+ name: z6.string().optional().describe("Test name.")
3642
+ }).passthrough();
3643
+ var TestsListSchema = makePaginatedSchema(TestListItemSchema);
3644
+
3645
+ // ../api-schema/dist/schemas/suites.js
3646
+ import { z as z7 } from "zod";
3647
+ var SuiteSchema = z7.object({
3648
+ id: UuidSchema.describe("Suite UUID."),
3649
+ name: z7.string().nullable().describe("Suite name."),
3650
+ testsCount: z7.number().describe("Number of tests included in the suite."),
3651
+ tests: z7.array(z7.record(z7.unknown())).describe("Raw test entries included in the suite response."),
3652
+ autoAddNewTests: z7.boolean().describe("Whether newly created tests are automatically added to the suite."),
3653
+ autoRetry: z7.number().describe("Number of automatic retries configured for failed tests in the suite."),
3654
+ notes: z7.string().nullable().describe("Optional suite notes."),
3655
+ runInParallel: z7.boolean().describe("Whether suite tests run in parallel."),
3656
+ runProfileId: z7.string().nullable().describe("Run profile UUID used by default for the suite, if configured.")
3657
+ }).passthrough();
3658
+ var SuiteDetailsSchema = SuiteSchema.extend({
3659
+ webappUrl: z7.string().describe("BugBug web app URL for the suite details page.")
3660
+ });
3661
+ var SuitesListSchema = makePaginatedSchema(SuiteSchema);
3662
+
3663
+ // ../api-schema/dist/schemas/profiles.js
3664
+ import { z as z8 } from "zod";
3665
+ var ProfileSchema = z8.object({
3666
+ id: UuidSchema.describe("Run profile UUID."),
3667
+ name: z8.string().describe("Run profile name."),
3668
+ isDefault: z8.boolean().optional().describe("Whether this is the default run profile.")
3669
+ }).passthrough();
3670
+ var ProfilesListSchema = makePaginatedSchema(ProfileSchema);
3671
+
3672
+ // ../api-schema/dist/schemas/projects.js
3673
+ import { z as z9 } from "zod";
3674
+ var ProjectSchema = z9.object({
3675
+ id: UuidSchema.describe("Project UUID."),
3676
+ name: z9.string().nullable().describe("Project name."),
3677
+ homepageUrl: z9.string().nullable().describe("Project homepage URL."),
3678
+ organizationId: z9.string().nullable().describe("Owning organization UUID."),
3679
+ organizationName: z9.string().describe("Owning organization name.")
3680
+ }).passthrough();
3681
+ var ProjectsListSchema = makePaginatedSchema(ProjectSchema);
3682
+
3683
+ // ../api-schema/dist/schemas/runs.js
3684
+ import { z as z10 } from "zod";
3685
+ var RunResultSchema = z10.object({
3686
+ id: UuidSchema.describe("Run UUID."),
3687
+ status: StatusSchema.describe("Current status of the run."),
3688
+ webappUrl: z10.string().describe("BugBug web app URL for the run details page."),
3689
+ modified: DateTimeSchema.optional().describe("Last modification timestamp reported by the API.")
3690
+ }).passthrough();
3691
+ var StepRunScreenshotSchema = z10.object({
3692
+ id: UuidSchema.describe("Step run UUID."),
3693
+ stepId: UuidSchema.nullable().describe("Original step UUID associated with the screenshot, if available."),
3694
+ screenshotUrl: z10.string().describe("Screenshot URL for the step run.")
3695
+ }).passthrough();
3696
+ var TestRunSchema = z10.object({
3697
+ id: UuidSchema.describe("Test run UUID."),
3698
+ name: z10.string().min(1).describe("Test run name."),
3699
+ queued: DateTimeSchema.nullable().describe("Queue timestamp, if the run waited before execution."),
3700
+ status: StatusSchema.describe("Current status of the test run."),
3701
+ sequence: z10.number().nullable().describe("Sequence number assigned by BugBug, if available."),
3702
+ webappUrl: z10.string().describe("BugBug web app URL for the test run."),
3703
+ errorCode: StepErrorCodeSchema.nullable().describe("Structured BugBug error code for the test run, if any."),
3704
+ duration: z10.string().nullable().describe("Execution duration reported by the API."),
3705
+ started: DateTimeSchema.nullable().describe("Run start timestamp, if available."),
3706
+ ended: DateTimeSchema.nullable().describe("Run end timestamp, if available."),
3707
+ stepsRuns: z10.string().describe("API reference or serialized value containing the step runs payload."),
3708
+ runProfileId: UuidSchema.nullable().describe("Run profile UUID used for this execution, if available."),
3709
+ testId: UuidSchema.nullable().describe("Original test UUID associated with this run, if available."),
3710
+ suiteRunId: UuidSchema.nullable().describe("Parent suite run UUID when the test run belongs to a suite run."),
3711
+ triggeredBy: TriggeredBySchema
3712
+ }).passthrough();
3713
+ var TestRunStateSchema = z10.object({
3714
+ id: UuidSchema.describe("Test run UUID."),
3715
+ modified: DateTimeSchema.describe("Last modification timestamp reported by the API."),
3716
+ status: StatusSchema.optional().describe("Current status of the test run."),
3717
+ webappUrl: z10.string().describe("BugBug web app URL for the test run."),
3718
+ duration: z10.string().nullable().optional().describe("Execution duration reported by the API."),
3719
+ started: DateTimeSchema.nullable().optional().describe("Run start timestamp, if available."),
3720
+ ended: DateTimeSchema.nullable().optional().describe("Run end timestamp, if available.")
3721
+ }).passthrough();
3722
+ var TestRunScreenshotsSchema = z10.object({
3723
+ id: UuidSchema.describe("Test run UUID."),
3724
+ stepsRuns: z10.array(StepRunScreenshotSchema).describe("Screenshots captured for step runs within the test run.")
3725
+ }).passthrough();
3726
+ var SuiteRunSchema = z10.object({
3727
+ id: UuidSchema.describe("Suite run UUID."),
3728
+ name: z10.string().min(1).describe("Suite run name."),
3729
+ queued: DateTimeSchema.nullable().describe("Queue timestamp, if the suite run waited before execution."),
3730
+ status: StatusSchema.describe("Current status of the suite run."),
3731
+ sequence: z10.number().nullable().describe("Sequence number assigned by BugBug, if available."),
3732
+ webappUrl: z10.string().describe("BugBug web app URL for the suite run."),
3733
+ errorCode: StepErrorCodeSchema.nullable().describe("Structured BugBug error code for the suite run, if any."),
3734
+ duration: z10.string().nullable().describe("Execution duration reported by the API."),
3735
+ started: DateTimeSchema.nullable().describe("Run start timestamp, if available."),
3736
+ ended: DateTimeSchema.nullable().describe("Run end timestamp, if available."),
3737
+ profileId: UuidSchema.describe("Run profile UUID used for the suite run."),
3738
+ profileName: z10.string().describe("Run profile name used for the suite run."),
3739
+ testRuns: z10.array(TestRunSchema).describe("Test runs included in the suite run."),
3740
+ triggeredBy: TriggeredBySchema
3741
+ }).passthrough();
3742
+ var SuiteRunStateSchema = z10.object({
3743
+ id: UuidSchema.describe("Suite run UUID."),
3744
+ modified: DateTimeSchema.describe("Last modification timestamp reported by the API."),
3745
+ status: StatusSchema.describe("Current status of the suite run."),
3746
+ webappUrl: z10.string().describe("BugBug web app URL for the suite run."),
3747
+ duration: z10.string().nullable().optional().describe("Execution duration reported by the API."),
3748
+ started: DateTimeSchema.nullable().optional().describe("Run start timestamp, if available."),
3749
+ ended: DateTimeSchema.nullable().optional().describe("Run end timestamp, if available.")
3750
+ }).passthrough();
3751
+ var SuiteRunScreenshotsSchema = z10.object({
3752
+ id: UuidSchema.describe("Suite run UUID."),
3753
+ testsRuns: z10.array(TestRunScreenshotsSchema).describe("Screenshot collections grouped by test run within the suite run.")
3754
+ }).passthrough();
3755
+ var RunVariableSchema = z10.object({
3756
+ key: z10.string().describe("Variable name."),
3757
+ value: z10.string().nullable().optional().describe("Variable value.")
3758
+ });
3759
+
3760
+ // src/index.ts
3761
+ function createBugBug(config) {
3762
+ return new BugBugSDK(config);
3763
+ }
3764
+ export {
3765
+ ASSERTION_STEP_TYPES,
3766
+ AuthModule,
3767
+ AuthenticationError,
3768
+ AuthorizationError,
3769
+ BUGBUG_CLI_OAUTH_CLIENT_ID,
3770
+ BUGBUG_MCP_OAUTH_CLIENT_ID,
3771
+ BUGBUG_OAUTH_CODE_CHALLENGE_METHOD,
3772
+ BugBugError,
3773
+ BugBugSDK,
3774
+ CancellationError,
3775
+ ComponentsModule,
3776
+ ConfigModule,
3777
+ ConfigurationError,
3778
+ CursorSchema,
3779
+ DebugArtifactByTypeSchema,
3780
+ GroupsModule,
3781
+ HttpError,
3782
+ InternalServerError,
3783
+ MissingProjectIdError,
3784
+ NetworkError,
3785
+ NotFoundError,
3786
+ PageSizeSchema,
3787
+ ProfilesModule,
3788
+ ProjectArtifactsModule,
3789
+ ProjectModule,
3790
+ ProjectsModule,
3791
+ RUN_MODES,
3792
+ RateLimitError,
3793
+ RunModeSchema,
3794
+ SDK_ERROR_CODES,
3795
+ STEP_ERROR_CODES,
3796
+ ScreenSizeTypeSchema,
3797
+ SelectorsPresetSchema,
3798
+ ServiceUnavailableError,
3799
+ StatusSchema,
3800
+ StepCreateSchema,
3801
+ StepErrorCodeSchema,
3802
+ StepRunsModule,
3803
+ StepSchema,
3804
+ StepTypeSchema,
3805
+ StepUpdateSchema,
3806
+ StepsModule,
3807
+ SuitesModule,
3808
+ TestsModule,
3809
+ TimeoutError,
3810
+ UnexpectedResponseError,
3811
+ UuidSchema,
3812
+ VISUAL_REGRESSION_RESOLUTION_ACTIONS,
3813
+ VISUAL_REGRESSION_RESOLUTION_TYPE_BY_ACTION,
3814
+ VISUAL_REGRESSION_STEP_TYPES,
3815
+ ValidationError,
3816
+ VariablesModule,
3817
+ VisualRegressionModule,
3818
+ createBugBug,
3819
+ createOAuthCodeChallenge,
3820
+ createOAuthCodeVerifier,
3821
+ createOAuthState,
3822
+ isAssertionStepType,
3823
+ isAuthenticationError,
3824
+ isAuthorizationError,
3825
+ isBugBugError,
3826
+ isCancellationError,
3827
+ isCompletedStatus,
3828
+ isConfigurationError,
3829
+ isEditingStatus,
3830
+ isFailedStatus,
3831
+ isHttpError,
3832
+ isInternalServerError,
3833
+ isMissingProjectIdError,
3834
+ isNetworkError,
3835
+ isNotFoundError,
3836
+ isPassedStatus,
3837
+ isRateLimitError,
3838
+ isRunningStatus,
3839
+ isServiceUnavailableError,
3840
+ isTimeoutError,
3841
+ isUnexpectedResponseError,
3842
+ isValidationError,
3843
+ isVisualRegressionStepType
3844
+ };