@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 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
- // Fail the job.
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: "1.3.1"};
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 data = await response.json();
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, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [], relevantRunWithBugsUrl } = status;
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) => `