@qawolf/ci-sdk 1.1.1 → 1.2.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 +126 -0
- package/dist/index.cjs +269 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +269 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ to determine the status of your CI/CD step/job/action.
|
|
|
14
14
|
- [Notify Deployment](#notify-deployment)
|
|
15
15
|
- [Notify Preview Deployment (Pull Request / Merge Request Testing)](#notify-preview)
|
|
16
16
|
- [Poll for CI Greenlight Status](#ci-greenlight)
|
|
17
|
+
- [Advanced: Poll with Custom Control (Iterator)](#ci-greenlight-iterator)
|
|
17
18
|
- [Upload Run Input Artifacts](#upload-artifacts)
|
|
18
19
|
- [Requirements](#requirements)
|
|
19
20
|
- [Versioning](#versioning)
|
|
@@ -180,6 +181,127 @@ const { pollCiGreenlightStatus } = makeQaWolfSdk({
|
|
|
180
181
|
})();
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
<a id="ci-greenlight-iterator"></a>
|
|
185
|
+
|
|
186
|
+
## Advanced: Poll with Custom Control (Iterator)
|
|
187
|
+
|
|
188
|
+
For advanced use cases where you need fine-grained control over the polling lifecycle, use `makePollCiGreenlightStatusIterator`. This async generator gives you full control to implement custom logic such as:
|
|
189
|
+
|
|
190
|
+
- Stop polling early based on time limits (e.g., "wait max 10 minutes in review then proceed")
|
|
191
|
+
- Stop polling based on bug counts (e.g., "proceed if only 3 or fewer blocking bugs")
|
|
192
|
+
- Implement custom logging or monitoring at each poll iteration
|
|
193
|
+
- Access detailed bug data during the "under review" stage
|
|
194
|
+
|
|
195
|
+
> ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e).
|
|
196
|
+
|
|
197
|
+
### Example: Early Exit with Custom Logic
|
|
198
|
+
|
|
199
|
+
> ⚠️ **Important**: When using the iterator, ensure you handle all run stages in your switch statement. The `default` case with `satisfies never` provides compile-time safety - if a new stage is added, TypeScript will error. This prevents silently passing your CI job while ignoring error conditions.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { makeQaWolfSdk } from "@qawolf/ci-sdk";
|
|
203
|
+
|
|
204
|
+
const { makePollCiGreenlightStatusIterator } = makeQaWolfSdk({
|
|
205
|
+
apiKey: "qawolf_xxxxx",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
(async () => {
|
|
209
|
+
let underReviewStartTime: number | null = null;
|
|
210
|
+
|
|
211
|
+
const iterator = makePollCiGreenlightStatusIterator({
|
|
212
|
+
runId: "your-run-id",
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
for await (const iteration of iterator) {
|
|
216
|
+
// Check if the iterator yielded an abort result
|
|
217
|
+
if (iteration.isAborted) {
|
|
218
|
+
console.error(`Poll aborted: ${iteration.abortReason}`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Handle status iterations
|
|
223
|
+
const { status, stageChanged } = iteration;
|
|
224
|
+
|
|
225
|
+
switch (status.runStage) {
|
|
226
|
+
case "initializing":
|
|
227
|
+
console.log(`Run stage: ${status.runStage}`);
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
case "underReview": {
|
|
231
|
+
// Track when we first enter underReview
|
|
232
|
+
if (stageChanged) {
|
|
233
|
+
underReviewStartTime = Date.now();
|
|
234
|
+
console.log(`Run stage: ${status.runStage}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const timeInUnderReview = underReviewStartTime
|
|
238
|
+
? Date.now() - underReviewStartTime
|
|
239
|
+
: 0;
|
|
240
|
+
|
|
241
|
+
// Option 1: Stop after 10 minutes in review
|
|
242
|
+
if (timeInUnderReview > 10 * 60 * 1000) {
|
|
243
|
+
console.log("10 minutes in review, proceeding with deployment");
|
|
244
|
+
// Decide whether to fail CI based on your criteria
|
|
245
|
+
if (status.blockingBugsCount > 5) {
|
|
246
|
+
console.log("Too many blocking bugs, failing CI");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Option 2: Stop if bug count is acceptable
|
|
253
|
+
if (status.blockingBugsCount <= 3) {
|
|
254
|
+
console.log(
|
|
255
|
+
`Only ${status.blockingBugsCount} blocking bugs, acceptable to proceed`,
|
|
256
|
+
);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case "completed":
|
|
263
|
+
console.log(`Completed with greenlight: ${status.greenlight}`);
|
|
264
|
+
if (!status.greenlight) {
|
|
265
|
+
console.log("Run failed, blocking bugs found");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
console.log("Run passed successfully");
|
|
269
|
+
return; // Exit the loop
|
|
270
|
+
|
|
271
|
+
case "canceled":
|
|
272
|
+
console.log("Run was canceled");
|
|
273
|
+
process.exit(1);
|
|
274
|
+
|
|
275
|
+
default:
|
|
276
|
+
// Exhaustive check - TypeScript will error if a case is missing
|
|
277
|
+
status.runStage satisfies never;
|
|
278
|
+
throw new Error(`Unexpected run stage: ${status.runStage}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
})();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Yielded Iteration Object
|
|
285
|
+
|
|
286
|
+
The iterator yields a discriminated union that can be either a status update or an abort notification:
|
|
287
|
+
|
|
288
|
+
**Status Iteration** (`isAborted: false`):
|
|
289
|
+
|
|
290
|
+
- `isAborted`: `false` - Indicates a normal status update
|
|
291
|
+
- `status`: The current `CiGreenlightStatus` from the API
|
|
292
|
+
- `previousStatus`: The status from the previous iteration (undefined on first iteration)
|
|
293
|
+
- `stageChanged`: Boolean indicating if the run stage changed from the previous iteration
|
|
294
|
+
- `elapsedMs`: Milliseconds elapsed since polling started
|
|
295
|
+
|
|
296
|
+
**Aborted Iteration** (`isAborted: true`):
|
|
297
|
+
|
|
298
|
+
- `isAborted`: `true` - Indicates the polling was aborted
|
|
299
|
+
- `abortReason`: String indicating why polling was aborted (e.g., `"poll-timed-out"`, `"run-canceled"`, `"network-error"`, `"4XX-client-error"`, `"5XX-server-error"`)
|
|
300
|
+
- `httpStatus`: HTTP status code if applicable, otherwise `undefined`
|
|
301
|
+
- `elapsedMs`: Milliseconds elapsed since polling started
|
|
302
|
+
|
|
303
|
+
**Important**: Always check `iteration.isAborted` to determine which type of result you received. When `isAborted === true`, you should handle the abort reason and exit appropriately.
|
|
304
|
+
|
|
183
305
|
<a id="upload-artifacts"></a>
|
|
184
306
|
|
|
185
307
|
## Upload Run Input Artifacts
|
|
@@ -294,6 +416,10 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
|
|
|
294
416
|
|
|
295
417
|
# Changelog
|
|
296
418
|
|
|
419
|
+
## v1.2.0
|
|
420
|
+
|
|
421
|
+
- New `makePollCiGreenlightStatusIterator` async generator function for advanced polling control with custom early-exit logic
|
|
422
|
+
|
|
297
423
|
## v1.1.0
|
|
298
424
|
|
|
299
425
|
- New `ephemeralEnvironment` parameter for `attemptNotifyDeploy`
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
|
|
|
38
38
|
|
|
39
39
|
// package.json
|
|
40
40
|
var package_default = {
|
|
41
|
-
version: "1.
|
|
41
|
+
version: "1.2.0"};
|
|
42
42
|
|
|
43
43
|
// src/lib/sdk/defaults/userAgent.ts
|
|
44
44
|
var defaultUserAgent = `ci-sdk/${package_default.version}`;
|
|
@@ -358,6 +358,29 @@ async function generateSignedUrlForTempTeamStorage(deps, apiConfig, config) {
|
|
|
358
358
|
}
|
|
359
359
|
__name(generateSignedUrlForTempTeamStorage, "generateSignedUrlForTempTeamStorage");
|
|
360
360
|
|
|
361
|
+
// src/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.ts
|
|
362
|
+
function buildCompletedRunResult(status) {
|
|
363
|
+
if (status.greenlight) return {
|
|
364
|
+
greenlightStatus: status,
|
|
365
|
+
outcome: "success"
|
|
366
|
+
};
|
|
367
|
+
else return {
|
|
368
|
+
greenlightStatus: status,
|
|
369
|
+
outcome: "failed"
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
__name(buildCompletedRunResult, "buildCompletedRunResult");
|
|
373
|
+
|
|
374
|
+
// src/lib/sdk/domain/pollCiGreenlight/consts.ts
|
|
375
|
+
var pollCiGreenlightDefaults = {
|
|
376
|
+
abortOnSuperseded: false,
|
|
377
|
+
maxRetries: 10,
|
|
378
|
+
pollTimeout: 120 * 60 * 1e3,
|
|
379
|
+
retryInterval: 10 * 1e3,
|
|
380
|
+
runInProgressInterval: 30 * 1e3,
|
|
381
|
+
runUnderReviewInterval: 60 * 1e3
|
|
382
|
+
};
|
|
383
|
+
|
|
361
384
|
// src/lib/api/ci-greenlight.ts
|
|
362
385
|
async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { runId }, { fetch: localFetch, log }) {
|
|
363
386
|
try {
|
|
@@ -413,56 +436,61 @@ async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { run
|
|
|
413
436
|
}
|
|
414
437
|
__name(fetchCiGreenlightStatus, "fetchCiGreenlightStatus");
|
|
415
438
|
|
|
416
|
-
// src/lib/sdk/domain/
|
|
417
|
-
|
|
418
|
-
abortOnSuperseded: false,
|
|
419
|
-
maxRetries: 10,
|
|
420
|
-
pollTimeout: 120 * 60 * 1e3,
|
|
421
|
-
retryInterval: 10 * 1e3,
|
|
422
|
-
runInProgressInterval: 30 * 1e3,
|
|
423
|
-
runUnderReviewInterval: 60 * 1e3
|
|
424
|
-
};
|
|
425
|
-
async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
|
|
426
|
-
let retries = 0;
|
|
439
|
+
// src/lib/sdk/domain/pollCiGreenlight/iterator.ts
|
|
440
|
+
async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...pollConfig }) {
|
|
427
441
|
const { log } = deps;
|
|
428
|
-
const {
|
|
429
|
-
let
|
|
442
|
+
const { maxRetries = pollCiGreenlightDefaults.maxRetries, pollTimeout = pollCiGreenlightDefaults.pollTimeout, retryInterval = pollCiGreenlightDefaults.retryInterval, runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval, runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval } = pollConfig;
|
|
443
|
+
let retries = 0;
|
|
444
|
+
let previousStatus = void 0;
|
|
430
445
|
const startedAt = Date.now();
|
|
431
|
-
|
|
432
|
-
while (!(hasTimedOut = Date.now() - startedAt > pollTimeout)) {
|
|
446
|
+
while (Date.now() - startedAt <= pollTimeout) {
|
|
433
447
|
const responseState = await fetchCiGreenlightStatus(apiConfig, {
|
|
434
448
|
runId
|
|
435
449
|
}, deps);
|
|
450
|
+
const elapsedMs = Date.now() - startedAt;
|
|
436
451
|
if (!responseState.ok && !responseState.canRetry) {
|
|
437
|
-
|
|
438
|
-
return {
|
|
452
|
+
yield {
|
|
439
453
|
abortReason: "4XX-client-error",
|
|
454
|
+
elapsedMs,
|
|
440
455
|
httpStatus: responseState.httpStatus,
|
|
441
|
-
|
|
456
|
+
isAborted: true
|
|
442
457
|
};
|
|
458
|
+
return;
|
|
443
459
|
}
|
|
444
460
|
if (!responseState.ok) {
|
|
445
461
|
retries += 1;
|
|
462
|
+
const { errorType, httpStatus } = responseState;
|
|
446
463
|
if (retries > maxRetries) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
464
|
+
switch (errorType) {
|
|
465
|
+
case "network":
|
|
466
|
+
yield {
|
|
467
|
+
abortReason: "network-error",
|
|
468
|
+
elapsedMs,
|
|
469
|
+
httpStatus: void 0,
|
|
470
|
+
isAborted: true
|
|
471
|
+
};
|
|
472
|
+
break;
|
|
473
|
+
case "not-found":
|
|
474
|
+
yield {
|
|
475
|
+
abortReason: "404-run-not-found",
|
|
476
|
+
elapsedMs,
|
|
477
|
+
httpStatus: httpStatus ?? 404,
|
|
478
|
+
isAborted: true
|
|
479
|
+
};
|
|
480
|
+
break;
|
|
481
|
+
case "server":
|
|
482
|
+
yield {
|
|
483
|
+
abortReason: "5XX-server-error",
|
|
484
|
+
elapsedMs,
|
|
485
|
+
httpStatus: httpStatus ?? 500,
|
|
486
|
+
isAborted: true
|
|
487
|
+
};
|
|
488
|
+
break;
|
|
489
|
+
default:
|
|
490
|
+
throw Error(`Unexpected error type: ${errorType}`);
|
|
454
491
|
}
|
|
455
|
-
|
|
456
|
-
if (responseState.errorType === "not-found") abortReason = "404-run-not-found";
|
|
457
|
-
else if (responseState.errorType === "server") abortReason = "5XX-server-error";
|
|
458
|
-
else ;
|
|
459
|
-
return {
|
|
460
|
-
abortReason,
|
|
461
|
-
httpStatus: responseState.httpStatus,
|
|
462
|
-
outcome: "aborted"
|
|
463
|
-
};
|
|
492
|
+
return;
|
|
464
493
|
}
|
|
465
|
-
const { errorType, httpStatus } = responseState;
|
|
466
494
|
const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
|
|
467
495
|
if (errorType === "network") log.warn(`\u274C Network error${retryLabel}`);
|
|
468
496
|
else if (errorType === "not-found") log.info(`\u23F3 Run not found${retryLabel}`);
|
|
@@ -472,89 +500,231 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
|
|
|
472
500
|
}
|
|
473
501
|
retries = 0;
|
|
474
502
|
const { greenlightStatus } = responseState;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
log.warn(`\u274C Aborting due to superseded run (option 'abortOnSuperseded' is enabled).${supersedingDetails}`);
|
|
492
|
-
return {
|
|
493
|
-
abortReason: "superseded-run",
|
|
503
|
+
const stageChanged = !previousStatus || previousStatus.runStage !== greenlightStatus.runStage;
|
|
504
|
+
const isFailedTerminalStage = greenlightStatus.runStage === "canceled" || greenlightStatus.runStage === "completed" && greenlightStatus.greenlight === false;
|
|
505
|
+
yield {
|
|
506
|
+
elapsedMs,
|
|
507
|
+
isAborted: false,
|
|
508
|
+
isFailedTerminalStage,
|
|
509
|
+
previousStatus,
|
|
510
|
+
stageChanged,
|
|
511
|
+
status: greenlightStatus
|
|
512
|
+
};
|
|
513
|
+
previousStatus = greenlightStatus;
|
|
514
|
+
switch (greenlightStatus.runStage) {
|
|
515
|
+
case "canceled": {
|
|
516
|
+
yield {
|
|
517
|
+
abortReason: "run-canceled",
|
|
518
|
+
elapsedMs,
|
|
494
519
|
httpStatus: void 0,
|
|
495
|
-
|
|
520
|
+
isAborted: true
|
|
496
521
|
};
|
|
522
|
+
return;
|
|
497
523
|
}
|
|
498
|
-
|
|
524
|
+
case "completed":
|
|
525
|
+
return;
|
|
526
|
+
case "initializing":
|
|
527
|
+
await sleep(runInProgressInterval);
|
|
528
|
+
break;
|
|
529
|
+
case "underReview":
|
|
530
|
+
await sleep(runUnderReviewInterval);
|
|
531
|
+
break;
|
|
532
|
+
default:
|
|
533
|
+
throw Error(`Unexpected run stage: ${greenlightStatus}`);
|
|
499
534
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
535
|
+
}
|
|
536
|
+
yield {
|
|
537
|
+
abortReason: "poll-timed-out",
|
|
538
|
+
elapsedMs: Date.now() - startedAt,
|
|
539
|
+
httpStatus: void 0,
|
|
540
|
+
isAborted: true
|
|
541
|
+
};
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
__name(makePollCiGreenlightStatusIterator, "makePollCiGreenlightStatusIterator");
|
|
545
|
+
|
|
546
|
+
// src/lib/sdk/domain/pollCiGreenlight/logging.ts
|
|
547
|
+
function buildSuccessLogMessage(status) {
|
|
548
|
+
const { nonBlockingBugsCount, relevantRunUrl } = status;
|
|
549
|
+
const urlInfo = `See ${relevantRunUrl}`;
|
|
550
|
+
return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
|
|
551
|
+
${urlInfo}`;
|
|
552
|
+
}
|
|
553
|
+
__name(buildSuccessLogMessage, "buildSuccessLogMessage");
|
|
554
|
+
function buildFailedLogMessage(status) {
|
|
555
|
+
const { blockingBugsCount, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [], relevantRunWithBugsUrl } = status;
|
|
556
|
+
const blockingBugList = blockingBugUrls.map((url) => `
|
|
503
557
|
\u2022 ${url}`).join("");
|
|
504
|
-
|
|
558
|
+
const nonBlockingBugList = nonBlockingBugUrls.map((url) => `
|
|
505
559
|
\u2022 ${url}`).join("");
|
|
506
|
-
|
|
560
|
+
const bugsListInText = `${blockingBugUrls.length > 0 ? `
|
|
507
561
|
BLOCKING BUGS:${blockingBugList}` : ""}${nonBlockingBugUrls.length > 0 ? `
|
|
508
562
|
NON BLOCKING BUGS:${nonBlockingBugList}` : ""}`;
|
|
509
|
-
|
|
510
|
-
log.info(`\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
|
|
511
|
-
${urlInfo}`);
|
|
512
|
-
return {
|
|
513
|
-
greenlightStatus,
|
|
514
|
-
outcome: "success"
|
|
515
|
-
};
|
|
516
|
-
} else {
|
|
517
|
-
log.warn(`\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
|
|
563
|
+
return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
|
|
518
564
|
See ${relevantRunWithBugsUrl}
|
|
519
565
|
You may review the priority of certain bugs to mark these non-blocking and retry the job.
|
|
520
|
-
` + bugsListInText
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
566
|
+
` + bugsListInText;
|
|
567
|
+
}
|
|
568
|
+
__name(buildFailedLogMessage, "buildFailedLogMessage");
|
|
569
|
+
function buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl) {
|
|
570
|
+
const supersedingDetails = `
|
|
571
|
+
- Root: ${rootRunUrl}
|
|
572
|
+
- Superseding: ${relevantRunUrl}`;
|
|
573
|
+
return `\u274C Aborting due to superseded run (option 'abortOnSuperseded' is enabled).${supersedingDetails}`;
|
|
574
|
+
}
|
|
575
|
+
__name(buildSupersededAbortLogMessage, "buildSupersededAbortLogMessage");
|
|
576
|
+
function buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl) {
|
|
577
|
+
const supersedingDetails = `
|
|
578
|
+
- Root: ${rootRunUrl}
|
|
579
|
+
- Superseding: ${relevantRunUrl}`;
|
|
580
|
+
return `Root run was superseded:${supersedingDetails}`;
|
|
581
|
+
}
|
|
582
|
+
__name(buildSupersededContinueLogMessage, "buildSupersededContinueLogMessage");
|
|
583
|
+
function buildRunStageLogMessage(runStage, relevantRunUrl) {
|
|
584
|
+
const urlInfo = `See ${relevantRunUrl}`;
|
|
585
|
+
switch (runStage) {
|
|
586
|
+
case "canceled":
|
|
587
|
+
return `\u{1F6AB} Run was canceled. Aborting... Please contact support.
|
|
588
|
+
${urlInfo}`;
|
|
589
|
+
case "initializing":
|
|
590
|
+
return `\u23F3 Run is in progress... ${urlInfo}`;
|
|
591
|
+
case "underReview":
|
|
592
|
+
return `\u{1F575}\uFE0F Run is under review... ${urlInfo}`;
|
|
593
|
+
case "completed":
|
|
594
|
+
throw Error("buildRunStageLogMessage should not be called for completed stage");
|
|
595
|
+
default:
|
|
596
|
+
throw Error(`Unexpected run stage: ${runStage}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
__name(buildRunStageLogMessage, "buildRunStageLogMessage");
|
|
600
|
+
|
|
601
|
+
// src/lib/sdk/domain/pollCiGreenlight/mapIteratorAbortToGreenlightStatus.ts
|
|
602
|
+
function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
|
|
603
|
+
const { log } = deps;
|
|
604
|
+
const { abortReason, httpStatus } = iteration;
|
|
605
|
+
switch (abortReason) {
|
|
606
|
+
case "4XX-client-error":
|
|
607
|
+
log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
|
|
608
|
+
return {
|
|
609
|
+
abortReason,
|
|
610
|
+
httpStatus,
|
|
611
|
+
outcome: "aborted"
|
|
612
|
+
};
|
|
613
|
+
case "5XX-server-error":
|
|
614
|
+
log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
|
|
615
|
+
return {
|
|
616
|
+
abortReason,
|
|
617
|
+
httpStatus,
|
|
618
|
+
outcome: "aborted"
|
|
619
|
+
};
|
|
620
|
+
case "404-run-not-found":
|
|
621
|
+
log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
|
|
622
|
+
return {
|
|
623
|
+
abortReason,
|
|
624
|
+
httpStatus,
|
|
625
|
+
outcome: "aborted"
|
|
626
|
+
};
|
|
627
|
+
case "network-error":
|
|
628
|
+
log.error(`\u{1F6AB} Unrecoverable network error from CI greenlight, aborting.`);
|
|
629
|
+
return {
|
|
630
|
+
abortReason,
|
|
631
|
+
httpStatus: void 0,
|
|
632
|
+
outcome: "aborted"
|
|
633
|
+
};
|
|
634
|
+
case "poll-timed-out":
|
|
635
|
+
log.warn(`\u274C Poll timed out after ${pollTimeout / 1e3}s`);
|
|
636
|
+
return {
|
|
637
|
+
abortReason,
|
|
638
|
+
httpStatus: void 0,
|
|
639
|
+
outcome: "aborted"
|
|
640
|
+
};
|
|
641
|
+
case "run-canceled":
|
|
642
|
+
log.info(`Run was canceled`);
|
|
530
643
|
return {
|
|
531
|
-
abortReason
|
|
644
|
+
abortReason,
|
|
532
645
|
httpStatus: void 0,
|
|
533
646
|
outcome: "aborted"
|
|
534
647
|
};
|
|
648
|
+
case "XXX-other-http-code":
|
|
649
|
+
log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
|
|
650
|
+
return {
|
|
651
|
+
abortReason,
|
|
652
|
+
httpStatus,
|
|
653
|
+
outcome: "aborted"
|
|
654
|
+
};
|
|
655
|
+
default:
|
|
656
|
+
throw Error(`Unexpected abort reason: ${abortReason}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
__name(mapIteratorAbortToGreenlightStatus, "mapIteratorAbortToGreenlightStatus");
|
|
660
|
+
|
|
661
|
+
// src/lib/sdk/domain/pollCiGreenlight/index.ts
|
|
662
|
+
async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
|
|
663
|
+
const { log } = deps;
|
|
664
|
+
const { abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded, pollTimeout = pollCiGreenlightDefaults.pollTimeout } = pollConfig;
|
|
665
|
+
const iterator = makePollCiGreenlightStatusIterator(deps, apiConfig, {
|
|
666
|
+
runId,
|
|
667
|
+
...pollConfig
|
|
668
|
+
});
|
|
669
|
+
for await (const lastPoll of iterator) {
|
|
670
|
+
if (lastPoll.isAborted) return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
|
|
671
|
+
const { previousStatus, stageChanged, status } = lastPoll;
|
|
672
|
+
if (stageChanged) {
|
|
673
|
+
try {
|
|
674
|
+
await onRunStageChanged(status, previousStatus);
|
|
675
|
+
} catch (e) {
|
|
676
|
+
log.error(`Error in onRunStageChanged callback`, e);
|
|
677
|
+
}
|
|
535
678
|
}
|
|
536
|
-
|
|
537
|
-
|
|
679
|
+
const { relevantRunId, relevantRunUrl, rootRunId, rootRunUrl, runStage } = status;
|
|
680
|
+
const isSuperseded = rootRunId !== relevantRunId;
|
|
681
|
+
const shouldAbortOnSuperseded = isSuperseded && abortOnSuperseded;
|
|
682
|
+
if (shouldAbortOnSuperseded) {
|
|
683
|
+
const message = buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl);
|
|
684
|
+
log.warn(message);
|
|
538
685
|
return {
|
|
539
|
-
abortReason: "
|
|
686
|
+
abortReason: "superseded-run",
|
|
540
687
|
httpStatus: void 0,
|
|
541
688
|
outcome: "aborted"
|
|
542
689
|
};
|
|
543
690
|
}
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
691
|
+
if (isSuperseded) {
|
|
692
|
+
const message = buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl);
|
|
693
|
+
log.info(message);
|
|
694
|
+
}
|
|
695
|
+
switch (runStage) {
|
|
696
|
+
case "canceled":
|
|
697
|
+
case "initializing":
|
|
698
|
+
case "underReview": {
|
|
699
|
+
log.info(buildRunStageLogMessage(runStage, relevantRunUrl));
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case "completed": {
|
|
703
|
+
const result = buildCompletedRunResult(status);
|
|
704
|
+
switch (result.outcome) {
|
|
705
|
+
case "aborted": {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
case "failed": {
|
|
709
|
+
const message = buildFailedLogMessage(status);
|
|
710
|
+
log.warn(message);
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
case "success": {
|
|
714
|
+
const message = buildSuccessLogMessage(status);
|
|
715
|
+
log.info(message);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
default:
|
|
719
|
+
throw Error(`Unexpected outcome: ${result}`);
|
|
720
|
+
}
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
default:
|
|
724
|
+
throw Error(`Unexpected run stage: ${runStage}`);
|
|
725
|
+
}
|
|
551
726
|
}
|
|
552
|
-
|
|
553
|
-
return {
|
|
554
|
-
abortReason: "poll-timed-out",
|
|
555
|
-
httpStatus: void 0,
|
|
556
|
-
outcome: "aborted"
|
|
557
|
-
};
|
|
727
|
+
throw Error("Iterator ended unexpectedly");
|
|
558
728
|
}
|
|
559
729
|
__name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
|
|
560
730
|
|
|
@@ -580,6 +750,7 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
|
|
|
580
750
|
attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
|
|
581
751
|
generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(null, deps, apiConfig),
|
|
582
752
|
generateSignedUrlForTempTeamStorage: generateSignedUrlForTempTeamStorage.bind(null, deps, apiConfig),
|
|
753
|
+
makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(null, deps, apiConfig),
|
|
583
754
|
pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
|
|
584
755
|
};
|
|
585
756
|
}
|