@qawolf/ci-sdk 0.12.1 → 0.13.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 CHANGED
@@ -101,6 +101,13 @@ an up-to-date API usage version.
101
101
 
102
102
  # Changelog
103
103
 
104
+ ## v0.13.0
105
+
106
+ - New `pollTimeout` parameter for `pollCiGreenlightStatus` to allow for a custom
107
+ timeout in milliseconds for the polling operation. It now defaults to two hours.
108
+ - New `abortReason` and `httpStatus` fields added to the `PollCiGreenlightStatus` type
109
+ with `"aborted"` outcome to provide more context on why the poll was aborted.
110
+
104
111
  ## v0.12.1
105
112
 
106
113
  - Export dependencies types from root module.
@@ -3,24 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pollCiGreenlightStatus = void 0;
4
4
  const utils_js_1 = require("../../utils.js");
5
5
  const ci_greenlight_js_1 = require("../../api/ci-greenlight.js");
6
+ // If you change these values, make sure you update the JSDoc
7
+ // comments in the PollConfig type.
6
8
  const safeDefaults = {
7
9
  maxRetries: 10,
8
10
  retryInterval: 10 * 1000,
9
11
  runInProgressInterval: 30 * 1000,
10
12
  runUnderReviewInterval: 60 * 1000,
13
+ pollTimeout: 120 * 60 * 1000,
11
14
  };
12
15
  async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStageChanged: onRunStageChanged = () => void 0, ...pollConfig }) {
13
16
  let retries = 0;
14
17
  const { log } = deps;
15
- const { maxRetries = safeDefaults.maxRetries, retryInterval = safeDefaults.retryInterval, runInProgressInterval = safeDefaults.runInProgressInterval, runUnderReviewInterval = safeDefaults.runUnderReviewInterval, } = pollConfig;
18
+ const { maxRetries = safeDefaults.maxRetries, retryInterval = safeDefaults.retryInterval, runInProgressInterval = safeDefaults.runInProgressInterval, runUnderReviewInterval = safeDefaults.runUnderReviewInterval, pollTimeout = safeDefaults.pollTimeout, } = pollConfig;
16
19
  let currentStatus = undefined;
17
- while (true) {
20
+ const startedAt = Date.now();
21
+ let hasTimedOut = false;
22
+ while (!(hasTimedOut = Date.now() - startedAt > pollTimeout)) {
18
23
  const responseState = await (0, ci_greenlight_js_1.fetchCiGreenlightStatus)(apiConfig, {
19
24
  runId,
20
25
  }, deps);
21
26
  if (!responseState.ok && !responseState.canRetry) {
22
27
  log.error(`🚫 Unrecoverable error (status ${responseState.httpStatus}) from CI greenlight, aborting.`);
23
- return { outcome: "aborted" };
28
+ return {
29
+ outcome: "aborted",
30
+ abortReason: "4XX-client-error",
31
+ httpStatus: responseState.httpStatus,
32
+ };
24
33
  }
25
34
  if (!responseState.ok) {
26
35
  // This is a non-OK retryable status.
@@ -29,7 +38,24 @@ async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStageChange
29
38
  log.error(`🚫 Unrecoverable error${responseState.httpStatus
30
39
  ? ` (status ${responseState.httpStatus})`
31
40
  : ""} from CI greenlight, aborting after ${maxRetries} attempts.`);
32
- return { outcome: "aborted" };
41
+ if (responseState.errorType === "network")
42
+ return {
43
+ outcome: "aborted",
44
+ abortReason: "network-error",
45
+ httpStatus: undefined,
46
+ };
47
+ let abortReason = "XXX-other-http-code";
48
+ if (responseState.errorType === "not-found")
49
+ abortReason = "404-run-not-found";
50
+ else if (responseState.errorType === "server")
51
+ abortReason = "5XX-server-error";
52
+ else
53
+ (0, utils_js_1.assertType)(responseState);
54
+ return {
55
+ outcome: "aborted",
56
+ abortReason,
57
+ httpStatus: responseState.httpStatus,
58
+ };
33
59
  }
34
60
  const { errorType, httpStatus } = responseState;
35
61
  const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
@@ -88,6 +114,23 @@ async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStageChange
88
114
  return { outcome: "failed", greenlightStatus };
89
115
  }
90
116
  }
117
+ if (runStage === "canceled") {
118
+ log.info(`🚫 Run was canceled. Aborting... Please contact support.\n${urlInfo}`);
119
+ return {
120
+ outcome: "aborted",
121
+ abortReason: "run-canceled",
122
+ httpStatus: undefined,
123
+ };
124
+ }
125
+ // Non-terminal run stages
126
+ if (hasTimedOut) {
127
+ log.warn(`❌ Poll timed out after ${pollTimeout / 1000}s. Aborting...`);
128
+ return {
129
+ outcome: "aborted",
130
+ abortReason: "poll-timed-out",
131
+ httpStatus: undefined,
132
+ };
133
+ }
91
134
  if (runStage === "initializing") {
92
135
  log.info(`⏳ Run is in progress... ${urlInfo}`);
93
136
  await (0, utils_js_1.sleep)(runInProgressInterval);
@@ -96,12 +139,16 @@ async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStageChange
96
139
  log.info(`🕵️ Run is under review... ${urlInfo}`);
97
140
  await (0, utils_js_1.sleep)(runUnderReviewInterval);
98
141
  }
99
- else if (runStage === "canceled") {
100
- log.info(`🚫 Run was canceled. Aborting... Please contact support.\n${urlInfo}`);
101
- return { outcome: "aborted" };
102
- }
103
- else
142
+ else {
143
+ (0, utils_js_1.assertType)(runStage);
104
144
  throw new Error(`Unexpected run stage: ${runStage}`);
145
+ }
105
146
  }
147
+ log.warn(`❌ Poll timed out after ${pollTimeout / 1000}s. Aborting...`);
148
+ return {
149
+ outcome: "aborted",
150
+ abortReason: "poll-timed-out",
151
+ httpStatus: undefined,
152
+ };
106
153
  }
107
154
  exports.pollCiGreenlightStatus = pollCiGreenlightStatus;
@@ -1,23 +1,32 @@
1
- import { sleep, pluralize } from "../../utils.js";
1
+ import { sleep, pluralize, assertType } from "../../utils.js";
2
2
  import { fetchCiGreenlightStatus, } from "../../api/ci-greenlight.js";
3
+ // If you change these values, make sure you update the JSDoc
4
+ // comments in the PollConfig type.
3
5
  const safeDefaults = {
4
6
  maxRetries: 10,
5
7
  retryInterval: 10 * 1000,
6
8
  runInProgressInterval: 30 * 1000,
7
9
  runUnderReviewInterval: 60 * 1000,
10
+ pollTimeout: 120 * 60 * 1000,
8
11
  };
9
12
  export async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStageChanged: onRunStageChanged = () => void 0, ...pollConfig }) {
10
13
  let retries = 0;
11
14
  const { log } = deps;
12
- const { maxRetries = safeDefaults.maxRetries, retryInterval = safeDefaults.retryInterval, runInProgressInterval = safeDefaults.runInProgressInterval, runUnderReviewInterval = safeDefaults.runUnderReviewInterval, } = pollConfig;
15
+ const { maxRetries = safeDefaults.maxRetries, retryInterval = safeDefaults.retryInterval, runInProgressInterval = safeDefaults.runInProgressInterval, runUnderReviewInterval = safeDefaults.runUnderReviewInterval, pollTimeout = safeDefaults.pollTimeout, } = pollConfig;
13
16
  let currentStatus = undefined;
14
- while (true) {
17
+ const startedAt = Date.now();
18
+ let hasTimedOut = false;
19
+ while (!(hasTimedOut = Date.now() - startedAt > pollTimeout)) {
15
20
  const responseState = await fetchCiGreenlightStatus(apiConfig, {
16
21
  runId,
17
22
  }, deps);
18
23
  if (!responseState.ok && !responseState.canRetry) {
19
24
  log.error(`🚫 Unrecoverable error (status ${responseState.httpStatus}) from CI greenlight, aborting.`);
20
- return { outcome: "aborted" };
25
+ return {
26
+ outcome: "aborted",
27
+ abortReason: "4XX-client-error",
28
+ httpStatus: responseState.httpStatus,
29
+ };
21
30
  }
22
31
  if (!responseState.ok) {
23
32
  // This is a non-OK retryable status.
@@ -26,7 +35,24 @@ export async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStag
26
35
  log.error(`🚫 Unrecoverable error${responseState.httpStatus
27
36
  ? ` (status ${responseState.httpStatus})`
28
37
  : ""} from CI greenlight, aborting after ${maxRetries} attempts.`);
29
- return { outcome: "aborted" };
38
+ if (responseState.errorType === "network")
39
+ return {
40
+ outcome: "aborted",
41
+ abortReason: "network-error",
42
+ httpStatus: undefined,
43
+ };
44
+ let abortReason = "XXX-other-http-code";
45
+ if (responseState.errorType === "not-found")
46
+ abortReason = "404-run-not-found";
47
+ else if (responseState.errorType === "server")
48
+ abortReason = "5XX-server-error";
49
+ else
50
+ assertType(responseState);
51
+ return {
52
+ outcome: "aborted",
53
+ abortReason,
54
+ httpStatus: responseState.httpStatus,
55
+ };
30
56
  }
31
57
  const { errorType, httpStatus } = responseState;
32
58
  const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
@@ -85,6 +111,23 @@ export async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStag
85
111
  return { outcome: "failed", greenlightStatus };
86
112
  }
87
113
  }
114
+ if (runStage === "canceled") {
115
+ log.info(`🚫 Run was canceled. Aborting... Please contact support.\n${urlInfo}`);
116
+ return {
117
+ outcome: "aborted",
118
+ abortReason: "run-canceled",
119
+ httpStatus: undefined,
120
+ };
121
+ }
122
+ // Non-terminal run stages
123
+ if (hasTimedOut) {
124
+ log.warn(`❌ Poll timed out after ${pollTimeout / 1000}s. Aborting...`);
125
+ return {
126
+ outcome: "aborted",
127
+ abortReason: "poll-timed-out",
128
+ httpStatus: undefined,
129
+ };
130
+ }
88
131
  if (runStage === "initializing") {
89
132
  log.info(`⏳ Run is in progress... ${urlInfo}`);
90
133
  await sleep(runInProgressInterval);
@@ -93,11 +136,15 @@ export async function pollCiGreenlightStatus(deps, apiConfig, { runId, onRunStag
93
136
  log.info(`🕵️ Run is under review... ${urlInfo}`);
94
137
  await sleep(runUnderReviewInterval);
95
138
  }
96
- else if (runStage === "canceled") {
97
- log.info(`🚫 Run was canceled. Aborting... Please contact support.\n${urlInfo}`);
98
- return { outcome: "aborted" };
99
- }
100
- else
139
+ else {
140
+ assertType(runStage);
101
141
  throw new Error(`Unexpected run stage: ${runStage}`);
142
+ }
102
143
  }
144
+ log.warn(`❌ Poll timed out after ${pollTimeout / 1000}s. Aborting...`);
145
+ return {
146
+ outcome: "aborted",
147
+ abortReason: "poll-timed-out",
148
+ httpStatus: undefined,
149
+ };
103
150
  }
@@ -2,11 +2,40 @@ import { CiGreenlightStatus } from "../../api/ci-greenlight.js";
2
2
  import { ApiConfig } from "../../api-types.js";
3
3
  import { SdkDependencies } from "../dependencies.js";
4
4
  export type PollConfig = {
5
+ /**
6
+ * Maximum number of network operations retries before aborting the job.
7
+ *
8
+ * @default 10
9
+ */
5
10
  maxRetries: number;
11
+ /**
12
+ * Interval in milliseconds between network operations retries.
13
+ *
14
+ * @default 10 seconds
15
+ */
6
16
  retryInterval: number;
17
+ /**
18
+ * Interval in milliseconds between polling for a run in progress.
19
+ *
20
+ * @default 30 seconds
21
+ */
7
22
  runInProgressInterval: number;
23
+ /**
24
+ * Interval in milliseconds between polling for a run under review.
25
+ *
26
+ * @default 60 minutes
27
+ */
8
28
  runUnderReviewInterval: number;
29
+ /**
30
+ * Timeout in milliseconds before aborting this polling operation.
31
+ *
32
+ * @default 120 minutes
33
+ */
34
+ pollTimeout: number;
9
35
  };
36
+ export type HttpRelatedAbortReason = "4XX-client-error" | "404-run-not-found" | "5XX-server-error" | "XXX-other-http-code";
37
+ export type NonHttpRelatedAbortReason = "poll-timed-out" | "run-canceled" | "network-error";
38
+ export type AbortReason = NonHttpRelatedAbortReason | HttpRelatedAbortReason;
10
39
  export type PollCiGreenlightStatus = {
11
40
  /**
12
41
  * No blocking bugs were found in the run.
@@ -28,6 +57,15 @@ export type PollCiGreenlightStatus = {
28
57
  * The run was aborted due to an unrecoverable error.
29
58
  */
30
59
  outcome: "aborted";
60
+ abortReason: HttpRelatedAbortReason;
61
+ httpStatus: number;
62
+ } | {
63
+ /**
64
+ * The run was aborted due to an unrecoverable error.
65
+ */
66
+ outcome: "aborted";
67
+ abortReason: NonHttpRelatedAbortReason;
68
+ httpStatus: undefined;
31
69
  };
32
70
  export type PollCiGreenlightParams = {
33
71
  runId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/ci-sdk",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "A simple SDK for interacting with QAWolf in CI scripts.",
5
5
  "type": "module",
6
6
  "private": false,