@qawolf/ci-sdk 1.3.1 → 2.0.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 +22 -4
- package/dist/index.cjs +126 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -31
- package/dist/index.d.ts +100 -31
- package/dist/index.js +126 -65
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -113,8 +113,14 @@ const { attemptNotifyDeploy } = makeQaWolfSdk({
|
|
|
113
113
|
|
|
114
114
|
(async () => {
|
|
115
115
|
const result = await attemptNotifyDeploy(deployConfig);
|
|
116
|
+
if (result.outcome === "skipped") {
|
|
117
|
+
// No trigger matched this deployment, a deterministic no-op rather than a
|
|
118
|
+
// failure. Let the job pass (or fail here instead if you would rather
|
|
119
|
+
// catch a trigger misconfiguration).
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
116
122
|
if (result.outcome !== "success") {
|
|
117
|
-
//
|
|
123
|
+
// "failed" or "aborted": fail the job.
|
|
118
124
|
process.exit(1);
|
|
119
125
|
}
|
|
120
126
|
const runId = result.runId;
|
|
@@ -132,16 +138,13 @@ const { attemptNotifyDeploy } = makeQaWolfSdk({
|
|
|
132
138
|
Once enabled, to use PR/MR testing functionality:
|
|
133
139
|
|
|
134
140
|
1. For `GitHub` repositories:
|
|
135
|
-
|
|
136
141
|
- Preferably, use our [GitHub Action](https://github.com/marketplace/actions/notify-qa-wolf-on-deploy)
|
|
137
142
|
- Pass `hostingService: "GitHub"`, `repository` information, and `pullRequestNumber` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
|
|
138
143
|
|
|
139
144
|
2. For `GitLab` repositories:
|
|
140
|
-
|
|
141
145
|
- Pass `hostingService: "GitLab"`, `repository` information, and `mergeRequestNumber` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
|
|
142
146
|
|
|
143
147
|
3. For `Ephemeral` deployments (no code hosting integration):
|
|
144
|
-
|
|
145
148
|
- Pass `ephemeralEnvironment: true` and `deploymentUrl` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
|
|
146
149
|
|
|
147
150
|
<a id="ci-greenlight"></a>
|
|
@@ -464,6 +467,21 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
|
|
|
464
467
|
|
|
465
468
|
# Changelog
|
|
466
469
|
|
|
470
|
+
## v2.0.0
|
|
471
|
+
|
|
472
|
+
- `attemptNotifyDeploy`: the `DeployStatus` response now carries `skippedTriggers` (triggers that matched the deployment but produced no run, each with a `reason` code and human-readable `message`) and `skippedDeployment` (set when no trigger matched at all). The result now distinguishes these cases. A trigger that matched but could not run (e.g. the environment was still syncing) returns `outcome: "failed"` with `failReason: "trigger-skipped"` and a `skippedTriggers` array, since a precondition was not met and retrying may succeed. A deployment that matched no trigger returns `outcome: "skipped"` with `skippedDeployment` (the server's reason, when it provides one). That result is deterministic given the current configuration, so it is reported as a skip rather than a failure and is logged as a warning, not an error. Preview deployments keep `outcome: "success"` but now log the skip reason. `outcome: "success"` results are unchanged.
|
|
473
|
+
- `pollCiGreenlightStatus`: the `canceled` `CiGreenlightStatus` object may now include a `reason` code and a human-readable `message` explaining why the run was canceled. These are read off the status directly (via the `onRunStageChanged` callback or `makePollCiGreenlightStatusIterator`); the top-level `pollCiGreenlightStatus` result and logs are unchanged.
|
|
474
|
+
|
|
475
|
+
BREAKING CHANGES:
|
|
476
|
+
|
|
477
|
+
- `attemptNotifyDeploy`: a deployment that matched no trigger now returns `outcome: "skipped"` instead of `outcome: "failed"` / `failReason: "no-matched-trigger"`. Callers that switch on `outcome` must handle the new `"skipped"` case; code that treated a no-trigger-match as a failure should decide whether a deterministic no-op should still fail the deploy step. `AttemptDeployFailedReason` no longer includes `"no-matched-trigger"`.
|
|
478
|
+
- `pollCiGreenlightStatus`: on the `initializing` and `canceled` run stages, the fields `relevantRunWithBugsUrl`, `workflowsDisabledAfterRunCount`, `workflowsInRunCount`, and `workflowsUnderInvestigationCount` are now optional, aligning the types with the greenlight API. Code that reads these fields on those stages must handle `undefined`.
|
|
479
|
+
- Removed the deprecated `blockingBugUrls` and `nonBlockingBugUrls` fields and the `CiGreenlightCompletedSuiteDetails` type. Use `reproducedBugs` filtered by `isBlocking`, reading each bug's `applicationUrl`.
|
|
480
|
+
|
|
481
|
+
## v1.3.2
|
|
482
|
+
|
|
483
|
+
- Bump dependencies to address security alerts.
|
|
484
|
+
|
|
467
485
|
## v1.3.1
|
|
468
486
|
|
|
469
487
|
- Address `attemptNotifyDeploy` timing-out for some teams
|
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
|
|
|
41
41
|
|
|
42
42
|
// package.json
|
|
43
43
|
var package_default = {
|
|
44
|
-
version: "
|
|
44
|
+
version: "2.0.0"};
|
|
45
45
|
|
|
46
46
|
// src/lib/sdk/defaults/userAgent.ts
|
|
47
47
|
var defaultUserAgent = `ci-sdk/${package_default.version}`;
|
|
@@ -76,6 +76,126 @@ async function logFetchError({ log, methodName, response }) {
|
|
|
76
76
|
}
|
|
77
77
|
__name(logFetchError, "logFetchError");
|
|
78
78
|
|
|
79
|
+
// src/lib/sdk/domain/attemptDeploy/handleSuccessResponse.ts
|
|
80
|
+
async function handleSuccessResponse(data, log) {
|
|
81
|
+
const { environment, results } = data;
|
|
82
|
+
const skippedTriggers = data.skippedTriggers ?? [];
|
|
83
|
+
const skippedDeployment = data.skippedDeployment;
|
|
84
|
+
const isPreviewDeployment = environment && !environment.isStatic;
|
|
85
|
+
if (environment) {
|
|
86
|
+
const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
|
|
87
|
+
log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
|
|
88
|
+
}
|
|
89
|
+
if (results.length === 0) {
|
|
90
|
+
const skipMessages = [
|
|
91
|
+
...skippedTriggers.map((trigger) => trigger.message),
|
|
92
|
+
...skippedDeployment ? [
|
|
93
|
+
skippedDeployment.message
|
|
94
|
+
] : []
|
|
95
|
+
];
|
|
96
|
+
const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
|
|
97
|
+
if (isPreviewDeployment) {
|
|
98
|
+
log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`);
|
|
99
|
+
return {
|
|
100
|
+
environmentId: environment.id,
|
|
101
|
+
outcome: "success",
|
|
102
|
+
runId: void 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (skippedTriggers.length > 0) {
|
|
106
|
+
log.error(`\u{1F6AB} No run was created for this deployment.${skipDetail}`);
|
|
107
|
+
return {
|
|
108
|
+
failReason: "trigger-skipped",
|
|
109
|
+
outcome: "failed",
|
|
110
|
+
skippedTriggers
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
log.warn(skippedDeployment ? `\u26A0\uFE0F No run was created for this deployment.${skipDetail}` : `\u26A0\uFE0F No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`);
|
|
114
|
+
return {
|
|
115
|
+
outcome: "skipped",
|
|
116
|
+
skippedDeployment
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (!environment) {
|
|
120
|
+
log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
|
|
121
|
+
return {
|
|
122
|
+
failReason: "no-environment",
|
|
123
|
+
outcome: "failed"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const failedTrigger = results.find((result) => "failureReason" in result);
|
|
127
|
+
if (failedTrigger) {
|
|
128
|
+
log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureReason}". Please contact support.`);
|
|
129
|
+
return {
|
|
130
|
+
failReason: "run-could-not-be-created",
|
|
131
|
+
outcome: "failed"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
|
|
135
|
+
if (duplicateSuite) {
|
|
136
|
+
log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`);
|
|
137
|
+
return {
|
|
138
|
+
environmentId: environment.id,
|
|
139
|
+
outcome: "success",
|
|
140
|
+
runId: duplicateSuite.duplicateSuiteId
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const successfulTrigger = results.find((result) => "createdSuiteId" in result);
|
|
144
|
+
if (!successfulTrigger) {
|
|
145
|
+
log.error(`\u{1F6AB} Run could not be created. Please contact support.`);
|
|
146
|
+
return {
|
|
147
|
+
failReason: "run-could-not-be-created",
|
|
148
|
+
outcome: "failed"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`);
|
|
152
|
+
return {
|
|
153
|
+
environmentId: environment.id,
|
|
154
|
+
outcome: "success",
|
|
155
|
+
runId: successfulTrigger.createdSuiteId
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
__name(handleSuccessResponse, "handleSuccessResponse");
|
|
159
|
+
|
|
160
|
+
// src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
|
|
161
|
+
function mapSuiteResult(result) {
|
|
162
|
+
if ("created_suite_id" in result) {
|
|
163
|
+
return {
|
|
164
|
+
createdSuiteId: result.created_suite_id,
|
|
165
|
+
triggerId: result.trigger_id
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if ("failure_reason" in result) {
|
|
169
|
+
return {
|
|
170
|
+
failureReason: result.failure_reason,
|
|
171
|
+
triggerId: result.trigger_id
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
duplicateSuiteId: result.duplicate_suite_id,
|
|
176
|
+
skipReason: result.skip_reason,
|
|
177
|
+
triggerId: result.trigger_id
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
__name(mapSuiteResult, "mapSuiteResult");
|
|
181
|
+
function mapDeploySuccessResponse(wire) {
|
|
182
|
+
return {
|
|
183
|
+
environment: wire.environment,
|
|
184
|
+
results: wire.results.map(mapSuiteResult),
|
|
185
|
+
skippedDeployment: wire.skipped_deployment && {
|
|
186
|
+
message: wire.skipped_deployment.message,
|
|
187
|
+
reason: wire.skipped_deployment.reason
|
|
188
|
+
},
|
|
189
|
+
skippedTriggers: wire.skipped_triggers?.map((trigger) => ({
|
|
190
|
+
environmentId: trigger.environment_id,
|
|
191
|
+
message: trigger.message,
|
|
192
|
+
reason: trigger.reason,
|
|
193
|
+
triggerId: trigger.trigger_id
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
__name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
|
|
198
|
+
|
|
79
199
|
// src/lib/sdk/domain/attemptDeploy/index.ts
|
|
80
200
|
async function attemptNotifyDeploy(deps, apiConfig, config) {
|
|
81
201
|
const { fetch: localFetch, log } = deps;
|
|
@@ -109,7 +229,8 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
|
|
|
109
229
|
signal: AbortSignal.timeout(5 * 60 * 1e3)
|
|
110
230
|
});
|
|
111
231
|
if (!response.ok) return handleErrorResponse(response, log);
|
|
112
|
-
const
|
|
232
|
+
const wire = await response.json();
|
|
233
|
+
const data = mapDeploySuccessResponse(wire);
|
|
113
234
|
return handleSuccessResponse(data, log);
|
|
114
235
|
} catch {
|
|
115
236
|
log.error(`\u{1F6AB} Network error from deploy_success, aborting.`);
|
|
@@ -141,68 +262,6 @@ async function handleErrorResponse(response, log) {
|
|
|
141
262
|
};
|
|
142
263
|
}
|
|
143
264
|
__name(handleErrorResponse, "handleErrorResponse");
|
|
144
|
-
async function handleSuccessResponse(data, log) {
|
|
145
|
-
const { environment, results } = data;
|
|
146
|
-
const isPreviewDeployment = environment && !environment.isStatic;
|
|
147
|
-
if (environment) {
|
|
148
|
-
const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
|
|
149
|
-
log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
|
|
150
|
-
}
|
|
151
|
-
if (results.length === 0) {
|
|
152
|
-
if (isPreviewDeployment) {
|
|
153
|
-
log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.`);
|
|
154
|
-
return {
|
|
155
|
-
environmentId: environment.id,
|
|
156
|
-
outcome: "success",
|
|
157
|
-
runId: void 0
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
log.error(`\u{1F6AB} No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`);
|
|
161
|
-
return {
|
|
162
|
-
failReason: "no-matched-trigger",
|
|
163
|
-
outcome: "failed"
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
if (!environment) {
|
|
167
|
-
log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
|
|
168
|
-
return {
|
|
169
|
-
failReason: "no-environment",
|
|
170
|
-
outcome: "failed"
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
const failedTrigger = results.find((t) => "failure_reason" in t);
|
|
174
|
-
if (failedTrigger) {
|
|
175
|
-
log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failure_reason}". Please contact support.`);
|
|
176
|
-
return {
|
|
177
|
-
failReason: "run-could-not-be-created",
|
|
178
|
-
outcome: "failed"
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const duplicateSuite = results.find((t) => "duplicate_suite_id" in t);
|
|
182
|
-
if (duplicateSuite) {
|
|
183
|
-
log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicate_suite_id} already exists for this deployment.`);
|
|
184
|
-
return {
|
|
185
|
-
environmentId: environment.id,
|
|
186
|
-
outcome: "success",
|
|
187
|
-
runId: duplicateSuite.duplicate_suite_id
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
const successfulTrigger = results.find((t) => "created_suite_id" in t);
|
|
191
|
-
if (!successfulTrigger) {
|
|
192
|
-
log.error(`\u{1F6AB} No trigger matched the request. Please contact support.`);
|
|
193
|
-
return {
|
|
194
|
-
failReason: "no-matched-trigger",
|
|
195
|
-
outcome: "failed"
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.created_suite_id}`);
|
|
199
|
-
return {
|
|
200
|
-
environmentId: environment.id,
|
|
201
|
-
outcome: "success",
|
|
202
|
-
runId: successfulTrigger.created_suite_id
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
__name(handleSuccessResponse, "handleSuccessResponse");
|
|
206
265
|
|
|
207
266
|
// src/lib/api/generate-signed-url-for-run-inputs-executables.ts
|
|
208
267
|
async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
|
|
@@ -607,7 +666,9 @@ ${urlInfo}`;
|
|
|
607
666
|
}
|
|
608
667
|
__name(buildSuccessLogMessage, "buildSuccessLogMessage");
|
|
609
668
|
function buildFailedLogMessage(status) {
|
|
610
|
-
const { blockingBugsCount,
|
|
669
|
+
const { blockingBugsCount, nonBlockingBugsCount, relevantRunWithBugsUrl, reproducedBugs } = status;
|
|
670
|
+
const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
|
|
671
|
+
const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
|
|
611
672
|
const blockingBugList = blockingBugUrls.map((url) => `
|
|
612
673
|
\u2022 ${url}`).join("");
|
|
613
674
|
const nonBlockingBugList = nonBlockingBugUrls.map((url) => `
|