@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/README.md +22 -1
- package/dist/index.cjs +211 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -35
- package/dist/index.d.ts +111 -35
- package/dist/index.js +211 -67
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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) => `
|