@qawolf/ci-sdk 1.3.2 → 2.1.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.
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
39
39
 
40
40
  // package.json
41
41
  var package_default = {
42
- version: "1.3.2"};
42
+ version: "2.1.0"};
43
43
 
44
44
  // src/lib/sdk/defaults/userAgent.ts
45
45
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
@@ -74,11 +74,195 @@ async function logFetchError({ log, methodName, response }) {
74
74
  }
75
75
  __name(logFetchError, "logFetchError");
76
76
 
77
+ // src/lib/sdk/domain/attemptDeploy/handleSuccessResponse.ts
78
+ async function handleSuccessResponse(data, log) {
79
+ const { environment, results } = data;
80
+ const skippedTriggers = data.skippedTriggers ?? [];
81
+ const skippedDeployment = data.skippedDeployment;
82
+ const isPreviewDeployment = environment && !environment.isStatic;
83
+ if (environment) {
84
+ const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
85
+ log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
86
+ }
87
+ if (results.length === 0) {
88
+ const skipMessages = [
89
+ ...skippedTriggers.map((trigger) => trigger.message),
90
+ ...skippedDeployment ? [
91
+ skippedDeployment.message
92
+ ] : []
93
+ ];
94
+ const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
95
+ if (isPreviewDeployment) {
96
+ log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`);
97
+ return {
98
+ environmentId: environment.id,
99
+ outcome: "success",
100
+ runId: void 0
101
+ };
102
+ }
103
+ if (skippedTriggers.length > 0) {
104
+ log.error(`\u{1F6AB} No run was created for this deployment.${skipDetail}`);
105
+ return {
106
+ failReason: "trigger-skipped",
107
+ outcome: "failed",
108
+ skippedTriggers
109
+ };
110
+ }
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.`);
112
+ return {
113
+ outcome: "skipped",
114
+ skippedDeployment
115
+ };
116
+ }
117
+ if (!environment) {
118
+ log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
119
+ return {
120
+ failReason: "no-environment",
121
+ outcome: "failed"
122
+ };
123
+ }
124
+ const unrecognizedResult = results.find((result) => "unrecognized" in result);
125
+ 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
+ };
131
+ }
132
+ const failedTrigger = results.find((result) => "failureReason" in result);
133
+ 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
+ };
139
+ }
140
+ const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
141
+ if (duplicateSuite) {
142
+ log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`);
143
+ return {
144
+ environmentId: environment.id,
145
+ outcome: "success",
146
+ runId: duplicateSuite.duplicateSuiteId
147
+ };
148
+ }
149
+ const successfulTrigger = results.find((result) => "createdSuiteId" in result);
150
+ if (!successfulTrigger) {
151
+ 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
+ };
156
+ }
157
+ log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`);
158
+ return {
159
+ environmentId: environment.id,
160
+ outcome: "success",
161
+ runId: successfulTrigger.createdSuiteId
162
+ };
163
+ }
164
+ __name(handleSuccessResponse, "handleSuccessResponse");
165
+
166
+ // src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
167
+ 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
+ };
186
+ }
187
+ return {
188
+ triggerId: result.trigger_id,
189
+ unrecognized: true
190
+ };
191
+ }
192
+ __name(mapSuiteResult, "mapSuiteResult");
193
+ function mapDeploySuccessResponse(wire) {
194
+ return {
195
+ environment: wire.environment,
196
+ results: wire.results.map(mapSuiteResult),
197
+ skippedDeployment: wire.skipped_deployment && {
198
+ message: wire.skipped_deployment.message,
199
+ reason: wire.skipped_deployment.reason
200
+ },
201
+ skippedTriggers: wire.skipped_triggers?.map((trigger) => ({
202
+ environmentId: trigger.environment_id,
203
+ message: trigger.message,
204
+ reason: trigger.reason,
205
+ triggerId: trigger.trigger_id
206
+ }))
207
+ };
208
+ }
209
+ __name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
210
+
211
+ // src/lib/sdk/domain/attemptDeploy/parseDeploySuccessWireResponse.ts
212
+ function isRecord(value) {
213
+ return typeof value === "object" && value !== null && !Array.isArray(value);
214
+ }
215
+ __name(isRecord, "isRecord");
216
+ function isWireSuiteResult(value) {
217
+ if (!isRecord(value)) return false;
218
+ 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";
223
+ }
224
+ return true;
225
+ }
226
+ __name(isWireSuiteResult, "isWireSuiteResult");
227
+ function isEnvironment(value) {
228
+ if (!isRecord(value)) return false;
229
+ if (typeof value.id !== "string") return false;
230
+ if (typeof value.isStatic !== "boolean") return false;
231
+ if (value.alias !== void 0 && typeof value.alias !== "string") return false;
232
+ return true;
233
+ }
234
+ __name(isEnvironment, "isEnvironment");
235
+ function isSkippedDeployment(value) {
236
+ if (!isRecord(value)) return false;
237
+ return typeof value.message === "string" && typeof value.reason === "string";
238
+ }
239
+ __name(isSkippedDeployment, "isSkippedDeployment");
240
+ function isSkippedTrigger(value) {
241
+ if (!isRecord(value)) return false;
242
+ return typeof value.environment_id === "string" && typeof value.message === "string" && typeof value.reason === "string" && typeof value.trigger_id === "string";
243
+ }
244
+ __name(isSkippedTrigger, "isSkippedTrigger");
245
+ function isDeploySuccessWireResponse(value) {
246
+ 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;
251
+ return true;
252
+ }
253
+ __name(isDeploySuccessWireResponse, "isDeploySuccessWireResponse");
254
+ function parseDeploySuccessWireResponse(value) {
255
+ return isDeploySuccessWireResponse(value) ? value : "invalid";
256
+ }
257
+ __name(parseDeploySuccessWireResponse, "parseDeploySuccessWireResponse");
258
+
77
259
  // src/lib/sdk/domain/attemptDeploy/index.ts
78
260
  async function attemptNotifyDeploy(deps, apiConfig, config) {
79
261
  const { fetch: localFetch, log } = deps;
262
+ let response;
263
+ let bodyText;
80
264
  try {
81
- const response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
265
+ response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
82
266
  body: JSON.stringify({
83
267
  branch: config.branch,
84
268
  commit_url: config.commitUrl,
@@ -107,8 +291,7 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
107
291
  signal: AbortSignal.timeout(5 * 60 * 1e3)
108
292
  });
109
293
  if (!response.ok) return handleErrorResponse(response, log);
110
- const data = await response.json();
111
- return handleSuccessResponse(data, log);
294
+ bodyText = await response.text();
112
295
  } catch {
113
296
  log.error(`\u{1F6AB} Network error from deploy_success, aborting.`);
114
297
  return {
@@ -117,8 +300,29 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
117
300
  outcome: "aborted"
118
301
  };
119
302
  }
303
+ let rawBody;
304
+ try {
305
+ rawBody = JSON.parse(bodyText);
306
+ } catch {
307
+ return abortInvalidResponseBody(log, "the response body was not JSON");
308
+ }
309
+ const wire = parseDeploySuccessWireResponse(rawBody);
310
+ if (wire === "invalid") {
311
+ return abortInvalidResponseBody(log, "the response body did not match the expected shape");
312
+ }
313
+ const data = mapDeploySuccessResponse(wire);
314
+ return handleSuccessResponse(data, log);
120
315
  }
121
316
  __name(attemptNotifyDeploy, "attemptNotifyDeploy");
317
+ function abortInvalidResponseBody(log, detail) {
318
+ log.error(`\u{1F6AB} Invalid response from deploy_success (${detail}), aborting.`);
319
+ return {
320
+ abortReason: "invalid-response-body",
321
+ httpStatus: void 0,
322
+ outcome: "aborted"
323
+ };
324
+ }
325
+ __name(abortInvalidResponseBody, "abortInvalidResponseBody");
122
326
  async function handleErrorResponse(response, log) {
123
327
  const status = response.status;
124
328
  let abortReason;
@@ -139,68 +343,6 @@ async function handleErrorResponse(response, log) {
139
343
  };
140
344
  }
141
345
  __name(handleErrorResponse, "handleErrorResponse");
142
- async function handleSuccessResponse(data, log) {
143
- const { environment, results } = data;
144
- const isPreviewDeployment = environment && !environment.isStatic;
145
- if (environment) {
146
- const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
147
- log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
148
- }
149
- if (results.length === 0) {
150
- if (isPreviewDeployment) {
151
- log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.`);
152
- return {
153
- environmentId: environment.id,
154
- outcome: "success",
155
- runId: void 0
156
- };
157
- }
158
- log.error(`\u{1F6AB} No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`);
159
- return {
160
- failReason: "no-matched-trigger",
161
- outcome: "failed"
162
- };
163
- }
164
- if (!environment) {
165
- log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
166
- return {
167
- failReason: "no-environment",
168
- outcome: "failed"
169
- };
170
- }
171
- const failedTrigger = results.find((t) => "failure_reason" in t);
172
- if (failedTrigger) {
173
- log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failure_reason}". Please contact support.`);
174
- return {
175
- failReason: "run-could-not-be-created",
176
- outcome: "failed"
177
- };
178
- }
179
- const duplicateSuite = results.find((t) => "duplicate_suite_id" in t);
180
- if (duplicateSuite) {
181
- log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicate_suite_id} already exists for this deployment.`);
182
- return {
183
- environmentId: environment.id,
184
- outcome: "success",
185
- runId: duplicateSuite.duplicate_suite_id
186
- };
187
- }
188
- const successfulTrigger = results.find((t) => "created_suite_id" in t);
189
- if (!successfulTrigger) {
190
- log.error(`\u{1F6AB} No trigger matched the request. Please contact support.`);
191
- return {
192
- failReason: "no-matched-trigger",
193
- outcome: "failed"
194
- };
195
- }
196
- log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.created_suite_id}`);
197
- return {
198
- environmentId: environment.id,
199
- outcome: "success",
200
- runId: successfulTrigger.created_suite_id
201
- };
202
- }
203
- __name(handleSuccessResponse, "handleSuccessResponse");
204
346
 
205
347
  // src/lib/api/generate-signed-url-for-run-inputs-executables.ts
206
348
  async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
@@ -605,7 +747,9 @@ ${urlInfo}`;
605
747
  }
606
748
  __name(buildSuccessLogMessage, "buildSuccessLogMessage");
607
749
  function buildFailedLogMessage(status) {
608
- const { blockingBugsCount, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [], relevantRunWithBugsUrl } = status;
750
+ const { blockingBugsCount, nonBlockingBugsCount, relevantRunWithBugsUrl, reproducedBugs } = status;
751
+ const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
752
+ const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
609
753
  const blockingBugList = blockingBugUrls.map((url) => `
610
754
  \u2022 ${url}`).join("");
611
755
  const nonBlockingBugList = nonBlockingBugUrls.map((url) => `