@qawolf/ci-sdk 2.2.0 → 2.5.0

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 (42) hide show
  1. package/README.md +13 -0
  2. package/dist/index.cjs +389 -196
  3. package/dist/index.cjs.map +7 -1
  4. package/dist/index.d.ts +4 -557
  5. package/dist/index.js +364 -194
  6. package/dist/index.js.map +7 -1
  7. package/dist/lib/api/ci-greenlight-bug-data.d.ts +67 -0
  8. package/dist/lib/api/ci-greenlight.d.ts +76 -0
  9. package/dist/lib/api/generate-signed-url-for-run-inputs-executables.d.ts +4 -0
  10. package/dist/lib/api/generate-signed-url-for-team-storage.d.ts +4 -0
  11. package/dist/lib/api/index.d.ts +2 -0
  12. package/dist/lib/api/log.d.ts +6 -0
  13. package/dist/lib/api/signed-url.d.ts +11 -0
  14. package/dist/lib/api-types.d.ts +5 -0
  15. package/dist/lib/other.d.ts +5 -0
  16. package/dist/lib/sdk/defaults/fetch.d.ts +1 -0
  17. package/dist/lib/sdk/defaults/log.d.ts +2 -0
  18. package/dist/lib/sdk/defaults/serviceBase.d.ts +1 -0
  19. package/dist/lib/sdk/defaults/userAgent.d.ts +1 -0
  20. package/dist/lib/sdk/dependencies.d.ts +7 -0
  21. package/dist/lib/sdk/domain/attemptDeploy/deployConfig.d.ts +37 -0
  22. package/dist/lib/sdk/domain/attemptDeploy/deploySuccessWireResponse.d.ts +39 -0
  23. package/dist/lib/sdk/domain/attemptDeploy/handleSuccessResponse.d.ts +3 -0
  24. package/dist/lib/sdk/domain/attemptDeploy/index.d.ts +4 -0
  25. package/dist/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.d.ts +3 -0
  26. package/dist/lib/sdk/domain/attemptDeploy/parseDeploySuccessWireResponse.d.ts +2 -0
  27. package/dist/lib/sdk/domain/attemptDeploy/types.d.ts +129 -0
  28. package/dist/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-run-inputs-executables.d.ts +4 -0
  29. package/dist/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-team-storage.d.ts +16 -0
  30. package/dist/lib/sdk/domain/generateSignedUrls/types.d.ts +28 -0
  31. package/dist/lib/sdk/domain/notifyTerminatedEphemeralEnvironment/index.d.ts +4 -0
  32. package/dist/lib/sdk/domain/notifyTerminatedEphemeralEnvironment/types.d.ts +48 -0
  33. package/dist/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.d.ts +8 -0
  34. package/dist/lib/sdk/domain/pollCiGreenlight/consts.d.ts +7 -0
  35. package/dist/lib/sdk/domain/pollCiGreenlight/index.d.ts +4 -0
  36. package/dist/lib/sdk/domain/pollCiGreenlight/iterator.d.ts +43 -0
  37. package/dist/lib/sdk/domain/pollCiGreenlight/logging.d.ts +25 -0
  38. package/dist/lib/sdk/domain/pollCiGreenlight/mapIteratorAbortToGreenlightStatus.d.ts +7 -0
  39. package/dist/lib/sdk/domain/pollCiGreenlight/types.d.ts +145 -0
  40. package/dist/lib/sdk/index.d.ts +26 -0
  41. package/package.json +8 -7
  42. package/dist/index.d.cts +0 -557
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* Built with @qawolf/platform-builder@1.2.2 */
1
2
  var __defProp = Object.defineProperty;
2
3
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
4
 
@@ -12,10 +13,7 @@ function pluralize(count) {
12
13
  __name(pluralize, "pluralize");
13
14
  var buildFetchWithTimeout = /* @__PURE__ */ __name((fetch, defaultTimeoutMs) => async (...args) => {
14
15
  const signal = args[1]?.signal ?? AbortSignal.timeout(defaultTimeoutMs);
15
- return fetch(args[0], {
16
- ...args[1],
17
- signal
18
- });
16
+ return fetch(args[0], { ...args[1], signal });
19
17
  }, "buildFetchWithTimeout");
20
18
 
21
19
  // src/lib/sdk/defaults/fetch.ts
@@ -39,13 +37,61 @@ var defaultServiceBase = "https://app.qawolf.com/";
39
37
 
40
38
  // package.json
41
39
  var package_default = {
42
- version: "2.2.0"};
40
+ name: "@qawolf/ci-sdk",
41
+ version: "2.5.0",
42
+ private: false,
43
+ description: "A simple SDK for interacting with QAWolf in CI scripts.",
44
+ keywords: [],
45
+ license: "MIT",
46
+ author: "Jules Sam. Randolph<jules@qawolf.com>",
47
+ files: [
48
+ "/dist",
49
+ "/README.md"
50
+ ],
51
+ type: "module",
52
+ sideEffects: false,
53
+ exports: {
54
+ ".": {
55
+ types: "./dist/index.d.ts",
56
+ require: "./dist/index.cjs",
57
+ import: "./dist/index.js",
58
+ default: "./dist/index.js"
59
+ }
60
+ },
61
+ scripts: {
62
+ build: "platform-builder --config platformBuilder.config.ts --config platformBuilder.cjs.config.ts",
63
+ "lint:ci": "npm run lint:first-half; FIRST_HALF_EXIT=$?; npm run lint:second-half && exit $FIRST_HALF_EXIT",
64
+ "lint:dev": "npm run lint:first-half",
65
+ "lint:first-half": "eslint . --fix --quiet && oxfmt --write .",
66
+ "lint:second-half": "dpdm --exit-code circular:1 --no-tree --no-warning --transform --include='.*' --exclude='/(dist|node_modules)/' '**/*'",
67
+ prepublishOnly: "nx run ci-sdk:build",
68
+ test: 'NODE_OPTIONS="--experimental-vm-modules" NODE_NO_WARNINGS=1 jest',
69
+ "tsc:check": "tsc --noEmit"
70
+ },
71
+ dependencies: {
72
+ "@qawolf/ci-utils": "^1.1.2",
73
+ "@sinonjs/fake-timers": "^10.3.0",
74
+ tslib: "^2.6.2"
75
+ },
76
+ devDependencies: {
77
+ "@qawolf/platform-builder": "^1.2.2",
78
+ tsx: "^4.19.4",
79
+ typescript: "^5.9.3"
80
+ },
81
+ engines: {
82
+ node: ">=20"
83
+ }
84
+ };
43
85
 
44
86
  // src/lib/sdk/defaults/userAgent.ts
45
87
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
46
88
 
47
89
  // src/lib/api/log.ts
48
- async function logFetchError({ log, methodName, response }) {
90
+ async function logFetchError({
91
+ log,
92
+ methodName,
93
+ response
94
+ }) {
49
95
  if (response.ok) return;
50
96
  const { eventId, failureMessage } = await (async () => {
51
97
  try {
@@ -70,7 +116,9 @@ async function logFetchError({ log, methodName, response }) {
70
116
  const eventIdSuffix = eventId ? ` (Event ID: ${eventId})` : "";
71
117
  const messageSuffix = failureMessage ? `: ${failureMessage}` : "";
72
118
  const statusText = response.status >= 500 ? "Server Error" : response.statusText || "Unknown Error";
73
- log.error(`\u274C [${methodName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`);
119
+ log.error(
120
+ `\u274C [${methodName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`
121
+ );
74
122
  }
75
123
  __name(logFetchError, "logFetchError");
76
124
 
@@ -80,6 +128,7 @@ async function handleSuccessResponse(data, log) {
80
128
  const skippedTriggers = data.skippedTriggers ?? [];
81
129
  const skippedDeployment = data.skippedDeployment;
82
130
  const isPreviewDeployment = environment && !environment.isStatic;
131
+ if (data.warning) log.warn(`\u26A0\uFE0F ${data.warning}`);
83
132
  if (environment) {
84
133
  const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
85
134
  log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
@@ -87,13 +136,13 @@ async function handleSuccessResponse(data, log) {
87
136
  if (results.length === 0) {
88
137
  const skipMessages = [
89
138
  ...skippedTriggers.map((trigger) => trigger.message),
90
- ...skippedDeployment ? [
91
- skippedDeployment.message
92
- ] : []
139
+ ...skippedDeployment ? [skippedDeployment.message] : []
93
140
  ];
94
141
  const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
95
142
  if (isPreviewDeployment) {
96
- log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`);
143
+ log.info(
144
+ `\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`
145
+ );
97
146
  return {
98
147
  environmentId: environment.id,
99
148
  outcome: "success",
@@ -108,7 +157,9 @@ async function handleSuccessResponse(data, log) {
108
157
  skippedTriggers
109
158
  };
110
159
  }
111
- log.warn(skippedDeployment ? `\u26A0\uFE0F No run was created for this deployment.${skipDetail}` : `\u26A0\uFE0F No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`);
160
+ log.warn(
161
+ skippedDeployment ? `\u26A0\uFE0F No run was created for this deployment.${skipDetail}` : `\u26A0\uFE0F No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`
162
+ );
112
163
  return {
113
164
  outcome: "skipped",
114
165
  skippedDeployment
@@ -116,45 +167,43 @@ async function handleSuccessResponse(data, log) {
116
167
  }
117
168
  if (!environment) {
118
169
  log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
119
- return {
120
- failReason: "no-environment",
121
- outcome: "failed"
122
- };
170
+ return { failReason: "no-environment", outcome: "failed" };
123
171
  }
124
172
  const unrecognizedResult = results.find((result) => "unrecognized" in result);
125
173
  if (unrecognizedResult) {
126
- log.error(`\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`);
127
- return {
128
- failReason: "unknown-result",
129
- outcome: "failed"
130
- };
174
+ log.error(
175
+ `\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`
176
+ );
177
+ return { failReason: "unknown-result", outcome: "failed" };
131
178
  }
132
179
  const failedTrigger = results.find((result) => "failureReason" in result);
133
180
  if (failedTrigger) {
134
- log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureReason}". Please contact support.`);
135
- return {
136
- failReason: "run-could-not-be-created",
137
- outcome: "failed"
138
- };
181
+ log.error(
182
+ `\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureMessage ?? failedTrigger.failureReason}". Please contact support.`
183
+ );
184
+ return { failReason: "run-could-not-be-created", outcome: "failed" };
139
185
  }
140
186
  const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
141
187
  if (duplicateSuite) {
142
- log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`);
188
+ log.info(
189
+ `\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`
190
+ );
143
191
  return {
144
192
  environmentId: environment.id,
145
193
  outcome: "success",
146
194
  runId: duplicateSuite.duplicateSuiteId
147
195
  };
148
196
  }
149
- const successfulTrigger = results.find((result) => "createdSuiteId" in result);
197
+ const successfulTrigger = results.find(
198
+ (result) => "createdSuiteId" in result
199
+ );
150
200
  if (!successfulTrigger) {
151
201
  log.error(`\u{1F6AB} Run could not be created. Please contact support.`);
152
- return {
153
- failReason: "run-could-not-be-created",
154
- outcome: "failed"
155
- };
202
+ return { failReason: "run-could-not-be-created", outcome: "failed" };
156
203
  }
157
- log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`);
204
+ log.info(
205
+ `\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`
206
+ );
158
207
  return {
159
208
  environmentId: environment.id,
160
209
  outcome: "success",
@@ -165,29 +214,28 @@ __name(handleSuccessResponse, "handleSuccessResponse");
165
214
 
166
215
  // src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
167
216
  function mapSuiteResult(result) {
168
- if ("created_suite_id" in result) {
169
- return {
170
- createdSuiteId: result.created_suite_id,
171
- triggerId: result.trigger_id
172
- };
173
- }
174
- if ("failure_reason" in result) {
175
- return {
176
- failureReason: result.failure_reason,
177
- triggerId: result.trigger_id
178
- };
179
- }
180
- if ("duplicate_suite_id" in result) {
181
- return {
182
- duplicateSuiteId: result.duplicate_suite_id,
183
- skipReason: result.skip_reason,
184
- triggerId: result.trigger_id
185
- };
217
+ switch (result.outcome) {
218
+ case "created":
219
+ return {
220
+ createdSuiteId: result.created_suite_id,
221
+ triggerId: result.trigger_id
222
+ };
223
+ case "failed":
224
+ return {
225
+ failureCode: result.failure_code,
226
+ failureMessage: result.failure_message,
227
+ failureReason: result.failure_reason,
228
+ triggerId: result.trigger_id
229
+ };
230
+ case "skipped":
231
+ return {
232
+ duplicateSuiteId: result.duplicate_suite_id,
233
+ skipReason: result.skip_reason,
234
+ triggerId: result.trigger_id
235
+ };
236
+ default:
237
+ return { triggerId: result.trigger_id, unrecognized: true };
186
238
  }
187
- return {
188
- triggerId: result.trigger_id,
189
- unrecognized: true
190
- };
191
239
  }
192
240
  __name(mapSuiteResult, "mapSuiteResult");
193
241
  function mapDeploySuccessResponse(wire) {
@@ -203,7 +251,8 @@ function mapDeploySuccessResponse(wire) {
203
251
  message: trigger.message,
204
252
  reason: trigger.reason,
205
253
  triggerId: trigger.trigger_id
206
- }))
254
+ })),
255
+ warning: wire.warning
207
256
  };
208
257
  }
209
258
  __name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
@@ -213,22 +262,32 @@ function isRecord(value) {
213
262
  return typeof value === "object" && value !== null && !Array.isArray(value);
214
263
  }
215
264
  __name(isRecord, "isRecord");
265
+ function isOptionalString(value) {
266
+ return value === void 0 || typeof value === "string";
267
+ }
268
+ __name(isOptionalString, "isOptionalString");
216
269
  function isWireSuiteResult(value) {
217
270
  if (!isRecord(value)) return false;
218
271
  if (typeof value.trigger_id !== "string") return false;
219
- if ("created_suite_id" in value) return typeof value.created_suite_id === "string";
220
- if ("failure_reason" in value) return typeof value.failure_reason === "string";
221
- if ("duplicate_suite_id" in value) {
222
- return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
272
+ if (!isOptionalString(value.outcome)) return false;
273
+ switch (value.outcome) {
274
+ case "created":
275
+ return typeof value.created_suite_id === "string";
276
+ case "failed":
277
+ return typeof value.failure_reason === "string" && isOptionalString(value.failure_code) && isOptionalString(value.failure_message);
278
+ case "skipped":
279
+ return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
280
+ default:
281
+ return true;
223
282
  }
224
- return true;
225
283
  }
226
284
  __name(isWireSuiteResult, "isWireSuiteResult");
227
285
  function isEnvironment(value) {
228
286
  if (!isRecord(value)) return false;
229
287
  if (typeof value.id !== "string") return false;
230
288
  if (typeof value.isStatic !== "boolean") return false;
231
- if (value.alias !== void 0 && typeof value.alias !== "string") return false;
289
+ if (value.alias !== void 0 && typeof value.alias !== "string")
290
+ return false;
232
291
  return true;
233
292
  }
234
293
  __name(isEnvironment, "isEnvironment");
@@ -244,10 +303,15 @@ function isSkippedTrigger(value) {
244
303
  __name(isSkippedTrigger, "isSkippedTrigger");
245
304
  function isDeploySuccessWireResponse(value) {
246
305
  if (!isRecord(value)) return false;
247
- if (value.environment !== void 0 && !isEnvironment(value.environment)) return false;
248
- if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult)) return false;
249
- if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment)) return false;
250
- if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger))) return false;
306
+ if (value.environment !== void 0 && !isEnvironment(value.environment))
307
+ return false;
308
+ if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult))
309
+ return false;
310
+ if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment))
311
+ return false;
312
+ if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger)))
313
+ return false;
314
+ if (!isOptionalString(value.warning)) return false;
251
315
  return true;
252
316
  }
253
317
  __name(isDeploySuccessWireResponse, "isDeploySuccessWireResponse");
@@ -262,35 +326,38 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
262
326
  let response;
263
327
  let bodyText;
264
328
  try {
265
- response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
266
- body: JSON.stringify({
267
- branch: config.branch,
268
- commit_url: config.commitUrl,
269
- deduplication_key: config.deduplicationKey,
270
- deployment_type: config.deploymentType,
271
- deployment_url: config.deploymentUrl,
272
- ..."ephemeralEnvironment" in config ? {
273
- ephemeral_environment: config.ephemeralEnvironment
274
- } : "hostingService" in config && {
275
- hosting_service: config.hostingService,
276
- merge_request_number: config.hostingService === "GitLab" ? config.mergeRequestNumber : void 0,
277
- pull_request_number: config.hostingService === "GitHub" ? config.pullRequestNumber : void 0,
278
- repository: config.repository
329
+ response = await localFetch(
330
+ new URL("/api/webhooks/deploy_success", apiConfig.serviceBase),
331
+ {
332
+ body: JSON.stringify({
333
+ branch: config.branch,
334
+ commit_url: config.commitUrl,
335
+ deduplication_key: config.deduplicationKey,
336
+ deployment_type: config.deploymentType,
337
+ deployment_url: config.deploymentUrl,
338
+ ..."ephemeralEnvironment" in config ? {
339
+ ephemeral_environment: config.ephemeralEnvironment
340
+ } : "hostingService" in config && {
341
+ hosting_service: config.hostingService,
342
+ merge_request_number: config.hostingService === "GitLab" ? config.mergeRequestNumber : void 0,
343
+ pull_request_number: config.hostingService === "GitHub" ? config.pullRequestNumber : void 0,
344
+ repository: config.repository
345
+ },
346
+ sha: config.sha,
347
+ variables: config.variables,
348
+ workspaceId: config.workspaceId
349
+ }),
350
+ headers: {
351
+ Authorization: `Bearer ${apiConfig.apiKey}`,
352
+ "Content-Type": "application/json",
353
+ "User-Agent": apiConfig.userAgent
279
354
  },
280
- sha: config.sha,
281
- variables: config.variables,
282
- workspaceId: config.workspaceId
283
- }),
284
- headers: {
285
- Authorization: `Bearer ${apiConfig.apiKey}`,
286
- "Content-Type": "application/json",
287
- "User-Agent": apiConfig.userAgent
288
- },
289
- method: "POST",
290
- // Override default timeout to 5 minutes. This gives extra room
291
- // for both (1) waiting for reconciliation (2) creating the run.
292
- signal: AbortSignal.timeout(5 * 60 * 1e3)
293
- });
355
+ method: "POST",
356
+ // Override default timeout to 5 minutes. This gives extra room
357
+ // for both (1) waiting for reconciliation (2) creating the run.
358
+ signal: AbortSignal.timeout(5 * 60 * 1e3)
359
+ }
360
+ );
294
361
  if (!response.ok) return handleErrorResponse(response, log);
295
362
  bodyText = await response.text();
296
363
  } catch {
@@ -309,7 +376,10 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
309
376
  }
310
377
  const wire = parseDeploySuccessWireResponse(rawBody);
311
378
  if (wire === "invalid") {
312
- return abortInvalidResponseBody(log, "the response body did not match the expected shape");
379
+ return abortInvalidResponseBody(
380
+ log,
381
+ "the response body did not match the expected shape"
382
+ );
313
383
  }
314
384
  const data = mapDeploySuccessResponse(wire);
315
385
  return handleSuccessResponse(data, log);
@@ -350,10 +420,17 @@ var signedUrlPaths = {
350
420
  runInputsExecutables: "/api/v0/run-inputs-executables-signed-urls",
351
421
  teamStorage: "/api/v0/team-storage-signed-url"
352
422
  };
353
- function buildSignedUrl({ destinationFilePath, endpoint, serviceBase, workspaceId }) {
423
+ function buildSignedUrl({
424
+ destinationFilePath,
425
+ endpoint,
426
+ serviceBase,
427
+ workspaceId
428
+ }) {
354
429
  const url = new URL(signedUrlPaths[endpoint], serviceBase);
355
- if (destinationFilePath !== void 0) url.searchParams.set("file", destinationFilePath);
356
- if (workspaceId !== void 0) url.searchParams.set("workspaceId", workspaceId);
430
+ if (destinationFilePath !== void 0)
431
+ url.searchParams.set("file", destinationFilePath);
432
+ if (workspaceId !== void 0)
433
+ url.searchParams.set("workspaceId", workspaceId);
357
434
  return url;
358
435
  }
359
436
  __name(buildSignedUrl, "buildSignedUrl");
@@ -361,19 +438,22 @@ __name(buildSignedUrl, "buildSignedUrl");
361
438
  // src/lib/api/generate-signed-url-for-run-inputs-executables.ts
362
439
  async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
363
440
  try {
364
- const response = await localFetch(buildSignedUrl({
365
- destinationFilePath,
366
- endpoint: "runInputsExecutables",
367
- serviceBase,
368
- workspaceId
369
- }), {
370
- headers: {
371
- Authorization: `Bearer ${apiKey}`,
372
- "Content-Type": "application/json",
373
- "User-Agent": userAgent
374
- },
375
- method: "GET"
376
- });
441
+ const response = await localFetch(
442
+ buildSignedUrl({
443
+ destinationFilePath,
444
+ endpoint: "runInputsExecutables",
445
+ serviceBase,
446
+ workspaceId
447
+ }),
448
+ {
449
+ headers: {
450
+ Authorization: `Bearer ${apiKey}`,
451
+ "Content-Type": "application/json",
452
+ "User-Agent": userAgent
453
+ },
454
+ method: "GET"
455
+ }
456
+ );
377
457
  if (response.status === 200) {
378
458
  const json = await response.json();
379
459
  return {
@@ -414,18 +494,28 @@ async function generateSignedUrlForRunInputsExecutablesStorage(deps, apiConfig,
414
494
  success: false
415
495
  };
416
496
  }
417
- const responseState = await callGenerateSignedUrlForRunInputsExecutablesStorage(apiConfig, config, deps);
497
+ const responseState = await callGenerateSignedUrlForRunInputsExecutablesStorage(
498
+ apiConfig,
499
+ config,
500
+ deps
501
+ );
418
502
  if (!responseState.success) {
419
503
  let abortReason = "XXX-other-http-code";
420
504
  if (responseState.httpStatus >= 500) {
421
505
  abortReason = "5XX-server-error";
422
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`);
506
+ log.error(
507
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
508
+ );
423
509
  } else {
424
510
  if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
425
- else if (responseState.httpStatus === 401) abortReason = "401-invalid-credentials";
511
+ else if (responseState.httpStatus === 401)
512
+ abortReason = "401-invalid-credentials";
426
513
  else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
427
- else if (responseState.httpStatus === 0) abortReason = "client-network-error";
428
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`);
514
+ else if (responseState.httpStatus === 0)
515
+ abortReason = "client-network-error";
516
+ log.error(
517
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
518
+ );
429
519
  }
430
520
  return {
431
521
  abortReason,
@@ -446,19 +536,22 @@ __name(generateSignedUrlForRunInputsExecutablesStorage, "generateSignedUrlForRun
446
536
  // src/lib/api/generate-signed-url-for-team-storage.ts
447
537
  async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
448
538
  try {
449
- const response = await localFetch(buildSignedUrl({
450
- destinationFilePath,
451
- endpoint: "teamStorage",
452
- serviceBase,
453
- workspaceId
454
- }), {
455
- headers: {
456
- Authorization: `Bearer ${apiKey}`,
457
- "Content-Type": "application/json",
458
- "User-Agent": userAgent
459
- },
460
- method: "GET"
461
- });
539
+ const response = await localFetch(
540
+ buildSignedUrl({
541
+ destinationFilePath,
542
+ endpoint: "teamStorage",
543
+ serviceBase,
544
+ workspaceId
545
+ }),
546
+ {
547
+ headers: {
548
+ Authorization: `Bearer ${apiKey}`,
549
+ "Content-Type": "application/json",
550
+ "User-Agent": userAgent
551
+ },
552
+ method: "GET"
553
+ }
554
+ );
462
555
  if (response.status === 200) {
463
556
  const json = await response.json();
464
557
  return {
@@ -499,18 +592,28 @@ async function generateSignedUrlForTempTeamStorage(deps, apiConfig, config) {
499
592
  success: false
500
593
  };
501
594
  }
502
- const responseState = await callGenerateSignedUrlForTempTeamStorage(apiConfig, config, deps);
595
+ const responseState = await callGenerateSignedUrlForTempTeamStorage(
596
+ apiConfig,
597
+ config,
598
+ deps
599
+ );
503
600
  if (!responseState.success) {
504
601
  let abortReason = "XXX-other-http-code";
505
602
  if (responseState.httpStatus >= 500) {
506
603
  abortReason = "5XX-server-error";
507
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`);
604
+ log.error(
605
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
606
+ );
508
607
  } else {
509
608
  if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
510
- else if (responseState.httpStatus === 401) abortReason = "401-invalid-credentials";
609
+ else if (responseState.httpStatus === 401)
610
+ abortReason = "401-invalid-credentials";
511
611
  else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
512
- else if (responseState.httpStatus === 0) abortReason = "client-network-error";
513
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`);
612
+ else if (responseState.httpStatus === 0)
613
+ abortReason = "client-network-error";
614
+ log.error(
615
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
616
+ );
514
617
  }
515
618
  return {
516
619
  abortReason,
@@ -532,21 +635,22 @@ __name(generateSignedUrlForTempTeamStorage, "generateSignedUrlForTempTeamStorage
532
635
  var notifyTerminatedEphemeralEnvironment = /* @__PURE__ */ __name(async (deps, apiConfig, config) => {
533
636
  const { fetch: localFetch, log } = deps;
534
637
  try {
535
- const response = await localFetch(new URL("/api/webhooks/environment_terminated", apiConfig.serviceBase), {
536
- body: JSON.stringify(config),
537
- headers: {
538
- Authorization: `Bearer ${apiConfig.apiKey}`,
539
- "Content-Type": "application/json",
540
- "User-Agent": apiConfig.userAgent
541
- },
542
- method: "POST"
543
- });
638
+ const response = await localFetch(
639
+ new URL("/api/webhooks/environment_terminated", apiConfig.serviceBase),
640
+ {
641
+ body: JSON.stringify(config),
642
+ headers: {
643
+ Authorization: `Bearer ${apiConfig.apiKey}`,
644
+ "Content-Type": "application/json",
645
+ "User-Agent": apiConfig.userAgent
646
+ },
647
+ method: "POST"
648
+ }
649
+ );
544
650
  if (!response.ok) return handleErrorResponse2(response, log);
545
- const { environmentId } = await response.json();
546
- return {
547
- environmentId,
548
- outcome: "success"
549
- };
651
+ const { environmentId, warning } = await response.json();
652
+ if (warning) log.warn(`\u26A0\uFE0F ${warning}`);
653
+ return { environmentId, outcome: "success" };
550
654
  } catch {
551
655
  log.error(`\u{1F6AB} Network error from terminate_preview_environment, aborting.`);
552
656
  return {
@@ -579,14 +683,9 @@ __name(handleErrorResponse2, "handleErrorResponse");
579
683
 
580
684
  // src/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.ts
581
685
  function buildCompletedRunResult(status) {
582
- if (status.greenlight) return {
583
- greenlightStatus: status,
584
- outcome: "success"
585
- };
586
- else return {
587
- greenlightStatus: status,
588
- outcome: "failed"
589
- };
686
+ if (status.greenlight)
687
+ return { greenlightStatus: status, outcome: "success" };
688
+ else return { greenlightStatus: status, outcome: "failed" };
590
689
  }
591
690
  __name(buildCompletedRunResult, "buildCompletedRunResult");
592
691
 
@@ -595,20 +694,32 @@ var pollCiGreenlightDefaults = {
595
694
  abortOnSuperseded: false,
596
695
  maxRetries: 10,
597
696
  pollTimeout: 120 * 60 * 1e3,
697
+ // 2 hours
598
698
  retryInterval: 10 * 1e3,
699
+ // 10 seconds
599
700
  runInProgressInterval: 30 * 1e3,
701
+ // 30 seconds
600
702
  runUnderReviewInterval: 60 * 1e3
703
+ // 60 seconds
601
704
  };
602
705
 
603
706
  // src/lib/api/ci-greenlight.ts
604
- async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { runId }, { fetch: localFetch, log }) {
707
+ async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, {
708
+ runId
709
+ }, { fetch: localFetch, log }) {
605
710
  try {
606
- const resp = await localFetch(new URL(`/api/v0/ci-greenlight/${encodeURIComponent(runId)}`, serviceBase), {
607
- headers: {
608
- Authorization: `Bearer ${apiKey}`,
609
- "User-Agent": userAgent
711
+ const resp = await localFetch(
712
+ new URL(
713
+ `/api/v0/ci-greenlight/${encodeURIComponent(runId)}`,
714
+ serviceBase
715
+ ),
716
+ {
717
+ headers: {
718
+ Authorization: `Bearer ${apiKey}`,
719
+ "User-Agent": userAgent
720
+ }
610
721
  }
611
- });
722
+ );
612
723
  if (resp.ok) {
613
724
  const greenlightStatus = await resp.json();
614
725
  return {
@@ -658,14 +769,22 @@ __name(fetchCiGreenlightStatus, "fetchCiGreenlightStatus");
658
769
  // src/lib/sdk/domain/pollCiGreenlight/iterator.ts
659
770
  async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...pollConfig }) {
660
771
  const { log } = deps;
661
- const { maxRetries = pollCiGreenlightDefaults.maxRetries, pollTimeout = pollCiGreenlightDefaults.pollTimeout, retryInterval = pollCiGreenlightDefaults.retryInterval, runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval, runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval } = pollConfig;
772
+ const {
773
+ maxRetries = pollCiGreenlightDefaults.maxRetries,
774
+ pollTimeout = pollCiGreenlightDefaults.pollTimeout,
775
+ retryInterval = pollCiGreenlightDefaults.retryInterval,
776
+ runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval,
777
+ runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval
778
+ } = pollConfig;
662
779
  let retries = 0;
663
780
  let previousStatus = void 0;
664
781
  const startedAt = Date.now();
665
782
  while (Date.now() - startedAt <= pollTimeout) {
666
- const responseState = await fetchCiGreenlightStatus(apiConfig, {
667
- runId
668
- }, deps);
783
+ const responseState = await fetchCiGreenlightStatus(
784
+ apiConfig,
785
+ { runId },
786
+ deps
787
+ );
669
788
  const elapsedMs = Date.now() - startedAt;
670
789
  if (!responseState.ok && !responseState.canRetry) {
671
790
  yield {
@@ -712,7 +831,8 @@ async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...
712
831
  }
713
832
  const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
714
833
  if (errorType === "network") log.warn(`\u274C Network error${retryLabel}`);
715
- else if (errorType === "not-found") log.info(`\u23F3 Run not found${retryLabel}`);
834
+ else if (errorType === "not-found")
835
+ log.info(`\u23F3 Run not found${retryLabel}`);
716
836
  else if (errorType === "server") log.warn(`\u274C Server error${retryLabel}`);
717
837
  await sleep(retryInterval);
718
838
  continue;
@@ -749,7 +869,9 @@ async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...
749
869
  await sleep(runUnderReviewInterval);
750
870
  break;
751
871
  default:
752
- throw Error(`Unexpected run stage: ${greenlightStatus}`);
872
+ throw Error(
873
+ `Unexpected run stage: ${greenlightStatus}`
874
+ );
753
875
  }
754
876
  }
755
877
  yield {
@@ -766,12 +888,19 @@ __name(makePollCiGreenlightStatusIterator, "makePollCiGreenlightStatusIterator")
766
888
  function buildSuccessLogMessage(status) {
767
889
  const { nonBlockingBugsCount, relevantRunUrl } = status;
768
890
  const urlInfo = `See ${relevantRunUrl}`;
769
- return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
891
+ return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
892
+ nonBlockingBugsCount
893
+ )} found` : ""}.
770
894
  ${urlInfo}`;
771
895
  }
772
896
  __name(buildSuccessLogMessage, "buildSuccessLogMessage");
773
897
  function buildFailedLogMessage(status) {
774
- const { blockingBugsCount, nonBlockingBugsCount, relevantRunWithBugsUrl, reproducedBugs } = status;
898
+ const {
899
+ blockingBugsCount,
900
+ nonBlockingBugsCount,
901
+ relevantRunWithBugsUrl,
902
+ reproducedBugs
903
+ } = status;
775
904
  const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
776
905
  const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
777
906
  const blockingBugList = blockingBugUrls.map((url) => `
@@ -781,7 +910,11 @@ function buildFailedLogMessage(status) {
781
910
  const bugsListInText = `${blockingBugUrls.length > 0 ? `
782
911
  BLOCKING BUGS:${blockingBugList}` : ""}${nonBlockingBugUrls.length > 0 ? `
783
912
  NON BLOCKING BUGS:${nonBlockingBugList}` : ""}`;
784
- return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
913
+ return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(
914
+ blockingBugsCount
915
+ )} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
916
+ nonBlockingBugsCount
917
+ )}` : ""}.
785
918
  See ${relevantRunWithBugsUrl}
786
919
  You may review the priority of certain bugs to mark these non-blocking and retry the job.
787
920
  ` + bugsListInText;
@@ -812,7 +945,9 @@ ${urlInfo}`;
812
945
  case "underReview":
813
946
  return `\u{1F575}\uFE0F Run is under review... ${urlInfo}`;
814
947
  case "completed":
815
- throw Error("buildRunStageLogMessage should not be called for completed stage");
948
+ throw Error(
949
+ "buildRunStageLogMessage should not be called for completed stage"
950
+ );
816
951
  default:
817
952
  throw Error(`Unexpected run stage: ${runStage}`);
818
953
  }
@@ -825,21 +960,27 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
825
960
  const { abortReason, httpStatus } = iteration;
826
961
  switch (abortReason) {
827
962
  case "4XX-client-error":
828
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
963
+ log.error(
964
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
965
+ );
829
966
  return {
830
967
  abortReason,
831
968
  httpStatus,
832
969
  outcome: "aborted"
833
970
  };
834
971
  case "5XX-server-error":
835
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
972
+ log.error(
973
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
974
+ );
836
975
  return {
837
976
  abortReason,
838
977
  httpStatus,
839
978
  outcome: "aborted"
840
979
  };
841
980
  case "404-run-not-found":
842
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
981
+ log.error(
982
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
983
+ );
843
984
  return {
844
985
  abortReason,
845
986
  httpStatus,
@@ -867,7 +1008,9 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
867
1008
  outcome: "aborted"
868
1009
  };
869
1010
  case "XXX-other-http-code":
870
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
1011
+ log.error(
1012
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
1013
+ );
871
1014
  return {
872
1015
  abortReason,
873
1016
  httpStatus,
@@ -880,15 +1023,23 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
880
1023
  __name(mapIteratorAbortToGreenlightStatus, "mapIteratorAbortToGreenlightStatus");
881
1024
 
882
1025
  // src/lib/sdk/domain/pollCiGreenlight/index.ts
883
- async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
1026
+ async function pollCiGreenlightStatus(deps, apiConfig, {
1027
+ onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"),
1028
+ runId,
1029
+ ...pollConfig
1030
+ }) {
884
1031
  const { log } = deps;
885
- const { abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded, pollTimeout = pollCiGreenlightDefaults.pollTimeout } = pollConfig;
1032
+ const {
1033
+ abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded,
1034
+ pollTimeout = pollCiGreenlightDefaults.pollTimeout
1035
+ } = pollConfig;
886
1036
  const iterator = makePollCiGreenlightStatusIterator(deps, apiConfig, {
887
1037
  runId,
888
1038
  ...pollConfig
889
1039
  });
890
1040
  for await (const lastPoll of iterator) {
891
- if (lastPoll.isAborted) return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
1041
+ if (lastPoll.isAborted)
1042
+ return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
892
1043
  const { previousStatus, stageChanged, status } = lastPoll;
893
1044
  if (stageChanged) {
894
1045
  try {
@@ -901,7 +1052,10 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
901
1052
  const isSuperseded = rootRunId !== relevantRunId;
902
1053
  const shouldAbortOnSuperseded = isSuperseded && abortOnSuperseded;
903
1054
  if (shouldAbortOnSuperseded) {
904
- const message = buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl);
1055
+ const message = buildSupersededAbortLogMessage(
1056
+ rootRunUrl,
1057
+ relevantRunUrl
1058
+ );
905
1059
  log.warn(message);
906
1060
  return {
907
1061
  abortReason: "superseded-run",
@@ -910,7 +1064,10 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
910
1064
  };
911
1065
  }
912
1066
  if (isSuperseded) {
913
- const message = buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl);
1067
+ const message = buildSupersededContinueLogMessage(
1068
+ rootRunUrl,
1069
+ relevantRunUrl
1070
+ );
914
1071
  log.info(message);
915
1072
  }
916
1073
  switch (runStage) {
@@ -950,10 +1107,19 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
950
1107
  __name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
951
1108
 
952
1109
  // src/lib/sdk/index.ts
953
- function makeQaWolfSdk({ apiKey, serviceBase = defaultServiceBase, userAgent = defaultUserAgent }, { fetch = defaultFetch, log = defaultLogDriver } = {}) {
1110
+ function makeQaWolfSdk({
1111
+ apiKey,
1112
+ serviceBase = defaultServiceBase,
1113
+ userAgent = defaultUserAgent
1114
+ }, {
1115
+ fetch = defaultFetch,
1116
+ log = defaultLogDriver
1117
+ } = {}) {
954
1118
  if (typeof fetch !== "function") {
955
- throw Error(`QA Wolf CI-SDK requires fetch to be defined. Make sure you are using NodeJS 18+, OR pass a fetch polyfill to the makeQaWolfSdk function.
956
- We recommend 'undici' package for that purpose. See the Requirement section of our README for more guidance.`);
1119
+ throw Error(
1120
+ `QA Wolf CI-SDK requires fetch to be defined. Make sure you are using NodeJS 18+, OR pass a fetch polyfill to the makeQaWolfSdk function.
1121
+ We recommend 'undici' package for that purpose. See the Requirement section of our README for more guidance.`
1122
+ );
957
1123
  }
958
1124
  const deps = {
959
1125
  // Versions of fetch may have different default timeouts. For consistency, we explicitly set it to 2 minutes here.
@@ -962,22 +1128,26 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
962
1128
  fetch: buildFetchWithTimeout(fetch, 12e4),
963
1129
  log
964
1130
  };
965
- const apiConfig = {
966
- apiKey,
967
- serviceBase,
968
- userAgent
969
- };
1131
+ const apiConfig = { apiKey, serviceBase, userAgent };
970
1132
  return {
971
1133
  attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
972
- generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(null, deps, apiConfig),
1134
+ generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(
1135
+ null,
1136
+ deps,
1137
+ apiConfig
1138
+ ),
973
1139
  generateSignedUrlForTempTeamStorage: generateSignedUrlForTempTeamStorage.bind(null, deps, apiConfig),
974
- makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(null, deps, apiConfig),
1140
+ makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(
1141
+ null,
1142
+ deps,
1143
+ apiConfig
1144
+ ),
975
1145
  notifyTerminatedEphemeralEnvironment: notifyTerminatedEphemeralEnvironment.bind(null, deps, apiConfig),
976
1146
  pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
977
1147
  };
978
1148
  }
979
1149
  __name(makeQaWolfSdk, "makeQaWolfSdk");
980
-
981
- export { makeQaWolfSdk };
1150
+ export {
1151
+ makeQaWolfSdk
1152
+ };
982
1153
  //# sourceMappingURL=index.js.map
983
- //# sourceMappingURL=index.js.map