@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/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
36
36
 
37
37
  // package.json
38
38
  var package_default = {
39
- version: "1.1.1"};
39
+ version: "1.2.0"};
40
40
 
41
41
  // src/lib/sdk/defaults/userAgent.ts
42
42
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
@@ -356,6 +356,29 @@ async function generateSignedUrlForTempTeamStorage(deps, apiConfig, config) {
356
356
  }
357
357
  __name(generateSignedUrlForTempTeamStorage, "generateSignedUrlForTempTeamStorage");
358
358
 
359
+ // src/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.ts
360
+ function buildCompletedRunResult(status) {
361
+ if (status.greenlight) return {
362
+ greenlightStatus: status,
363
+ outcome: "success"
364
+ };
365
+ else return {
366
+ greenlightStatus: status,
367
+ outcome: "failed"
368
+ };
369
+ }
370
+ __name(buildCompletedRunResult, "buildCompletedRunResult");
371
+
372
+ // src/lib/sdk/domain/pollCiGreenlight/consts.ts
373
+ var pollCiGreenlightDefaults = {
374
+ abortOnSuperseded: false,
375
+ maxRetries: 10,
376
+ pollTimeout: 120 * 60 * 1e3,
377
+ retryInterval: 10 * 1e3,
378
+ runInProgressInterval: 30 * 1e3,
379
+ runUnderReviewInterval: 60 * 1e3
380
+ };
381
+
359
382
  // src/lib/api/ci-greenlight.ts
360
383
  async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { runId }, { fetch: localFetch, log }) {
361
384
  try {
@@ -411,56 +434,61 @@ async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { run
411
434
  }
412
435
  __name(fetchCiGreenlightStatus, "fetchCiGreenlightStatus");
413
436
 
414
- // src/lib/sdk/domain/poll-ci-greenlight.ts
415
- var safeDefaults = {
416
- abortOnSuperseded: false,
417
- maxRetries: 10,
418
- pollTimeout: 120 * 60 * 1e3,
419
- retryInterval: 10 * 1e3,
420
- runInProgressInterval: 30 * 1e3,
421
- runUnderReviewInterval: 60 * 1e3
422
- };
423
- async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
424
- let retries = 0;
437
+ // src/lib/sdk/domain/pollCiGreenlight/iterator.ts
438
+ async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...pollConfig }) {
425
439
  const { log } = deps;
426
- const { abortOnSuperseded = safeDefaults.abortOnSuperseded, maxRetries = safeDefaults.maxRetries, pollTimeout = safeDefaults.pollTimeout, retryInterval = safeDefaults.retryInterval, runInProgressInterval = safeDefaults.runInProgressInterval, runUnderReviewInterval = safeDefaults.runUnderReviewInterval } = pollConfig;
427
- let currentStatus = void 0;
440
+ const { maxRetries = pollCiGreenlightDefaults.maxRetries, pollTimeout = pollCiGreenlightDefaults.pollTimeout, retryInterval = pollCiGreenlightDefaults.retryInterval, runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval, runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval } = pollConfig;
441
+ let retries = 0;
442
+ let previousStatus = void 0;
428
443
  const startedAt = Date.now();
429
- let hasTimedOut = false;
430
- while (!(hasTimedOut = Date.now() - startedAt > pollTimeout)) {
444
+ while (Date.now() - startedAt <= pollTimeout) {
431
445
  const responseState = await fetchCiGreenlightStatus(apiConfig, {
432
446
  runId
433
447
  }, deps);
448
+ const elapsedMs = Date.now() - startedAt;
434
449
  if (!responseState.ok && !responseState.canRetry) {
435
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) from CI greenlight, aborting.`);
436
- return {
450
+ yield {
437
451
  abortReason: "4XX-client-error",
452
+ elapsedMs,
438
453
  httpStatus: responseState.httpStatus,
439
- outcome: "aborted"
454
+ isAborted: true
440
455
  };
456
+ return;
441
457
  }
442
458
  if (!responseState.ok) {
443
459
  retries += 1;
460
+ const { errorType, httpStatus } = responseState;
444
461
  if (retries > maxRetries) {
445
- log.error(`\u{1F6AB} Unrecoverable error${responseState.httpStatus ? ` (status ${responseState.httpStatus})` : ""} from CI greenlight, aborting after ${maxRetries} attempts.`);
446
- if (responseState.errorType === "network") {
447
- return {
448
- abortReason: "network-error",
449
- httpStatus: void 0,
450
- outcome: "aborted"
451
- };
462
+ switch (errorType) {
463
+ case "network":
464
+ yield {
465
+ abortReason: "network-error",
466
+ elapsedMs,
467
+ httpStatus: void 0,
468
+ isAborted: true
469
+ };
470
+ break;
471
+ case "not-found":
472
+ yield {
473
+ abortReason: "404-run-not-found",
474
+ elapsedMs,
475
+ httpStatus: httpStatus ?? 404,
476
+ isAborted: true
477
+ };
478
+ break;
479
+ case "server":
480
+ yield {
481
+ abortReason: "5XX-server-error",
482
+ elapsedMs,
483
+ httpStatus: httpStatus ?? 500,
484
+ isAborted: true
485
+ };
486
+ break;
487
+ default:
488
+ throw Error(`Unexpected error type: ${errorType}`);
452
489
  }
453
- let abortReason = "XXX-other-http-code";
454
- if (responseState.errorType === "not-found") abortReason = "404-run-not-found";
455
- else if (responseState.errorType === "server") abortReason = "5XX-server-error";
456
- else ;
457
- return {
458
- abortReason,
459
- httpStatus: responseState.httpStatus,
460
- outcome: "aborted"
461
- };
490
+ return;
462
491
  }
463
- const { errorType, httpStatus } = responseState;
464
492
  const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
465
493
  if (errorType === "network") log.warn(`\u274C Network error${retryLabel}`);
466
494
  else if (errorType === "not-found") log.info(`\u23F3 Run not found${retryLabel}`);
@@ -470,89 +498,231 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
470
498
  }
471
499
  retries = 0;
472
500
  const { greenlightStatus } = responseState;
473
- const previousStatus = currentStatus;
474
- currentStatus = greenlightStatus;
475
- if (!previousStatus || previousStatus.runStage !== greenlightStatus.runStage) {
476
- try {
477
- await onRunStageChanged(greenlightStatus, previousStatus);
478
- } catch (e) {
479
- log.error(`Error in onRunStageChanged callback`, e);
480
- }
481
- }
482
- const urlInfo = `See ${greenlightStatus.relevantRunUrl}`;
483
- const { greenlight, relevantRunId, relevantRunUrl, relevantRunWithBugsUrl, rootRunId, rootRunUrl, runStage } = greenlightStatus;
484
- if (rootRunId !== relevantRunId) {
485
- const supersedingDetails = `
486
- - Root: ${rootRunUrl}
487
- - Superseding: ${relevantRunUrl}`;
488
- if (abortOnSuperseded) {
489
- log.warn(`\u274C Aborting due to superseded run (option 'abortOnSuperseded' is enabled).${supersedingDetails}`);
490
- return {
491
- abortReason: "superseded-run",
501
+ const stageChanged = !previousStatus || previousStatus.runStage !== greenlightStatus.runStage;
502
+ const isFailedTerminalStage = greenlightStatus.runStage === "canceled" || greenlightStatus.runStage === "completed" && greenlightStatus.greenlight === false;
503
+ yield {
504
+ elapsedMs,
505
+ isAborted: false,
506
+ isFailedTerminalStage,
507
+ previousStatus,
508
+ stageChanged,
509
+ status: greenlightStatus
510
+ };
511
+ previousStatus = greenlightStatus;
512
+ switch (greenlightStatus.runStage) {
513
+ case "canceled": {
514
+ yield {
515
+ abortReason: "run-canceled",
516
+ elapsedMs,
492
517
  httpStatus: void 0,
493
- outcome: "aborted"
518
+ isAborted: true
494
519
  };
520
+ return;
495
521
  }
496
- log.info(`Root run was superseded:${supersedingDetails}`);
522
+ case "completed":
523
+ return;
524
+ case "initializing":
525
+ await sleep(runInProgressInterval);
526
+ break;
527
+ case "underReview":
528
+ await sleep(runUnderReviewInterval);
529
+ break;
530
+ default:
531
+ throw Error(`Unexpected run stage: ${greenlightStatus}`);
497
532
  }
498
- if (runStage === "completed") {
499
- const { blockingBugsCount, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [] } = greenlightStatus;
500
- const blockingBugList = blockingBugUrls.map((url) => `
533
+ }
534
+ yield {
535
+ abortReason: "poll-timed-out",
536
+ elapsedMs: Date.now() - startedAt,
537
+ httpStatus: void 0,
538
+ isAborted: true
539
+ };
540
+ return;
541
+ }
542
+ __name(makePollCiGreenlightStatusIterator, "makePollCiGreenlightStatusIterator");
543
+
544
+ // src/lib/sdk/domain/pollCiGreenlight/logging.ts
545
+ function buildSuccessLogMessage(status) {
546
+ const { nonBlockingBugsCount, relevantRunUrl } = status;
547
+ const urlInfo = `See ${relevantRunUrl}`;
548
+ return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
549
+ ${urlInfo}`;
550
+ }
551
+ __name(buildSuccessLogMessage, "buildSuccessLogMessage");
552
+ function buildFailedLogMessage(status) {
553
+ const { blockingBugsCount, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [], relevantRunWithBugsUrl } = status;
554
+ const blockingBugList = blockingBugUrls.map((url) => `
501
555
  \u2022 ${url}`).join("");
502
- const nonBlockingBugList = nonBlockingBugUrls.map((url) => `
556
+ const nonBlockingBugList = nonBlockingBugUrls.map((url) => `
503
557
  \u2022 ${url}`).join("");
504
- const bugsListInText = `${blockingBugUrls.length > 0 ? `
558
+ const bugsListInText = `${blockingBugUrls.length > 0 ? `
505
559
  BLOCKING BUGS:${blockingBugList}` : ""}${nonBlockingBugUrls.length > 0 ? `
506
560
  NON BLOCKING BUGS:${nonBlockingBugList}` : ""}`;
507
- if (greenlight) {
508
- log.info(`\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
509
- ${urlInfo}`);
510
- return {
511
- greenlightStatus,
512
- outcome: "success"
513
- };
514
- } else {
515
- log.warn(`\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
561
+ return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
516
562
  See ${relevantRunWithBugsUrl}
517
563
  You may review the priority of certain bugs to mark these non-blocking and retry the job.
518
- ` + bugsListInText);
519
- return {
520
- greenlightStatus,
521
- outcome: "failed"
522
- };
523
- }
524
- }
525
- if (runStage === "canceled") {
526
- log.info(`\u{1F6AB} Run was canceled. Aborting... Please contact support.
527
- ${urlInfo}`);
564
+ ` + bugsListInText;
565
+ }
566
+ __name(buildFailedLogMessage, "buildFailedLogMessage");
567
+ function buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl) {
568
+ const supersedingDetails = `
569
+ - Root: ${rootRunUrl}
570
+ - Superseding: ${relevantRunUrl}`;
571
+ return `\u274C Aborting due to superseded run (option 'abortOnSuperseded' is enabled).${supersedingDetails}`;
572
+ }
573
+ __name(buildSupersededAbortLogMessage, "buildSupersededAbortLogMessage");
574
+ function buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl) {
575
+ const supersedingDetails = `
576
+ - Root: ${rootRunUrl}
577
+ - Superseding: ${relevantRunUrl}`;
578
+ return `Root run was superseded:${supersedingDetails}`;
579
+ }
580
+ __name(buildSupersededContinueLogMessage, "buildSupersededContinueLogMessage");
581
+ function buildRunStageLogMessage(runStage, relevantRunUrl) {
582
+ const urlInfo = `See ${relevantRunUrl}`;
583
+ switch (runStage) {
584
+ case "canceled":
585
+ return `\u{1F6AB} Run was canceled. Aborting... Please contact support.
586
+ ${urlInfo}`;
587
+ case "initializing":
588
+ return `\u23F3 Run is in progress... ${urlInfo}`;
589
+ case "underReview":
590
+ return `\u{1F575}\uFE0F Run is under review... ${urlInfo}`;
591
+ case "completed":
592
+ throw Error("buildRunStageLogMessage should not be called for completed stage");
593
+ default:
594
+ throw Error(`Unexpected run stage: ${runStage}`);
595
+ }
596
+ }
597
+ __name(buildRunStageLogMessage, "buildRunStageLogMessage");
598
+
599
+ // src/lib/sdk/domain/pollCiGreenlight/mapIteratorAbortToGreenlightStatus.ts
600
+ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
601
+ const { log } = deps;
602
+ const { abortReason, httpStatus } = iteration;
603
+ switch (abortReason) {
604
+ case "4XX-client-error":
605
+ log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
606
+ return {
607
+ abortReason,
608
+ httpStatus,
609
+ outcome: "aborted"
610
+ };
611
+ case "5XX-server-error":
612
+ log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
613
+ return {
614
+ abortReason,
615
+ httpStatus,
616
+ outcome: "aborted"
617
+ };
618
+ case "404-run-not-found":
619
+ log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
620
+ return {
621
+ abortReason,
622
+ httpStatus,
623
+ outcome: "aborted"
624
+ };
625
+ case "network-error":
626
+ log.error(`\u{1F6AB} Unrecoverable network error from CI greenlight, aborting.`);
627
+ return {
628
+ abortReason,
629
+ httpStatus: void 0,
630
+ outcome: "aborted"
631
+ };
632
+ case "poll-timed-out":
633
+ log.warn(`\u274C Poll timed out after ${pollTimeout / 1e3}s`);
634
+ return {
635
+ abortReason,
636
+ httpStatus: void 0,
637
+ outcome: "aborted"
638
+ };
639
+ case "run-canceled":
640
+ log.info(`Run was canceled`);
528
641
  return {
529
- abortReason: "run-canceled",
642
+ abortReason,
530
643
  httpStatus: void 0,
531
644
  outcome: "aborted"
532
645
  };
646
+ case "XXX-other-http-code":
647
+ log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
648
+ return {
649
+ abortReason,
650
+ httpStatus,
651
+ outcome: "aborted"
652
+ };
653
+ default:
654
+ throw Error(`Unexpected abort reason: ${abortReason}`);
655
+ }
656
+ }
657
+ __name(mapIteratorAbortToGreenlightStatus, "mapIteratorAbortToGreenlightStatus");
658
+
659
+ // src/lib/sdk/domain/pollCiGreenlight/index.ts
660
+ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
661
+ const { log } = deps;
662
+ const { abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded, pollTimeout = pollCiGreenlightDefaults.pollTimeout } = pollConfig;
663
+ const iterator = makePollCiGreenlightStatusIterator(deps, apiConfig, {
664
+ runId,
665
+ ...pollConfig
666
+ });
667
+ for await (const lastPoll of iterator) {
668
+ if (lastPoll.isAborted) return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
669
+ const { previousStatus, stageChanged, status } = lastPoll;
670
+ if (stageChanged) {
671
+ try {
672
+ await onRunStageChanged(status, previousStatus);
673
+ } catch (e) {
674
+ log.error(`Error in onRunStageChanged callback`, e);
675
+ }
533
676
  }
534
- if (hasTimedOut) {
535
- log.warn(`\u274C Poll timed out after ${pollTimeout / 1e3}s. Aborting...`);
677
+ const { relevantRunId, relevantRunUrl, rootRunId, rootRunUrl, runStage } = status;
678
+ const isSuperseded = rootRunId !== relevantRunId;
679
+ const shouldAbortOnSuperseded = isSuperseded && abortOnSuperseded;
680
+ if (shouldAbortOnSuperseded) {
681
+ const message = buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl);
682
+ log.warn(message);
536
683
  return {
537
- abortReason: "poll-timed-out",
684
+ abortReason: "superseded-run",
538
685
  httpStatus: void 0,
539
686
  outcome: "aborted"
540
687
  };
541
688
  }
542
- if (runStage === "initializing") {
543
- log.info(`\u23F3 Run is in progress... ${urlInfo}`);
544
- await sleep(runInProgressInterval);
545
- } else if (runStage === "underReview") {
546
- log.info(`\u{1F575}\uFE0F Run is under review... ${urlInfo}`);
547
- await sleep(runUnderReviewInterval);
548
- } else throw Error(`Unexpected run stage: ${runStage}`);
689
+ if (isSuperseded) {
690
+ const message = buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl);
691
+ log.info(message);
692
+ }
693
+ switch (runStage) {
694
+ case "canceled":
695
+ case "initializing":
696
+ case "underReview": {
697
+ log.info(buildRunStageLogMessage(runStage, relevantRunUrl));
698
+ break;
699
+ }
700
+ case "completed": {
701
+ const result = buildCompletedRunResult(status);
702
+ switch (result.outcome) {
703
+ case "aborted": {
704
+ break;
705
+ }
706
+ case "failed": {
707
+ const message = buildFailedLogMessage(status);
708
+ log.warn(message);
709
+ break;
710
+ }
711
+ case "success": {
712
+ const message = buildSuccessLogMessage(status);
713
+ log.info(message);
714
+ break;
715
+ }
716
+ default:
717
+ throw Error(`Unexpected outcome: ${result}`);
718
+ }
719
+ return result;
720
+ }
721
+ default:
722
+ throw Error(`Unexpected run stage: ${runStage}`);
723
+ }
549
724
  }
550
- log.warn(`\u274C Poll timed out after ${pollTimeout / 1e3}s. Aborting...`);
551
- return {
552
- abortReason: "poll-timed-out",
553
- httpStatus: void 0,
554
- outcome: "aborted"
555
- };
725
+ throw Error("Iterator ended unexpectedly");
556
726
  }
557
727
  __name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
558
728
 
@@ -578,6 +748,7 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
578
748
  attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
579
749
  generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(null, deps, apiConfig),
580
750
  generateSignedUrlForTempTeamStorage: generateSignedUrlForTempTeamStorage.bind(null, deps, apiConfig),
751
+ makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(null, deps, apiConfig),
581
752
  pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
582
753
  };
583
754
  }