@qawolf/ci-sdk 2.8.0 → 3.0.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 +10 -3
- package/dist/index.js +7 -9
- package/dist/index.js.map +2 -2
- package/package.json +6 -8
- package/dist/index.cjs +0 -1185
- package/dist/index.cjs.map +0 -7
package/dist/index.cjs
DELETED
|
@@ -1,1185 +0,0 @@
|
|
|
1
|
-
/* Built with @qawolf/platform-builder@1.2.3 */
|
|
2
|
-
"use strict";
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
-
|
|
22
|
-
// src/index.ts
|
|
23
|
-
var index_exports = {};
|
|
24
|
-
__export(index_exports, {
|
|
25
|
-
makeQaWolfSdk: () => makeQaWolfSdk
|
|
26
|
-
});
|
|
27
|
-
module.exports = __toCommonJS(index_exports);
|
|
28
|
-
|
|
29
|
-
// src/lib/other.ts
|
|
30
|
-
function sleep(ms) {
|
|
31
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
|
-
}
|
|
33
|
-
__name(sleep, "sleep");
|
|
34
|
-
function pluralize(count) {
|
|
35
|
-
return count === 1 ? "" : "s";
|
|
36
|
-
}
|
|
37
|
-
__name(pluralize, "pluralize");
|
|
38
|
-
var buildFetchWithTimeout = /* @__PURE__ */ __name((fetch, defaultTimeoutMs) => async (...args) => {
|
|
39
|
-
const signal = args[1]?.signal ?? AbortSignal.timeout(defaultTimeoutMs);
|
|
40
|
-
return fetch(args[0], { ...args[1], signal });
|
|
41
|
-
}, "buildFetchWithTimeout");
|
|
42
|
-
|
|
43
|
-
// src/lib/sdk/defaults/fetch.ts
|
|
44
|
-
var defaultFetch = globalThis.fetch;
|
|
45
|
-
|
|
46
|
-
// src/lib/sdk/defaults/log.ts
|
|
47
|
-
var defaultLogDriver = {
|
|
48
|
-
error(message, error) {
|
|
49
|
-
console.error(message, error);
|
|
50
|
-
},
|
|
51
|
-
info(message) {
|
|
52
|
-
console.log(message);
|
|
53
|
-
},
|
|
54
|
-
warn(message) {
|
|
55
|
-
console.warn(message);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// src/lib/sdk/defaults/serviceBase.ts
|
|
60
|
-
var defaultServiceBase = "https://app.qawolf.com/";
|
|
61
|
-
|
|
62
|
-
// package.json
|
|
63
|
-
var package_default = {
|
|
64
|
-
name: "@qawolf/ci-sdk",
|
|
65
|
-
version: "2.8.0",
|
|
66
|
-
private: false,
|
|
67
|
-
description: "A simple SDK for interacting with QAWolf in CI scripts.",
|
|
68
|
-
keywords: [],
|
|
69
|
-
license: "MIT",
|
|
70
|
-
author: "Jules Sam. Randolph<jules@qawolf.com>",
|
|
71
|
-
files: [
|
|
72
|
-
"/dist",
|
|
73
|
-
"/README.md"
|
|
74
|
-
],
|
|
75
|
-
type: "module",
|
|
76
|
-
sideEffects: false,
|
|
77
|
-
exports: {
|
|
78
|
-
".": {
|
|
79
|
-
types: "./dist/index.d.ts",
|
|
80
|
-
require: "./dist/index.cjs",
|
|
81
|
-
import: "./dist/index.js",
|
|
82
|
-
default: "./dist/index.js"
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
scripts: {
|
|
86
|
-
build: "platform-builder --config platformBuilder.config.ts --config platformBuilder.cjs.config.ts",
|
|
87
|
-
"lint:ci": "npm run lint:first-half; FIRST_HALF_EXIT=$?; npm run lint:second-half && exit $FIRST_HALF_EXIT",
|
|
88
|
-
"lint:dev": "npm run lint:first-half",
|
|
89
|
-
"lint:first-half": "eslint . --fix --quiet && oxfmt --write .",
|
|
90
|
-
"lint:second-half": "dpdm --exit-code circular:1 --no-tree --no-warning --transform --include='.*' --exclude='/(dist|node_modules)/' '**/*'",
|
|
91
|
-
prepublishOnly: "nx run ci-sdk:build",
|
|
92
|
-
test: 'NODE_OPTIONS="--experimental-vm-modules" NODE_NO_WARNINGS=1 jest',
|
|
93
|
-
"tsc:check": "tsc --noEmit"
|
|
94
|
-
},
|
|
95
|
-
dependencies: {
|
|
96
|
-
"@qawolf/ci-utils": "^1.1.2",
|
|
97
|
-
"@sinonjs/fake-timers": "^10.3.0",
|
|
98
|
-
tslib: "^2.6.2"
|
|
99
|
-
},
|
|
100
|
-
devDependencies: {
|
|
101
|
-
"@qawolf/platform-builder": "^1.2.3",
|
|
102
|
-
tsx: "^4.19.4",
|
|
103
|
-
typescript: "^5.9.3"
|
|
104
|
-
},
|
|
105
|
-
engines: {
|
|
106
|
-
node: ">=20"
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// src/lib/sdk/defaults/userAgent.ts
|
|
111
|
-
var defaultUserAgent = `ci-sdk/${package_default.version}`;
|
|
112
|
-
|
|
113
|
-
// src/lib/api/log.ts
|
|
114
|
-
async function logFetchError({
|
|
115
|
-
log,
|
|
116
|
-
methodName,
|
|
117
|
-
response
|
|
118
|
-
}) {
|
|
119
|
-
if (response.ok) return;
|
|
120
|
-
const { eventId, failureMessage } = await (async () => {
|
|
121
|
-
try {
|
|
122
|
-
const body = await response.json().catch(() => response.text());
|
|
123
|
-
if (typeof body === "object" && body !== null) {
|
|
124
|
-
return {
|
|
125
|
-
eventId: body.eventId,
|
|
126
|
-
failureMessage: body.failureMessage
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
eventId: void 0,
|
|
131
|
-
failureMessage: body
|
|
132
|
-
};
|
|
133
|
-
} catch {
|
|
134
|
-
return {
|
|
135
|
-
eventId: void 0,
|
|
136
|
-
failureMessage: void 0
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
})();
|
|
140
|
-
const eventIdSuffix = eventId ? ` (Event ID: ${eventId})` : "";
|
|
141
|
-
const messageSuffix = failureMessage ? `: ${failureMessage}` : "";
|
|
142
|
-
const statusText = response.status >= 500 ? "Server Error" : response.statusText || "Unknown Error";
|
|
143
|
-
log.error(
|
|
144
|
-
`\u274C [${methodName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
__name(logFetchError, "logFetchError");
|
|
148
|
-
|
|
149
|
-
// src/lib/sdk/domain/attemptDeploy/handleSuccessResponse.ts
|
|
150
|
-
async function handleSuccessResponse(data, log) {
|
|
151
|
-
const { environment, results } = data;
|
|
152
|
-
const skippedTriggers = data.skippedTriggers ?? [];
|
|
153
|
-
const skippedDeployment = data.skippedDeployment;
|
|
154
|
-
const isPreviewDeployment = environment && !environment.isStatic;
|
|
155
|
-
if (data.warning) log.warn(`\u26A0\uFE0F ${data.warning}`);
|
|
156
|
-
if (environment) {
|
|
157
|
-
const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
|
|
158
|
-
log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
|
|
159
|
-
}
|
|
160
|
-
if (results.length === 0) {
|
|
161
|
-
const skipMessages = [
|
|
162
|
-
...skippedTriggers.map((trigger) => trigger.message),
|
|
163
|
-
...skippedDeployment ? [skippedDeployment.message] : []
|
|
164
|
-
];
|
|
165
|
-
const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
|
|
166
|
-
if (isPreviewDeployment) {
|
|
167
|
-
log.info(
|
|
168
|
-
`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`
|
|
169
|
-
);
|
|
170
|
-
return {
|
|
171
|
-
environmentId: environment.id,
|
|
172
|
-
outcome: "success",
|
|
173
|
-
runId: void 0
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
if (skippedTriggers.length > 0) {
|
|
177
|
-
log.error(`\u{1F6AB} No run was created for this deployment.${skipDetail}`);
|
|
178
|
-
return {
|
|
179
|
-
failReason: "trigger-skipped",
|
|
180
|
-
outcome: "failed",
|
|
181
|
-
skippedTriggers
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
log.warn(
|
|
185
|
-
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.`
|
|
186
|
-
);
|
|
187
|
-
return {
|
|
188
|
-
outcome: "skipped",
|
|
189
|
-
skippedDeployment
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
if (!environment) {
|
|
193
|
-
log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
|
|
194
|
-
return { failReason: "no-environment", outcome: "failed" };
|
|
195
|
-
}
|
|
196
|
-
const unrecognizedResult = results.find((result) => "unrecognized" in result);
|
|
197
|
-
if (unrecognizedResult) {
|
|
198
|
-
log.error(
|
|
199
|
-
`\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`
|
|
200
|
-
);
|
|
201
|
-
return { failReason: "unknown-result", outcome: "failed" };
|
|
202
|
-
}
|
|
203
|
-
const failedTrigger = results.find((result) => "failureReason" in result);
|
|
204
|
-
if (failedTrigger) {
|
|
205
|
-
log.error(
|
|
206
|
-
`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureMessage ?? failedTrigger.failureReason}". Please contact support.`
|
|
207
|
-
);
|
|
208
|
-
return { failReason: "run-could-not-be-created", outcome: "failed" };
|
|
209
|
-
}
|
|
210
|
-
const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
|
|
211
|
-
if (duplicateSuite) {
|
|
212
|
-
log.info(
|
|
213
|
-
`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`
|
|
214
|
-
);
|
|
215
|
-
return {
|
|
216
|
-
environmentId: environment.id,
|
|
217
|
-
outcome: "success",
|
|
218
|
-
runId: duplicateSuite.duplicateSuiteId
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
const successfulTrigger = results.find(
|
|
222
|
-
(result) => "createdSuiteId" in result
|
|
223
|
-
);
|
|
224
|
-
if (!successfulTrigger) {
|
|
225
|
-
log.error(`\u{1F6AB} Run could not be created. Please contact support.`);
|
|
226
|
-
return { failReason: "run-could-not-be-created", outcome: "failed" };
|
|
227
|
-
}
|
|
228
|
-
log.info(
|
|
229
|
-
`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`
|
|
230
|
-
);
|
|
231
|
-
return {
|
|
232
|
-
environmentId: environment.id,
|
|
233
|
-
outcome: "success",
|
|
234
|
-
runId: successfulTrigger.createdSuiteId
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
__name(handleSuccessResponse, "handleSuccessResponse");
|
|
238
|
-
|
|
239
|
-
// src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
|
|
240
|
-
function mapSuiteResult(result) {
|
|
241
|
-
switch (result.outcome) {
|
|
242
|
-
case "created":
|
|
243
|
-
return {
|
|
244
|
-
createdSuiteId: result.created_suite_id,
|
|
245
|
-
triggerId: result.trigger_id
|
|
246
|
-
};
|
|
247
|
-
case "failed":
|
|
248
|
-
return {
|
|
249
|
-
failureCode: result.failure_code,
|
|
250
|
-
failureMessage: result.failure_message,
|
|
251
|
-
failureReason: result.failure_reason,
|
|
252
|
-
triggerId: result.trigger_id
|
|
253
|
-
};
|
|
254
|
-
case "skipped":
|
|
255
|
-
return {
|
|
256
|
-
duplicateSuiteId: result.duplicate_suite_id,
|
|
257
|
-
skipReason: result.skip_reason,
|
|
258
|
-
triggerId: result.trigger_id
|
|
259
|
-
};
|
|
260
|
-
default:
|
|
261
|
-
return { triggerId: result.trigger_id, unrecognized: true };
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
__name(mapSuiteResult, "mapSuiteResult");
|
|
265
|
-
function mapDeploySuccessResponse(wire) {
|
|
266
|
-
return {
|
|
267
|
-
environment: wire.environment,
|
|
268
|
-
results: wire.results.map(mapSuiteResult),
|
|
269
|
-
skippedDeployment: wire.skipped_deployment && {
|
|
270
|
-
message: wire.skipped_deployment.message,
|
|
271
|
-
reason: wire.skipped_deployment.reason
|
|
272
|
-
},
|
|
273
|
-
skippedTriggers: wire.skipped_triggers?.map((trigger) => ({
|
|
274
|
-
environmentId: trigger.environment_id,
|
|
275
|
-
message: trigger.message,
|
|
276
|
-
reason: trigger.reason,
|
|
277
|
-
triggerId: trigger.trigger_id
|
|
278
|
-
})),
|
|
279
|
-
warning: wire.warning
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
__name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
|
|
283
|
-
|
|
284
|
-
// src/lib/sdk/domain/attemptDeploy/parseDeploySuccessWireResponse.ts
|
|
285
|
-
function isRecord(value) {
|
|
286
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
287
|
-
}
|
|
288
|
-
__name(isRecord, "isRecord");
|
|
289
|
-
function isOptionalString(value) {
|
|
290
|
-
return value === void 0 || typeof value === "string";
|
|
291
|
-
}
|
|
292
|
-
__name(isOptionalString, "isOptionalString");
|
|
293
|
-
function isWireSuiteResult(value) {
|
|
294
|
-
if (!isRecord(value)) return false;
|
|
295
|
-
if (typeof value.trigger_id !== "string") return false;
|
|
296
|
-
if (!isOptionalString(value.outcome)) return false;
|
|
297
|
-
switch (value.outcome) {
|
|
298
|
-
case "created":
|
|
299
|
-
return typeof value.created_suite_id === "string";
|
|
300
|
-
case "failed":
|
|
301
|
-
return typeof value.failure_reason === "string" && isOptionalString(value.failure_code) && isOptionalString(value.failure_message);
|
|
302
|
-
case "skipped":
|
|
303
|
-
return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
|
|
304
|
-
default:
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
__name(isWireSuiteResult, "isWireSuiteResult");
|
|
309
|
-
function isEnvironment(value) {
|
|
310
|
-
if (!isRecord(value)) return false;
|
|
311
|
-
if (typeof value.id !== "string") return false;
|
|
312
|
-
if (typeof value.isStatic !== "boolean") return false;
|
|
313
|
-
if (value.alias !== void 0 && typeof value.alias !== "string")
|
|
314
|
-
return false;
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
__name(isEnvironment, "isEnvironment");
|
|
318
|
-
function isSkippedDeployment(value) {
|
|
319
|
-
if (!isRecord(value)) return false;
|
|
320
|
-
return typeof value.message === "string" && typeof value.reason === "string";
|
|
321
|
-
}
|
|
322
|
-
__name(isSkippedDeployment, "isSkippedDeployment");
|
|
323
|
-
function isSkippedTrigger(value) {
|
|
324
|
-
if (!isRecord(value)) return false;
|
|
325
|
-
return typeof value.environment_id === "string" && typeof value.message === "string" && typeof value.reason === "string" && typeof value.trigger_id === "string";
|
|
326
|
-
}
|
|
327
|
-
__name(isSkippedTrigger, "isSkippedTrigger");
|
|
328
|
-
function isDeploySuccessWireResponse(value) {
|
|
329
|
-
if (!isRecord(value)) return false;
|
|
330
|
-
if (value.environment !== void 0 && !isEnvironment(value.environment))
|
|
331
|
-
return false;
|
|
332
|
-
if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult))
|
|
333
|
-
return false;
|
|
334
|
-
if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment))
|
|
335
|
-
return false;
|
|
336
|
-
if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger)))
|
|
337
|
-
return false;
|
|
338
|
-
if (!isOptionalString(value.warning)) return false;
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
__name(isDeploySuccessWireResponse, "isDeploySuccessWireResponse");
|
|
342
|
-
function parseDeploySuccessWireResponse(value) {
|
|
343
|
-
return isDeploySuccessWireResponse(value) ? value : "invalid";
|
|
344
|
-
}
|
|
345
|
-
__name(parseDeploySuccessWireResponse, "parseDeploySuccessWireResponse");
|
|
346
|
-
|
|
347
|
-
// src/lib/sdk/domain/attemptDeploy/index.ts
|
|
348
|
-
async function attemptNotifyDeploy(deps, apiConfig, config) {
|
|
349
|
-
const { fetch: localFetch, log } = deps;
|
|
350
|
-
let response;
|
|
351
|
-
let bodyText;
|
|
352
|
-
try {
|
|
353
|
-
response = await localFetch(
|
|
354
|
-
new URL("/api/webhooks/deploy_success", apiConfig.serviceBase),
|
|
355
|
-
{
|
|
356
|
-
body: JSON.stringify({
|
|
357
|
-
branch: config.branch,
|
|
358
|
-
commit_url: config.commitUrl,
|
|
359
|
-
deduplication_key: config.deduplicationKey,
|
|
360
|
-
deployment_type: config.deploymentType,
|
|
361
|
-
deployment_url: config.deploymentUrl,
|
|
362
|
-
..."ephemeralEnvironment" in config ? {
|
|
363
|
-
ephemeral_environment: config.ephemeralEnvironment
|
|
364
|
-
} : "hostingService" in config && {
|
|
365
|
-
hosting_service: config.hostingService,
|
|
366
|
-
merge_request_number: config.hostingService === "GitLab" ? config.mergeRequestNumber : void 0,
|
|
367
|
-
pull_request_number: config.hostingService === "GitHub" ? config.pullRequestNumber : void 0,
|
|
368
|
-
repository: config.repository
|
|
369
|
-
},
|
|
370
|
-
sha: config.sha,
|
|
371
|
-
variables: config.variables,
|
|
372
|
-
workspaceId: config.workspaceId
|
|
373
|
-
}),
|
|
374
|
-
headers: {
|
|
375
|
-
Authorization: `Bearer ${apiConfig.apiKey}`,
|
|
376
|
-
"Content-Type": "application/json",
|
|
377
|
-
"User-Agent": apiConfig.userAgent
|
|
378
|
-
},
|
|
379
|
-
method: "POST",
|
|
380
|
-
// Override default timeout to 5 minutes. This gives extra room
|
|
381
|
-
// for both (1) waiting for reconciliation (2) creating the run.
|
|
382
|
-
signal: AbortSignal.timeout(5 * 60 * 1e3)
|
|
383
|
-
}
|
|
384
|
-
);
|
|
385
|
-
if (!response.ok) return handleErrorResponse(response, log);
|
|
386
|
-
bodyText = await response.text();
|
|
387
|
-
} catch {
|
|
388
|
-
log.error(`\u{1F6AB} Network error from deploy_success, aborting.`);
|
|
389
|
-
return {
|
|
390
|
-
abortReason: "network-error",
|
|
391
|
-
httpStatus: void 0,
|
|
392
|
-
outcome: "aborted"
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
let rawBody;
|
|
396
|
-
try {
|
|
397
|
-
rawBody = JSON.parse(bodyText);
|
|
398
|
-
} catch {
|
|
399
|
-
return abortInvalidResponseBody(log, "the response body was not JSON");
|
|
400
|
-
}
|
|
401
|
-
const wire = parseDeploySuccessWireResponse(rawBody);
|
|
402
|
-
if (wire === "invalid") {
|
|
403
|
-
return abortInvalidResponseBody(
|
|
404
|
-
log,
|
|
405
|
-
"the response body did not match the expected shape"
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
const data = mapDeploySuccessResponse(wire);
|
|
409
|
-
return handleSuccessResponse(data, log);
|
|
410
|
-
}
|
|
411
|
-
__name(attemptNotifyDeploy, "attemptNotifyDeploy");
|
|
412
|
-
function abortInvalidResponseBody(log, detail) {
|
|
413
|
-
log.error(`\u{1F6AB} Invalid response from deploy_success (${detail}), aborting.`);
|
|
414
|
-
return {
|
|
415
|
-
abortReason: "invalid-response-body",
|
|
416
|
-
httpStatus: void 0,
|
|
417
|
-
outcome: "aborted"
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
__name(abortInvalidResponseBody, "abortInvalidResponseBody");
|
|
421
|
-
async function handleErrorResponse(response, log) {
|
|
422
|
-
const status = response.status;
|
|
423
|
-
let abortReason;
|
|
424
|
-
if (status === void 0) abortReason = "network-error";
|
|
425
|
-
else if (status === 401) abortReason = "401-unauthorized";
|
|
426
|
-
else if (status === 403) abortReason = "403-forbidden";
|
|
427
|
-
else if (status >= 500) abortReason = "5XX-server-error";
|
|
428
|
-
else abortReason = "4XX-client-error";
|
|
429
|
-
await logFetchError({
|
|
430
|
-
log,
|
|
431
|
-
methodName: "attemptNotifyDeploy",
|
|
432
|
-
response
|
|
433
|
-
});
|
|
434
|
-
return {
|
|
435
|
-
abortReason,
|
|
436
|
-
httpStatus: status,
|
|
437
|
-
outcome: "aborted"
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
__name(handleErrorResponse, "handleErrorResponse");
|
|
441
|
-
|
|
442
|
-
// src/lib/api/signed-url.ts
|
|
443
|
-
var signedUrlPaths = {
|
|
444
|
-
runInputsExecutables: "/api/v0/run-inputs-executables-signed-urls",
|
|
445
|
-
teamStorage: "/api/v0/team-storage-signed-url"
|
|
446
|
-
};
|
|
447
|
-
function buildSignedUrl({
|
|
448
|
-
destinationFilePath,
|
|
449
|
-
endpoint,
|
|
450
|
-
serviceBase,
|
|
451
|
-
workspaceId
|
|
452
|
-
}) {
|
|
453
|
-
const url = new URL(signedUrlPaths[endpoint], serviceBase);
|
|
454
|
-
if (destinationFilePath !== void 0)
|
|
455
|
-
url.searchParams.set("file", destinationFilePath);
|
|
456
|
-
if (workspaceId !== void 0)
|
|
457
|
-
url.searchParams.set("workspaceId", workspaceId);
|
|
458
|
-
return url;
|
|
459
|
-
}
|
|
460
|
-
__name(buildSignedUrl, "buildSignedUrl");
|
|
461
|
-
|
|
462
|
-
// src/lib/api/generate-signed-url-for-run-inputs-executables.ts
|
|
463
|
-
async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
|
|
464
|
-
try {
|
|
465
|
-
const response = await localFetch(
|
|
466
|
-
buildSignedUrl({
|
|
467
|
-
destinationFilePath,
|
|
468
|
-
endpoint: "runInputsExecutables",
|
|
469
|
-
serviceBase,
|
|
470
|
-
workspaceId
|
|
471
|
-
}),
|
|
472
|
-
{
|
|
473
|
-
headers: {
|
|
474
|
-
Authorization: `Bearer ${apiKey}`,
|
|
475
|
-
"Content-Type": "application/json",
|
|
476
|
-
"User-Agent": userAgent
|
|
477
|
-
},
|
|
478
|
-
method: "GET"
|
|
479
|
-
}
|
|
480
|
-
);
|
|
481
|
-
if (response.status === 200) {
|
|
482
|
-
const json = await response.json();
|
|
483
|
-
return {
|
|
484
|
-
fileLocation: json.fileLocation,
|
|
485
|
-
httpStatus: response.status,
|
|
486
|
-
interactiveRunFileLocation: json.interactiveRunFileLocation,
|
|
487
|
-
playgroundFileLocation: json.playgroundFileLocation,
|
|
488
|
-
signedUrl: json.signedUrl,
|
|
489
|
-
success: true
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
await logFetchError({
|
|
493
|
-
log,
|
|
494
|
-
methodName: "callGenerateSignedUrlForRunInputsExecutablesStorage",
|
|
495
|
-
response
|
|
496
|
-
});
|
|
497
|
-
return {
|
|
498
|
-
errorMessage: await response.text(),
|
|
499
|
-
httpStatus: response.status,
|
|
500
|
-
success: false
|
|
501
|
-
};
|
|
502
|
-
} catch {
|
|
503
|
-
return {
|
|
504
|
-
errorMessage: "Network error, aborting request to generate signed URL",
|
|
505
|
-
httpStatus: 0,
|
|
506
|
-
success: false
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
__name(callGenerateSignedUrlForRunInputsExecutablesStorage, "callGenerateSignedUrlForRunInputsExecutablesStorage");
|
|
511
|
-
|
|
512
|
-
// src/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-run-inputs-executables.ts
|
|
513
|
-
async function generateSignedUrlForRunInputsExecutablesStorage(deps, apiConfig, config) {
|
|
514
|
-
const { log } = deps;
|
|
515
|
-
if (!config.destinationFilePath) {
|
|
516
|
-
log.error(`\u{1F6AB} destinationFilePath is a required config parameter`);
|
|
517
|
-
return {
|
|
518
|
-
abortReason: "sdk-argument-validation-failed",
|
|
519
|
-
success: false
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
const responseState = await callGenerateSignedUrlForRunInputsExecutablesStorage(
|
|
523
|
-
apiConfig,
|
|
524
|
-
config,
|
|
525
|
-
deps
|
|
526
|
-
);
|
|
527
|
-
if (!responseState.success) {
|
|
528
|
-
let abortReason = "XXX-other-http-code";
|
|
529
|
-
if (responseState.httpStatus >= 500) {
|
|
530
|
-
abortReason = "5XX-server-error";
|
|
531
|
-
log.error(
|
|
532
|
-
`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
|
|
533
|
-
);
|
|
534
|
-
} else {
|
|
535
|
-
if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
|
|
536
|
-
else if (responseState.httpStatus === 401)
|
|
537
|
-
abortReason = "401-invalid-credentials";
|
|
538
|
-
else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
|
|
539
|
-
else if (responseState.httpStatus === 0)
|
|
540
|
-
abortReason = "client-network-error";
|
|
541
|
-
log.error(
|
|
542
|
-
`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
return {
|
|
546
|
-
abortReason,
|
|
547
|
-
httpStatus: responseState.httpStatus,
|
|
548
|
-
success: false
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
log.info(`\u2705 Generated Signed Upload URL: ${responseState.signedUrl}.`);
|
|
552
|
-
return {
|
|
553
|
-
fileLocation: responseState.fileLocation,
|
|
554
|
-
interactiveRunFileLocation: responseState.interactiveRunFileLocation,
|
|
555
|
-
playgroundFileLocation: responseState.playgroundFileLocation,
|
|
556
|
-
success: true,
|
|
557
|
-
uploadUrl: responseState.signedUrl
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
__name(generateSignedUrlForRunInputsExecutablesStorage, "generateSignedUrlForRunInputsExecutablesStorage");
|
|
561
|
-
|
|
562
|
-
// src/lib/api/generate-signed-url-for-team-storage.ts
|
|
563
|
-
async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
|
|
564
|
-
try {
|
|
565
|
-
const response = await localFetch(
|
|
566
|
-
buildSignedUrl({
|
|
567
|
-
destinationFilePath,
|
|
568
|
-
endpoint: "teamStorage",
|
|
569
|
-
serviceBase,
|
|
570
|
-
workspaceId
|
|
571
|
-
}),
|
|
572
|
-
{
|
|
573
|
-
headers: {
|
|
574
|
-
Authorization: `Bearer ${apiKey}`,
|
|
575
|
-
"Content-Type": "application/json",
|
|
576
|
-
"User-Agent": userAgent
|
|
577
|
-
},
|
|
578
|
-
method: "GET"
|
|
579
|
-
}
|
|
580
|
-
);
|
|
581
|
-
if (response.status === 200) {
|
|
582
|
-
const json = await response.json();
|
|
583
|
-
return {
|
|
584
|
-
fileLocation: json.fileLocation,
|
|
585
|
-
httpStatus: response.status,
|
|
586
|
-
interactiveRunFileLocation: json.interactiveRunFileLocation,
|
|
587
|
-
playgroundFileLocation: json.playgroundFileLocation,
|
|
588
|
-
signedUrl: json.signedUrl,
|
|
589
|
-
success: true
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
await logFetchError({
|
|
593
|
-
log,
|
|
594
|
-
methodName: "callGenerateSignedUrlForTempTeamStorage",
|
|
595
|
-
response
|
|
596
|
-
});
|
|
597
|
-
return {
|
|
598
|
-
errorMessage: await response.text(),
|
|
599
|
-
httpStatus: response.status,
|
|
600
|
-
success: false
|
|
601
|
-
};
|
|
602
|
-
} catch {
|
|
603
|
-
return {
|
|
604
|
-
errorMessage: "Network error, aborting request to generate signed URL",
|
|
605
|
-
httpStatus: 0,
|
|
606
|
-
success: false
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
__name(callGenerateSignedUrlForTempTeamStorage, "callGenerateSignedUrlForTempTeamStorage");
|
|
611
|
-
|
|
612
|
-
// src/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-team-storage.ts
|
|
613
|
-
async function generateSignedUrlForTempTeamStorage(deps, apiConfig, config) {
|
|
614
|
-
const { log } = deps;
|
|
615
|
-
if (!config.destinationFilePath) {
|
|
616
|
-
log.error(`\u{1F6AB} destinationFilePath is a required config parameter`);
|
|
617
|
-
return {
|
|
618
|
-
abortReason: "sdk-argument-validation-failed",
|
|
619
|
-
success: false
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
const responseState = await callGenerateSignedUrlForTempTeamStorage(
|
|
623
|
-
apiConfig,
|
|
624
|
-
config,
|
|
625
|
-
deps
|
|
626
|
-
);
|
|
627
|
-
if (!responseState.success) {
|
|
628
|
-
let abortReason = "XXX-other-http-code";
|
|
629
|
-
if (responseState.httpStatus >= 500) {
|
|
630
|
-
abortReason = "5XX-server-error";
|
|
631
|
-
log.error(
|
|
632
|
-
`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
|
|
633
|
-
);
|
|
634
|
-
} else {
|
|
635
|
-
if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
|
|
636
|
-
else if (responseState.httpStatus === 401)
|
|
637
|
-
abortReason = "401-invalid-credentials";
|
|
638
|
-
else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
|
|
639
|
-
else if (responseState.httpStatus === 0)
|
|
640
|
-
abortReason = "client-network-error";
|
|
641
|
-
log.error(
|
|
642
|
-
`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
abortReason,
|
|
647
|
-
httpStatus: responseState.httpStatus,
|
|
648
|
-
success: false
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
log.info(`\u2705 Generated Signed Upload URL: ${responseState.signedUrl}.`);
|
|
652
|
-
return {
|
|
653
|
-
fileLocation: responseState.fileLocation,
|
|
654
|
-
interactiveRunFileLocation: responseState.interactiveRunFileLocation,
|
|
655
|
-
playgroundFileLocation: responseState.playgroundFileLocation,
|
|
656
|
-
success: true,
|
|
657
|
-
uploadUrl: responseState.signedUrl
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
__name(generateSignedUrlForTempTeamStorage, "generateSignedUrlForTempTeamStorage");
|
|
661
|
-
|
|
662
|
-
// src/lib/sdk/domain/notifyTerminatedEphemeralEnvironment/index.ts
|
|
663
|
-
var notifyTerminatedEphemeralEnvironment = /* @__PURE__ */ __name(async (deps, apiConfig, config) => {
|
|
664
|
-
const { fetch: localFetch, log } = deps;
|
|
665
|
-
try {
|
|
666
|
-
const response = await localFetch(
|
|
667
|
-
new URL("/api/webhooks/environment_terminated", apiConfig.serviceBase),
|
|
668
|
-
{
|
|
669
|
-
body: JSON.stringify(config),
|
|
670
|
-
headers: {
|
|
671
|
-
Authorization: `Bearer ${apiConfig.apiKey}`,
|
|
672
|
-
"Content-Type": "application/json",
|
|
673
|
-
"User-Agent": apiConfig.userAgent
|
|
674
|
-
},
|
|
675
|
-
method: "POST"
|
|
676
|
-
}
|
|
677
|
-
);
|
|
678
|
-
if (!response.ok) return handleErrorResponse2(response, log);
|
|
679
|
-
const { environmentId, warning } = await response.json();
|
|
680
|
-
if (warning) log.warn(`\u26A0\uFE0F ${warning}`);
|
|
681
|
-
return { environmentId, outcome: "success" };
|
|
682
|
-
} catch {
|
|
683
|
-
log.error(`\u{1F6AB} Network error from terminate_preview_environment, aborting.`);
|
|
684
|
-
return {
|
|
685
|
-
abortReason: "network-error",
|
|
686
|
-
outcome: "aborted"
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
}, "notifyTerminatedEphemeralEnvironment");
|
|
690
|
-
async function handleErrorResponse2(response, log) {
|
|
691
|
-
const status = response.status;
|
|
692
|
-
let abortReason;
|
|
693
|
-
if (status === void 0) abortReason = "network-error";
|
|
694
|
-
else if (status === 401) abortReason = "401-unauthorized";
|
|
695
|
-
else if (status === 403) abortReason = "403-forbidden";
|
|
696
|
-
else if (status === 409) abortReason = "409-conflict";
|
|
697
|
-
else if (status >= 500) abortReason = "5XX-server-error";
|
|
698
|
-
else abortReason = "4XX-client-error";
|
|
699
|
-
await logFetchError({
|
|
700
|
-
log,
|
|
701
|
-
methodName: "notifyTerminatedEphemeralEnvironment",
|
|
702
|
-
response
|
|
703
|
-
});
|
|
704
|
-
return {
|
|
705
|
-
abortReason,
|
|
706
|
-
httpStatus: status,
|
|
707
|
-
outcome: "aborted"
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
__name(handleErrorResponse2, "handleErrorResponse");
|
|
711
|
-
|
|
712
|
-
// src/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.ts
|
|
713
|
-
function buildCompletedRunResult(status) {
|
|
714
|
-
if (status.greenlight)
|
|
715
|
-
return { greenlightStatus: status, outcome: "success" };
|
|
716
|
-
else return { greenlightStatus: status, outcome: "failed" };
|
|
717
|
-
}
|
|
718
|
-
__name(buildCompletedRunResult, "buildCompletedRunResult");
|
|
719
|
-
|
|
720
|
-
// src/lib/sdk/domain/pollCiGreenlight/consts.ts
|
|
721
|
-
var pollCiGreenlightDefaults = {
|
|
722
|
-
abortOnSuperseded: false,
|
|
723
|
-
maxRetries: 10,
|
|
724
|
-
pollTimeout: 120 * 60 * 1e3,
|
|
725
|
-
// 2 hours
|
|
726
|
-
retryInterval: 10 * 1e3,
|
|
727
|
-
// 10 seconds
|
|
728
|
-
runInProgressInterval: 30 * 1e3,
|
|
729
|
-
// 30 seconds
|
|
730
|
-
runUnderReviewInterval: 60 * 1e3
|
|
731
|
-
// 60 seconds
|
|
732
|
-
};
|
|
733
|
-
|
|
734
|
-
// src/lib/api/ci-greenlight.ts
|
|
735
|
-
async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, {
|
|
736
|
-
runId
|
|
737
|
-
}, { fetch: localFetch, log }) {
|
|
738
|
-
try {
|
|
739
|
-
const resp = await localFetch(
|
|
740
|
-
new URL(
|
|
741
|
-
`/api/v0/ci-greenlight/${encodeURIComponent(runId)}`,
|
|
742
|
-
serviceBase
|
|
743
|
-
),
|
|
744
|
-
{
|
|
745
|
-
headers: {
|
|
746
|
-
Authorization: `Bearer ${apiKey}`,
|
|
747
|
-
"User-Agent": userAgent
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
);
|
|
751
|
-
if (resp.ok) {
|
|
752
|
-
const greenlightStatus = await resp.json();
|
|
753
|
-
return {
|
|
754
|
-
greenlightStatus,
|
|
755
|
-
httpStatus: resp.status,
|
|
756
|
-
ok: true
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
await logFetchError({
|
|
760
|
-
log,
|
|
761
|
-
methodName: "fetchCiGreenlightStatus",
|
|
762
|
-
response: resp
|
|
763
|
-
});
|
|
764
|
-
if (resp.status === 404) {
|
|
765
|
-
return {
|
|
766
|
-
canRetry: true,
|
|
767
|
-
errorType: "not-found",
|
|
768
|
-
httpStatus: resp.status,
|
|
769
|
-
ok: false
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
if (resp.status >= 500) {
|
|
773
|
-
return {
|
|
774
|
-
canRetry: true,
|
|
775
|
-
errorType: "server",
|
|
776
|
-
httpStatus: resp.status,
|
|
777
|
-
ok: false
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
return {
|
|
781
|
-
canRetry: false,
|
|
782
|
-
errorType: "client",
|
|
783
|
-
httpStatus: resp.status,
|
|
784
|
-
ok: false
|
|
785
|
-
};
|
|
786
|
-
} catch {
|
|
787
|
-
return {
|
|
788
|
-
canRetry: true,
|
|
789
|
-
errorType: "network",
|
|
790
|
-
httpStatus: 0,
|
|
791
|
-
ok: false
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
__name(fetchCiGreenlightStatus, "fetchCiGreenlightStatus");
|
|
796
|
-
|
|
797
|
-
// src/lib/sdk/domain/pollCiGreenlight/iterator.ts
|
|
798
|
-
async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...pollConfig }) {
|
|
799
|
-
const { log } = deps;
|
|
800
|
-
const {
|
|
801
|
-
maxRetries = pollCiGreenlightDefaults.maxRetries,
|
|
802
|
-
pollTimeout = pollCiGreenlightDefaults.pollTimeout,
|
|
803
|
-
retryInterval = pollCiGreenlightDefaults.retryInterval,
|
|
804
|
-
runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval,
|
|
805
|
-
runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval
|
|
806
|
-
} = pollConfig;
|
|
807
|
-
let retries = 0;
|
|
808
|
-
let previousStatus = void 0;
|
|
809
|
-
const startedAt = Date.now();
|
|
810
|
-
while (Date.now() - startedAt <= pollTimeout) {
|
|
811
|
-
const responseState = await fetchCiGreenlightStatus(
|
|
812
|
-
apiConfig,
|
|
813
|
-
{ runId },
|
|
814
|
-
deps
|
|
815
|
-
);
|
|
816
|
-
const elapsedMs = Date.now() - startedAt;
|
|
817
|
-
if (!responseState.ok && !responseState.canRetry) {
|
|
818
|
-
yield {
|
|
819
|
-
abortReason: "4XX-client-error",
|
|
820
|
-
elapsedMs,
|
|
821
|
-
httpStatus: responseState.httpStatus,
|
|
822
|
-
isAborted: true
|
|
823
|
-
};
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
if (!responseState.ok) {
|
|
827
|
-
retries += 1;
|
|
828
|
-
const { errorType, httpStatus } = responseState;
|
|
829
|
-
if (retries > maxRetries) {
|
|
830
|
-
switch (errorType) {
|
|
831
|
-
case "network":
|
|
832
|
-
yield {
|
|
833
|
-
abortReason: "network-error",
|
|
834
|
-
elapsedMs,
|
|
835
|
-
httpStatus: void 0,
|
|
836
|
-
isAborted: true
|
|
837
|
-
};
|
|
838
|
-
break;
|
|
839
|
-
case "not-found":
|
|
840
|
-
yield {
|
|
841
|
-
abortReason: "404-run-not-found",
|
|
842
|
-
elapsedMs,
|
|
843
|
-
httpStatus: httpStatus ?? 404,
|
|
844
|
-
isAborted: true
|
|
845
|
-
};
|
|
846
|
-
break;
|
|
847
|
-
case "server":
|
|
848
|
-
yield {
|
|
849
|
-
abortReason: "5XX-server-error",
|
|
850
|
-
elapsedMs,
|
|
851
|
-
httpStatus: httpStatus ?? 500,
|
|
852
|
-
isAborted: true
|
|
853
|
-
};
|
|
854
|
-
break;
|
|
855
|
-
default:
|
|
856
|
-
throw Error(`Unexpected error type: ${errorType}`);
|
|
857
|
-
}
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
|
|
861
|
-
if (errorType === "network") log.warn(`\u274C Network error${retryLabel}`);
|
|
862
|
-
else if (errorType === "not-found")
|
|
863
|
-
log.info(`\u23F3 Run not found${retryLabel}`);
|
|
864
|
-
else if (errorType === "server") log.warn(`\u274C Server error${retryLabel}`);
|
|
865
|
-
await sleep(retryInterval);
|
|
866
|
-
continue;
|
|
867
|
-
}
|
|
868
|
-
retries = 0;
|
|
869
|
-
const { greenlightStatus } = responseState;
|
|
870
|
-
const stageChanged = !previousStatus || previousStatus.runStage !== greenlightStatus.runStage;
|
|
871
|
-
const isFailedTerminalStage = greenlightStatus.runStage === "canceled" || greenlightStatus.runStage === "completed" && greenlightStatus.greenlight === false;
|
|
872
|
-
yield {
|
|
873
|
-
elapsedMs,
|
|
874
|
-
isAborted: false,
|
|
875
|
-
isFailedTerminalStage,
|
|
876
|
-
previousStatus,
|
|
877
|
-
stageChanged,
|
|
878
|
-
status: greenlightStatus
|
|
879
|
-
};
|
|
880
|
-
previousStatus = greenlightStatus;
|
|
881
|
-
switch (greenlightStatus.runStage) {
|
|
882
|
-
case "canceled": {
|
|
883
|
-
yield {
|
|
884
|
-
abortReason: "run-canceled",
|
|
885
|
-
elapsedMs,
|
|
886
|
-
httpStatus: void 0,
|
|
887
|
-
isAborted: true,
|
|
888
|
-
message: greenlightStatus.message,
|
|
889
|
-
reason: greenlightStatus.reason
|
|
890
|
-
};
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
case "completed":
|
|
894
|
-
return;
|
|
895
|
-
case "initializing":
|
|
896
|
-
await sleep(runInProgressInterval);
|
|
897
|
-
break;
|
|
898
|
-
case "underReview":
|
|
899
|
-
await sleep(runUnderReviewInterval);
|
|
900
|
-
break;
|
|
901
|
-
default:
|
|
902
|
-
throw Error(
|
|
903
|
-
`Unexpected run stage: ${greenlightStatus}`
|
|
904
|
-
);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
yield {
|
|
908
|
-
abortReason: "poll-timed-out",
|
|
909
|
-
elapsedMs: Date.now() - startedAt,
|
|
910
|
-
httpStatus: void 0,
|
|
911
|
-
isAborted: true
|
|
912
|
-
};
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
__name(makePollCiGreenlightStatusIterator, "makePollCiGreenlightStatusIterator");
|
|
916
|
-
|
|
917
|
-
// src/lib/sdk/domain/pollCiGreenlight/logging.ts
|
|
918
|
-
function buildSuccessLogMessage(status) {
|
|
919
|
-
const { message, nonBlockingBugsCount, relevantRunUrl, workflowsInRunCount } = status;
|
|
920
|
-
const urlInfo = `See ${relevantRunUrl}`;
|
|
921
|
-
if (workflowsInRunCount === 0 && message) return `\u2705 ${message}
|
|
922
|
-
${urlInfo}`;
|
|
923
|
-
return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
|
|
924
|
-
nonBlockingBugsCount
|
|
925
|
-
)} found` : ""}.
|
|
926
|
-
${urlInfo}`;
|
|
927
|
-
}
|
|
928
|
-
__name(buildSuccessLogMessage, "buildSuccessLogMessage");
|
|
929
|
-
function buildFailedLogMessage(status) {
|
|
930
|
-
const {
|
|
931
|
-
blockingBugsCount,
|
|
932
|
-
nonBlockingBugsCount,
|
|
933
|
-
relevantRunWithBugsUrl,
|
|
934
|
-
reproducedBugs
|
|
935
|
-
} = status;
|
|
936
|
-
const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
|
|
937
|
-
const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
|
|
938
|
-
const blockingBugList = blockingBugUrls.map((url) => `
|
|
939
|
-
\u2022 ${url}`).join("");
|
|
940
|
-
const nonBlockingBugList = nonBlockingBugUrls.map((url) => `
|
|
941
|
-
\u2022 ${url}`).join("");
|
|
942
|
-
const bugsListInText = `${blockingBugUrls.length > 0 ? `
|
|
943
|
-
BLOCKING BUGS:${blockingBugList}` : ""}${nonBlockingBugUrls.length > 0 ? `
|
|
944
|
-
NON BLOCKING BUGS:${nonBlockingBugList}` : ""}`;
|
|
945
|
-
return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(
|
|
946
|
-
blockingBugsCount
|
|
947
|
-
)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
|
|
948
|
-
nonBlockingBugsCount
|
|
949
|
-
)}` : ""}.
|
|
950
|
-
See ${relevantRunWithBugsUrl}
|
|
951
|
-
You may review the priority of certain bugs to mark these non-blocking and retry the job.
|
|
952
|
-
` + bugsListInText;
|
|
953
|
-
}
|
|
954
|
-
__name(buildFailedLogMessage, "buildFailedLogMessage");
|
|
955
|
-
function buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl) {
|
|
956
|
-
const supersedingDetails = `
|
|
957
|
-
- Root: ${rootRunUrl}
|
|
958
|
-
- Superseding: ${relevantRunUrl}`;
|
|
959
|
-
return `\u274C Aborting due to superseded run (option 'abortOnSuperseded' is enabled).${supersedingDetails}`;
|
|
960
|
-
}
|
|
961
|
-
__name(buildSupersededAbortLogMessage, "buildSupersededAbortLogMessage");
|
|
962
|
-
function buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl) {
|
|
963
|
-
const supersedingDetails = `
|
|
964
|
-
- Root: ${rootRunUrl}
|
|
965
|
-
- Superseding: ${relevantRunUrl}`;
|
|
966
|
-
return `Root run was superseded:${supersedingDetails}`;
|
|
967
|
-
}
|
|
968
|
-
__name(buildSupersededContinueLogMessage, "buildSupersededContinueLogMessage");
|
|
969
|
-
function buildRunStageLogMessage(status) {
|
|
970
|
-
const { relevantRunUrl, runStage } = status;
|
|
971
|
-
const urlInfo = `See ${relevantRunUrl}`;
|
|
972
|
-
switch (runStage) {
|
|
973
|
-
case "canceled":
|
|
974
|
-
return `\u{1F6AB} Run was canceled. ${status.message || "Aborting... Please contact support."}
|
|
975
|
-
${urlInfo}`;
|
|
976
|
-
case "initializing":
|
|
977
|
-
return `\u23F3 Run is in progress... ${urlInfo}`;
|
|
978
|
-
case "underReview":
|
|
979
|
-
return `\u{1F575}\uFE0F Run is under review... ${urlInfo}`;
|
|
980
|
-
default:
|
|
981
|
-
throw Error(`Unexpected run stage: ${runStage}`);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
__name(buildRunStageLogMessage, "buildRunStageLogMessage");
|
|
985
|
-
|
|
986
|
-
// src/lib/sdk/domain/pollCiGreenlight/mapIteratorAbortToGreenlightStatus.ts
|
|
987
|
-
function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
|
|
988
|
-
const { log } = deps;
|
|
989
|
-
const { abortReason, httpStatus } = iteration;
|
|
990
|
-
switch (abortReason) {
|
|
991
|
-
case "4XX-client-error":
|
|
992
|
-
log.error(
|
|
993
|
-
`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
|
|
994
|
-
);
|
|
995
|
-
return {
|
|
996
|
-
abortReason,
|
|
997
|
-
httpStatus,
|
|
998
|
-
outcome: "aborted"
|
|
999
|
-
};
|
|
1000
|
-
case "5XX-server-error":
|
|
1001
|
-
log.error(
|
|
1002
|
-
`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
|
|
1003
|
-
);
|
|
1004
|
-
return {
|
|
1005
|
-
abortReason,
|
|
1006
|
-
httpStatus,
|
|
1007
|
-
outcome: "aborted"
|
|
1008
|
-
};
|
|
1009
|
-
case "404-run-not-found":
|
|
1010
|
-
log.error(
|
|
1011
|
-
`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
|
|
1012
|
-
);
|
|
1013
|
-
return {
|
|
1014
|
-
abortReason,
|
|
1015
|
-
httpStatus,
|
|
1016
|
-
outcome: "aborted"
|
|
1017
|
-
};
|
|
1018
|
-
case "network-error":
|
|
1019
|
-
log.error(`\u{1F6AB} Unrecoverable network error from CI greenlight, aborting.`);
|
|
1020
|
-
return {
|
|
1021
|
-
abortReason,
|
|
1022
|
-
httpStatus: void 0,
|
|
1023
|
-
outcome: "aborted"
|
|
1024
|
-
};
|
|
1025
|
-
case "poll-timed-out":
|
|
1026
|
-
log.warn(`\u274C Poll timed out after ${pollTimeout / 1e3}s`);
|
|
1027
|
-
return {
|
|
1028
|
-
abortReason,
|
|
1029
|
-
httpStatus: void 0,
|
|
1030
|
-
outcome: "aborted"
|
|
1031
|
-
};
|
|
1032
|
-
case "run-canceled":
|
|
1033
|
-
log.info(
|
|
1034
|
-
iteration.message ? `Run was canceled. ${iteration.message}` : `Run was canceled`
|
|
1035
|
-
);
|
|
1036
|
-
return {
|
|
1037
|
-
abortReason,
|
|
1038
|
-
httpStatus: void 0,
|
|
1039
|
-
outcome: "aborted"
|
|
1040
|
-
};
|
|
1041
|
-
case "XXX-other-http-code":
|
|
1042
|
-
log.error(
|
|
1043
|
-
`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
|
|
1044
|
-
);
|
|
1045
|
-
return {
|
|
1046
|
-
abortReason,
|
|
1047
|
-
httpStatus,
|
|
1048
|
-
outcome: "aborted"
|
|
1049
|
-
};
|
|
1050
|
-
default:
|
|
1051
|
-
throw Error(`Unexpected abort reason: ${abortReason}`);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
__name(mapIteratorAbortToGreenlightStatus, "mapIteratorAbortToGreenlightStatus");
|
|
1055
|
-
|
|
1056
|
-
// src/lib/sdk/domain/pollCiGreenlight/index.ts
|
|
1057
|
-
async function pollCiGreenlightStatus(deps, apiConfig, {
|
|
1058
|
-
onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"),
|
|
1059
|
-
runId,
|
|
1060
|
-
...pollConfig
|
|
1061
|
-
}) {
|
|
1062
|
-
const { log } = deps;
|
|
1063
|
-
const {
|
|
1064
|
-
abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded,
|
|
1065
|
-
pollTimeout = pollCiGreenlightDefaults.pollTimeout
|
|
1066
|
-
} = pollConfig;
|
|
1067
|
-
const iterator = makePollCiGreenlightStatusIterator(deps, apiConfig, {
|
|
1068
|
-
runId,
|
|
1069
|
-
...pollConfig
|
|
1070
|
-
});
|
|
1071
|
-
for await (const lastPoll of iterator) {
|
|
1072
|
-
if (lastPoll.isAborted)
|
|
1073
|
-
return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
|
|
1074
|
-
const { previousStatus, stageChanged, status } = lastPoll;
|
|
1075
|
-
if (stageChanged) {
|
|
1076
|
-
try {
|
|
1077
|
-
await onRunStageChanged(status, previousStatus);
|
|
1078
|
-
} catch (e) {
|
|
1079
|
-
log.error(`Error in onRunStageChanged callback`, e);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
const { relevantRunId, relevantRunUrl, rootRunId, rootRunUrl, runStage } = status;
|
|
1083
|
-
const isSuperseded = rootRunId !== relevantRunId;
|
|
1084
|
-
const shouldAbortOnSuperseded = isSuperseded && abortOnSuperseded;
|
|
1085
|
-
if (shouldAbortOnSuperseded) {
|
|
1086
|
-
const message = buildSupersededAbortLogMessage(
|
|
1087
|
-
rootRunUrl,
|
|
1088
|
-
relevantRunUrl
|
|
1089
|
-
);
|
|
1090
|
-
log.warn(message);
|
|
1091
|
-
return {
|
|
1092
|
-
abortReason: "superseded-run",
|
|
1093
|
-
httpStatus: void 0,
|
|
1094
|
-
outcome: "aborted"
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
if (isSuperseded) {
|
|
1098
|
-
const message = buildSupersededContinueLogMessage(
|
|
1099
|
-
rootRunUrl,
|
|
1100
|
-
relevantRunUrl
|
|
1101
|
-
);
|
|
1102
|
-
log.info(message);
|
|
1103
|
-
}
|
|
1104
|
-
switch (runStage) {
|
|
1105
|
-
case "canceled":
|
|
1106
|
-
case "initializing":
|
|
1107
|
-
case "underReview": {
|
|
1108
|
-
log.info(buildRunStageLogMessage(status));
|
|
1109
|
-
break;
|
|
1110
|
-
}
|
|
1111
|
-
case "completed": {
|
|
1112
|
-
const result = buildCompletedRunResult(status);
|
|
1113
|
-
switch (result.outcome) {
|
|
1114
|
-
case "aborted": {
|
|
1115
|
-
break;
|
|
1116
|
-
}
|
|
1117
|
-
case "failed": {
|
|
1118
|
-
const message = buildFailedLogMessage(status);
|
|
1119
|
-
log.warn(message);
|
|
1120
|
-
break;
|
|
1121
|
-
}
|
|
1122
|
-
case "success": {
|
|
1123
|
-
const message = buildSuccessLogMessage(status);
|
|
1124
|
-
log.info(message);
|
|
1125
|
-
break;
|
|
1126
|
-
}
|
|
1127
|
-
default:
|
|
1128
|
-
throw Error(`Unexpected outcome: ${result}`);
|
|
1129
|
-
}
|
|
1130
|
-
return result;
|
|
1131
|
-
}
|
|
1132
|
-
default:
|
|
1133
|
-
throw Error(`Unexpected run stage: ${runStage}`);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
throw Error("Iterator ended unexpectedly");
|
|
1137
|
-
}
|
|
1138
|
-
__name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
|
|
1139
|
-
|
|
1140
|
-
// src/lib/sdk/index.ts
|
|
1141
|
-
function makeQaWolfSdk({
|
|
1142
|
-
apiKey,
|
|
1143
|
-
serviceBase = defaultServiceBase,
|
|
1144
|
-
userAgent = defaultUserAgent
|
|
1145
|
-
}, {
|
|
1146
|
-
fetch = defaultFetch,
|
|
1147
|
-
log = defaultLogDriver
|
|
1148
|
-
} = {}) {
|
|
1149
|
-
if (typeof fetch !== "function") {
|
|
1150
|
-
throw Error(
|
|
1151
|
-
`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.
|
|
1152
|
-
We recommend 'undici' package for that purpose. See the Requirement section of our README for more guidance.`
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
const deps = {
|
|
1156
|
-
// Versions of fetch may have different default timeouts. For consistency, we explicitly set it to 2 minutes here.
|
|
1157
|
-
// The increased timeout helps avoid edge cases where we need to search for a commit against multiple
|
|
1158
|
-
// code hosting service repositories as a fallback when not finding the repository based on organization/name.
|
|
1159
|
-
fetch: buildFetchWithTimeout(fetch, 12e4),
|
|
1160
|
-
log
|
|
1161
|
-
};
|
|
1162
|
-
const apiConfig = { apiKey, serviceBase, userAgent };
|
|
1163
|
-
return {
|
|
1164
|
-
attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
|
|
1165
|
-
generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(
|
|
1166
|
-
null,
|
|
1167
|
-
deps,
|
|
1168
|
-
apiConfig
|
|
1169
|
-
),
|
|
1170
|
-
generateSignedUrlForTempTeamStorage: generateSignedUrlForTempTeamStorage.bind(null, deps, apiConfig),
|
|
1171
|
-
makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(
|
|
1172
|
-
null,
|
|
1173
|
-
deps,
|
|
1174
|
-
apiConfig
|
|
1175
|
-
),
|
|
1176
|
-
notifyTerminatedEphemeralEnvironment: notifyTerminatedEphemeralEnvironment.bind(null, deps, apiConfig),
|
|
1177
|
-
pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
__name(makeQaWolfSdk, "makeQaWolfSdk");
|
|
1181
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1182
|
-
0 && (module.exports = {
|
|
1183
|
-
makeQaWolfSdk
|
|
1184
|
-
});
|
|
1185
|
-
//# sourceMappingURL=index.cjs.map
|