@qawolf/ci-sdk 1.1.1 → 1.3.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 +178 -0
- package/dist/index.cjs +323 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -2
- package/dist/index.d.ts +104 -2
- package/dist/index.js +323 -102
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ 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)
|
|
18
|
+
- [Notify a Terminated Ephemeral Environment](#notify-terminated-ephemeral-environment)
|
|
17
19
|
- [Upload Run Input Artifacts](#upload-artifacts)
|
|
18
20
|
- [Requirements](#requirements)
|
|
19
21
|
- [Versioning](#versioning)
|
|
@@ -180,6 +182,174 @@ const { pollCiGreenlightStatus } = makeQaWolfSdk({
|
|
|
180
182
|
})();
|
|
181
183
|
```
|
|
182
184
|
|
|
185
|
+
<a id="ci-greenlight-iterator"></a>
|
|
186
|
+
|
|
187
|
+
## Advanced: Poll with Custom Control (Iterator)
|
|
188
|
+
|
|
189
|
+
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:
|
|
190
|
+
|
|
191
|
+
- Stop polling early based on time limits (e.g., "wait max 10 minutes in review then proceed")
|
|
192
|
+
- Stop polling based on bug counts (e.g., "proceed if only 3 or fewer blocking bugs")
|
|
193
|
+
- Implement custom logging or monitoring at each poll iteration
|
|
194
|
+
- Access detailed bug data during the "under review" stage
|
|
195
|
+
|
|
196
|
+
> ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e).
|
|
197
|
+
|
|
198
|
+
### Example: Early Exit with Custom Logic
|
|
199
|
+
|
|
200
|
+
> ⚠️ **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.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { makeQaWolfSdk } from "@qawolf/ci-sdk";
|
|
204
|
+
|
|
205
|
+
const { makePollCiGreenlightStatusIterator } = makeQaWolfSdk({
|
|
206
|
+
apiKey: "qawolf_xxxxx",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
(async () => {
|
|
210
|
+
let underReviewStartTime: number | null = null;
|
|
211
|
+
|
|
212
|
+
const iterator = makePollCiGreenlightStatusIterator({
|
|
213
|
+
runId: "your-run-id",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
for await (const iteration of iterator) {
|
|
217
|
+
// Check if the iterator yielded an abort result
|
|
218
|
+
if (iteration.isAborted) {
|
|
219
|
+
console.error(`Poll aborted: ${iteration.abortReason}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Handle status iterations
|
|
224
|
+
const { status, stageChanged } = iteration;
|
|
225
|
+
|
|
226
|
+
switch (status.runStage) {
|
|
227
|
+
case "initializing":
|
|
228
|
+
console.log(`Run stage: ${status.runStage}`);
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case "underReview": {
|
|
232
|
+
// Track when we first enter underReview
|
|
233
|
+
if (stageChanged) {
|
|
234
|
+
underReviewStartTime = Date.now();
|
|
235
|
+
console.log(`Run stage: ${status.runStage}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const timeInUnderReview = underReviewStartTime
|
|
239
|
+
? Date.now() - underReviewStartTime
|
|
240
|
+
: 0;
|
|
241
|
+
|
|
242
|
+
// Option 1: Stop after 10 minutes in review
|
|
243
|
+
if (timeInUnderReview > 10 * 60 * 1000) {
|
|
244
|
+
console.log("10 minutes in review, proceeding with deployment");
|
|
245
|
+
// Decide whether to fail CI based on your criteria
|
|
246
|
+
if (status.blockingBugsCount > 5) {
|
|
247
|
+
console.log("Too many blocking bugs, failing CI");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Option 2: Stop if bug count is acceptable
|
|
254
|
+
if (status.blockingBugsCount <= 3) {
|
|
255
|
+
console.log(
|
|
256
|
+
`Only ${status.blockingBugsCount} blocking bugs, acceptable to proceed`,
|
|
257
|
+
);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "completed":
|
|
264
|
+
console.log(`Completed with greenlight: ${status.greenlight}`);
|
|
265
|
+
if (!status.greenlight) {
|
|
266
|
+
console.log("Run failed, blocking bugs found");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
console.log("Run passed successfully");
|
|
270
|
+
return; // Exit the loop
|
|
271
|
+
|
|
272
|
+
case "canceled":
|
|
273
|
+
console.log("Run was canceled");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
|
|
276
|
+
default:
|
|
277
|
+
// Exhaustive check - TypeScript will error if a case is missing
|
|
278
|
+
status.runStage satisfies never;
|
|
279
|
+
throw new Error(`Unexpected run stage: ${status.runStage}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
})();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Yielded Iteration Object
|
|
286
|
+
|
|
287
|
+
The iterator yields a discriminated union that can be either a status update or an abort notification:
|
|
288
|
+
|
|
289
|
+
**Status Iteration** (`isAborted: false`):
|
|
290
|
+
|
|
291
|
+
- `isAborted`: `false` - Indicates a normal status update
|
|
292
|
+
- `status`: The current `CiGreenlightStatus` from the API
|
|
293
|
+
- `previousStatus`: The status from the previous iteration (undefined on first iteration)
|
|
294
|
+
- `stageChanged`: Boolean indicating if the run stage changed from the previous iteration
|
|
295
|
+
- `elapsedMs`: Milliseconds elapsed since polling started
|
|
296
|
+
|
|
297
|
+
**Aborted Iteration** (`isAborted: true`):
|
|
298
|
+
|
|
299
|
+
- `isAborted`: `true` - Indicates the polling was aborted
|
|
300
|
+
- `abortReason`: String indicating why polling was aborted (e.g., `"poll-timed-out"`, `"run-canceled"`, `"network-error"`, `"4XX-client-error"`, `"5XX-server-error"`)
|
|
301
|
+
- `httpStatus`: HTTP status code if applicable, otherwise `undefined`
|
|
302
|
+
- `elapsedMs`: Milliseconds elapsed since polling started
|
|
303
|
+
|
|
304
|
+
**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.
|
|
305
|
+
|
|
306
|
+
<a id="notify-terminated-ephemeral-environment"></a>
|
|
307
|
+
|
|
308
|
+
## Notify a Terminated Ephemeral Environment
|
|
309
|
+
|
|
310
|
+
> ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/2c45b2a994fb8096a42af8d41c811ae2).
|
|
311
|
+
|
|
312
|
+
After you terminate an ephemeral environment or release it for something else to be installed in it, you should notify QA Wolf of that fact. When you notify us, we will stop all runs targeting that environment and check whether there are any changes to flows that need to be promoted to the static base environment. This eventually results in the environment showing as "closed" in QA Wolf.
|
|
313
|
+
|
|
314
|
+
> ⚠️ **Note**: If you have the QA Wolf GitHub integration enabled for your preview testing, GitHub will notify us of PRs being merged or closed, and we use that notification to stop runs and trigger flow promotion. In this case, it is typically redundant and unnecessary to call this..
|
|
315
|
+
|
|
316
|
+
> ⚠️ **Important**: PR/MR testing functionality must be activated by QA Wolf. Please reach out to your QA Wolf representative to enable this feature and help with the setup.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import {
|
|
320
|
+
type NotifyTerminatedEphemeralEnvironmentInput,
|
|
321
|
+
makeQaWolfSdk,
|
|
322
|
+
} from "@qawolf/ci-sdk";
|
|
323
|
+
|
|
324
|
+
// Example for environmentId
|
|
325
|
+
const terminateConfig: NotifyTerminatedEphemeralEnvironmentInput = {
|
|
326
|
+
environmentId: "test-environment-id",
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Example for environmentAlias
|
|
330
|
+
const terminateConfig: NotifyTerminatedEphemeralEnvironmentInput = {
|
|
331
|
+
environmentAlias: "test-environment",
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Example for deploymentUrl
|
|
335
|
+
const terminateConfig: NotifyTerminatedEphemeralEnvironmentInput = {
|
|
336
|
+
deploymentUrl: "https://test-environment",
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const { notifyTerminatedEphemeralEnvironment } = makeQaWolfSdk({
|
|
340
|
+
apiKey: "qawolf_xxxxx",
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
(async () => {
|
|
344
|
+
const result = await notifyTerminatedEphemeralEnvironment(terminateConfig);
|
|
345
|
+
if (result.outcome !== "success") {
|
|
346
|
+
// Fail the job.
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
const environmentId = result.environmentId;
|
|
350
|
+
})();
|
|
351
|
+
```
|
|
352
|
+
|
|
183
353
|
<a id="upload-artifacts"></a>
|
|
184
354
|
|
|
185
355
|
## Upload Run Input Artifacts
|
|
@@ -294,6 +464,14 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
|
|
|
294
464
|
|
|
295
465
|
# Changelog
|
|
296
466
|
|
|
467
|
+
## v1.3.0
|
|
468
|
+
|
|
469
|
+
- New `notifyTerminatedEphemeralEnvironment` method
|
|
470
|
+
|
|
471
|
+
## v1.2.0
|
|
472
|
+
|
|
473
|
+
- New `makePollCiGreenlightStatusIterator` async generator function for advanced polling control with custom early-exit logic
|
|
474
|
+
|
|
297
475
|
## v1.1.0
|
|
298
476
|
|
|
299
477
|
- New `ephemeralEnvironment` parameter for `attemptNotifyDeploy`
|