@qawolf/ci-sdk 0.12.0 → 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
@@ -17,11 +17,12 @@ to determine the status of your CI/CD step/job/action.
17
17
 
18
18
  ## Example: Trigger Run on Deployment
19
19
 
20
+ > ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/dd72e46ceb7f451dae4e9ef06f64a2cc).
21
+
20
22
  ```ts
21
23
  import { type DeployConfig, makeQaWolfSdk } from "@qawolf/ci-sdk";
22
24
 
23
25
  // Edit this to your needs.
24
- // See documentation here: https://qawolf.notion.site/cfe5a209256448a5bcd825bcf2d87e34
25
26
  const deployConfig: DeployConfig = {
26
27
  branch: undefined,
27
28
  commitUrl: undefined,
@@ -51,6 +52,8 @@ const { attemptNotifyDeploy } = makeQaWolfSdk({
51
52
 
52
53
  ## Example: Poll for CI Greenlight Status
53
54
 
55
+ > ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e).
56
+
54
57
  ```ts
55
58
  import { makeQaWolfSdk } from "@qawolf/ci-sdk";
56
59
 
@@ -63,7 +66,7 @@ const { pollCiGreenlightStatus } = makeQaWolfSdk({
63
66
  const { outcome } = await pollCiGreenlightStatus({
64
67
  runId,
65
68
  // Optional: Callback to be called when the run stage changes.
66
- // See https://qawolf.notion.site/dc42843313c246c8bf2a3a314ceb2c61 for
69
+ // See https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e for
67
70
  // documentation on these run stages.
68
71
  onRunStageChanged: (current, previous) => {
69
72
  console.log(current, previous);
@@ -86,10 +89,71 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
86
89
 
87
90
  - Versions below 1.0.0 still follow this scheme.
88
91
  - We recommend depending on the `^` range operator for this package, as it will not introduce breaking changes and guarantee
89
- an up-to-date API usage version.
90
- - We will provide a changelog for each release, which will be available in the [CHANGELOG.md](./CHANGELOG.md) file.
92
+ an up-to-date API usage version.
93
+ <!-- There is a limitation in NPM preventing us from using relative links and hosting other MD files when the repository hosting code is not
94
+ public, hence we inline the changelog in the README. See https://github.com/npm/feedback/discussions/666 -->
95
+ - We will provide a changelog for each release, which will be available in the Changelog section below.
91
96
  - This package major version will be bumped when an API breaking change is introduced. This won't
92
97
  happen too often, and we will reach out to you and give advance notice when it does.
93
98
  - Only top-level exports are considered part of the public API and covered by SemVer.
94
99
  - Addition of new fields in the API response types are not considered breaking changes.
95
100
  - Logs and debug messages are not considered part of the public API and can change at any time.
101
+
102
+ # Changelog
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
+
111
+ ## v0.12.1
112
+
113
+ - Export dependencies types from root module.
114
+ - Refine `DeployConfig.hostingService` type to match API requirements.
115
+ - Fix inaccessible changelog file from NPM.
116
+
117
+ ## v0.12.0
118
+
119
+ - Define a more restrictive `LogDriver` interface for easy integration with GHA
120
+ `core` interface.
121
+
122
+ ## v0.11.0
123
+
124
+ - Support `reproducedBugs` field from CI-greenlight API.
125
+
126
+ > :warning: These are the last breaking changes brought to the 0.x major version.
127
+ > These are being introduced while the SDK hasn't been advertised to the public yet.
128
+ > Future changes will follow the SemVer versioning scheme.
129
+
130
+ BREAKING CHANGES:
131
+
132
+ - `pollCiGreenlightStatus` and `attemptNotifyDeploy` now return a "result" object
133
+ with an `outcome` field that can be either `"success"`, `"failed"` or `"aborted"`,
134
+ instead of exiting the process with a non-zero code. This will provide more
135
+ flexibility to the user to decide how to handle the outcome.
136
+
137
+ ## v0.10.3
138
+
139
+ BREAKING CHANGES:
140
+
141
+ - Renamed `attemptDeploy` to `attemptNotifyDeploy`.
142
+
143
+ ## v0.10.2
144
+
145
+ - Avoid logging dots after URL names in the logs. Dots can confuse terminal
146
+ URL detection.
147
+
148
+ ## v0.10.1
149
+
150
+ - Fix TypeScript types visibility.
151
+
152
+ ## v0.10.0
153
+
154
+ Initial release.
155
+
156
+ ### Supported Endpoints
157
+
158
+ - `/api/deploy_success`
159
+ - `/api/v0/ci-greenlight/[root-run-id]`
package/dist/cjs/index.js CHANGED
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./lib/sdk/index.js"), exports);
18
+ __exportStar(require("./lib/sdk/dependencies.js"), exports);
@@ -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;
package/dist/esm/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./lib/sdk/index.js";
2
+ export * from "./lib/sdk/dependencies.js";
@@ -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
  }
@@ -1,2 +1,3 @@
1
1
  export * from "./lib/sdk/index.js";
2
+ export * from "./lib/sdk/dependencies.js";
2
3
  export type { CiGreenlightStatus, CiGreenlightStatusBase, DeployConfig, DeployStatus, } from "./lib/api/index.js";
@@ -64,7 +64,7 @@ export type CiGreenlightStatusBase = {
64
64
  };
65
65
  /**
66
66
  * The status of a CI greenlight run. See the API documentation here for
67
- * more details: https://qawolf.notion.site/dc42843313c246c8bf2a3a314ceb2c61
67
+ * more details: https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e
68
68
  */
69
69
  export type CiGreenlightStatus = CiGreenlightStatusBase & ({
70
70
  blockingBugsCount: number;
@@ -1,12 +1,16 @@
1
1
  import { ApiConfig } from "../api-types.js";
2
2
  import { SdkDependencies } from "../sdk/dependencies.js";
3
+ /**
4
+ * Refer to our official documentation for more information:
5
+ * https://qawolf.notion.site/dd72e46ceb7f451dae4e9ef06f64a2cc
6
+ */
3
7
  export type DeployConfig = {
4
8
  branch: string | undefined;
5
9
  commitUrl: string | undefined;
6
10
  deduplicationKey: string | undefined;
7
11
  deploymentType: string | undefined;
8
12
  deploymentUrl: string | undefined;
9
- hostingService: string | undefined;
13
+ hostingService: "GitHub" | "GitLab" | undefined;
10
14
  sha: string | undefined;
11
15
  variables: Record<string, string> | undefined;
12
16
  };
@@ -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.0",
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,
@@ -15,7 +15,8 @@
15
15
  "build": "./scripts/build.sh",
16
16
  "test": "jest",
17
17
  "prepare": "npm run build",
18
- "publish": "./scripts/publish.sh"
18
+ "npm-publish": "./scripts/publish.sh",
19
+ "tsc:check": "tsc --noEmit"
19
20
  },
20
21
  "keywords": [],
21
22
  "author": "Jules Sam. Randolph<jules@qawolf.com>",
package/CHANGELOG.md DELETED
@@ -1,43 +0,0 @@
1
- # v0.12.0
2
-
3
- - Define a more restrictive `LogDriver` interface for easy integration with GHA
4
- `core` interface.
5
-
6
- # v0.11.0
7
-
8
- - Support `reproducedBugs` field from CI-greenlight API.
9
-
10
- > :warning: These are the last breaking changes brought to the 0.x major version.
11
- > These are being introduced while the SDK hasn't been advertised to the public yet.
12
- > Future changes will follow the SemVer versioning scheme.
13
-
14
- BREAKING CHANGES:
15
-
16
- - `pollCiGreenlightStatus` and `attemptNotifyDeploy` now return a "result" object
17
- with an `outcome` field that can be either `"success"`, `"failed"` or `"aborted"`,
18
- instead of exiting the process with a non-zero code. This will provide more
19
- flexibility to the user to decide how to handle the outcome.
20
-
21
- # v0.10.3
22
-
23
- BREAKING CHANGES:
24
-
25
- - Renamed `attemptDeploy` to `attemptNotifyDeploy`.
26
-
27
- # v0.10.2
28
-
29
- - Avoid logging dots after URL names in the logs. Dots can confuse terminal
30
- URL detection.
31
-
32
- # v0.10.1
33
-
34
- - Fix TypeScript types visibility.
35
-
36
- # v0.10.0
37
-
38
- Initial release.
39
-
40
- ## Supported Endpoints
41
-
42
- - `/api/deploy_success`
43
- - `/api/v0/ci-greenlight/[root-run-id]`