@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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
100
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
97
|
-
|
|
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;
|