@qawolf/ci-sdk 1.3.2 → 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/dist/index.d.ts CHANGED
@@ -63,26 +63,10 @@ type CiGreenlightBugData = {
63
63
  * The count of blocking bugs reproduced in the run.
64
64
  */
65
65
  blockingBugsCount: number;
66
- /**
67
- * The URLs of the blocking bugs.
68
- *
69
- * @deprecated This field will be removed in the next major version.
70
- * Prefer using `reproducedBugs` filtered by `isBlocking: true`, reading
71
- * the `applicationUrl` field.
72
- */
73
- blockingBugUrls: string[];
74
66
  /**
75
67
  * The count of non-blocking bugs reproduced in the run.
76
68
  */
77
69
  nonBlockingBugsCount: number;
78
- /**
79
- * The URLs of the non-blocking bugs.
80
- *
81
- * @deprecated This field will be removed in the next major version.
82
- * Prefer using `reproducedBugs` filtered by `isBlocking: false`, reading
83
- * the `applicationUrl` field.
84
- */
85
- nonBlockingBugUrls: string[];
86
70
  /**
87
71
  * Blocking bugs that were not reproduced in this run, but are still
88
72
  * opened in the run environment. This may happen when the run only covers a
@@ -91,15 +75,20 @@ type CiGreenlightBugData = {
91
75
  otherBlockingBugsInEnvironment: CiGreenlightBugDetails[];
92
76
  /**
93
77
  * Bugs that were reproduced in this run. This includes newly found bugs.
78
+ * Filter by `isBlocking` and read `applicationUrl` for blocking /
79
+ * non-blocking bug URLs.
94
80
  */
95
81
  reproducedBugs: CiGreenlightBugDetails[];
96
82
  };
83
+
97
84
  type CiGreenlightStatusBase = {
98
85
  relevantRunId: string;
99
86
  relevantRunUrl: string;
100
- relevantRunWithBugsUrl: string;
101
87
  rootRunId: string;
102
88
  rootRunUrl: string;
89
+ };
90
+ type CiGreenlightStatusExtended = {
91
+ relevantRunWithBugsUrl: string;
103
92
  workflowsDisabledAfterRunCount: number;
104
93
  workflowsInRunCount: number;
105
94
  workflowsUnderInvestigationCount: number;
@@ -111,13 +100,26 @@ type CiGreenlightStatusBase = {
111
100
  type CiGreenlightStatus = CiGreenlightStatusBase & (({
112
101
  greenlight: boolean;
113
102
  runStage: "completed";
114
- } & CiGreenlightBugData) | ({
103
+ } & CiGreenlightBugData & CiGreenlightStatusExtended) | ({
115
104
  greenlight: false;
116
105
  runStage: "underReview";
117
- } & CiGreenlightBugData) | {
106
+ } & CiGreenlightBugData & CiGreenlightStatusExtended) | ({
118
107
  greenlight: false;
119
- runStage: "initializing" | "underReview" | "canceled";
120
- });
108
+ runStage: "initializing";
109
+ } & Partial<CiGreenlightStatusExtended>) | ({
110
+ greenlight: false;
111
+ /**
112
+ * Human-readable copy explaining why the run was canceled, when the
113
+ * server provides it.
114
+ */
115
+ message?: string;
116
+ /**
117
+ * Stable, machine-readable code for why the run was canceled, when the
118
+ * server provides it. Treat as an open set of values.
119
+ */
120
+ reason?: string;
121
+ runStage: "canceled";
122
+ } & Partial<CiGreenlightStatusExtended>));
121
123
 
122
124
  type GenerateSignedUrlStatus = {
123
125
  fileLocation?: string;
@@ -142,10 +144,41 @@ type GenerateSignedUrlResponse = {
142
144
  success: boolean;
143
145
  };
144
146
 
147
+ type WireSuiteResult = {
148
+ created_suite_id: string;
149
+ trigger_id: string;
150
+ } | {
151
+ failure_reason: string;
152
+ trigger_id: string;
153
+ } | {
154
+ duplicate_suite_id: string;
155
+ skip_reason: string;
156
+ trigger_id: string;
157
+ };
158
+ type DeploySuccessWireResponse = {
159
+ environment: {
160
+ alias?: string;
161
+ id: string;
162
+ isStatic: boolean;
163
+ } | undefined;
164
+ results: WireSuiteResult[];
165
+ skipped_deployment?: {
166
+ message: string;
167
+ reason: string;
168
+ };
169
+ skipped_triggers?: {
170
+ environment_id: string;
171
+ message: string;
172
+ reason: string;
173
+ trigger_id: string;
174
+ }[];
175
+ };
176
+
145
177
  /**
146
178
  * Refer to our official documentation for more information:
147
179
  * https://qawolf.notion.site/dd72e46ceb7f451dae4e9ef06f64a2cc
148
180
  */
181
+
149
182
  type BaseDeployConfig = {
150
183
  branch: string | undefined;
151
184
  commitUrl: string | undefined;
@@ -177,19 +210,29 @@ type EphemeralDeployConfig = BaseDeployConfig & {
177
210
  };
178
211
  type DeployConfig = GitHubDeployConfig | GitLabDeployConfig | EphemeralDeployConfig;
179
212
  type DeploySuccessSuiteCreatedResult = {
180
- created_suite_id: string;
181
- trigger_id: string;
213
+ createdSuiteId: string;
214
+ triggerId: string;
182
215
  };
183
216
  type DeploySuccessSuiteCreationFailureResult = {
184
- failure_reason: string;
185
- trigger_id: string;
217
+ failureReason: string;
218
+ triggerId: string;
186
219
  };
187
220
  type DeploySuccessSuiteAlreadyExistsResult = {
188
- duplicate_suite_id: string;
189
- skip_reason: string;
190
- trigger_id: string;
221
+ duplicateSuiteId: string;
222
+ skipReason: string;
223
+ triggerId: string;
191
224
  };
192
225
  type DeploySuccessSuiteResult = DeploySuccessSuiteCreatedResult | DeploySuccessSuiteCreationFailureResult | DeploySuccessSuiteAlreadyExistsResult;
226
+ type DeploySuccessSkippedTrigger = {
227
+ environmentId: string;
228
+ message: string;
229
+ reason: string;
230
+ triggerId: string;
231
+ };
232
+ type DeploySuccessDeploymentSkipped = {
233
+ message: string;
234
+ reason: string;
235
+ };
193
236
  type DeployStatus = {
194
237
  environment: {
195
238
  alias?: string;
@@ -197,6 +240,8 @@ type DeployStatus = {
197
240
  isStatic: boolean;
198
241
  } | undefined;
199
242
  results: Array<DeploySuccessSuiteResult>;
243
+ skippedDeployment?: DeploySuccessDeploymentSkipped;
244
+ skippedTriggers?: DeploySuccessSkippedTrigger[];
200
245
  };
201
246
  type DeployStatusSuccessResponse = {
202
247
  data: DeployStatus;
@@ -209,7 +254,6 @@ type DeployStatusErrorResponse = {
209
254
  status: number | undefined;
210
255
  };
211
256
  type DeployStatusResponse = DeployStatusSuccessResponse | DeployStatusErrorResponse;
212
- type AttemptDeployFailedReason = "no-matched-trigger" | "no-environment" | "run-could-not-be-created" | "duplicate-suite";
213
257
  type AttemptDeployHttpAbortReason = "4XX-client-error" | "401-unauthorized" | "403-forbidden" | "5XX-server-error" | "network-error";
214
258
  type AttemptDeployNonHttpAbortReason = "network-error";
215
259
  type AttemptDeployAbortReason = AttemptDeployHttpAbortReason | AttemptDeployNonHttpAbortReason;
@@ -227,7 +271,29 @@ type NotifyDeployStatus = {
227
271
  */
228
272
  runId: string | undefined;
229
273
  } | {
230
- failReason: AttemptDeployFailedReason;
274
+ /**
275
+ * One or more triggers matched the deployment but could not run (e.g. the
276
+ * environment was still syncing). A precondition was not met, so this is a
277
+ * failure rather than a skip: retrying once the environment is ready may
278
+ * create a run.
279
+ */
280
+ failReason: "trigger-skipped";
281
+ outcome: "failed";
282
+ skippedTriggers: DeploySuccessSkippedTrigger[];
283
+ } | {
284
+ /**
285
+ * No trigger matched the deployment. This is deterministic given the
286
+ * current configuration (repeating the request with the same config
287
+ * yields the same result), so it is a skip rather than a failure, and the
288
+ * caller should not treat it as an error. `skippedDeployment` carries the
289
+ * server's reason when it provided one (undefined on older servers or the
290
+ * legacy arm); there is no separate SDK-level reason code, since
291
+ * `outcome: "skipped"` only ever means no trigger matched.
292
+ */
293
+ outcome: "skipped";
294
+ skippedDeployment: DeploySuccessDeploymentSkipped | undefined;
295
+ } | {
296
+ failReason: "no-environment" | "run-could-not-be-created";
231
297
  /**
232
298
  * A run could not be created.
233
299
  */
@@ -247,6 +313,9 @@ type NotifyDeployStatus = {
247
313
  */
248
314
  outcome: "aborted";
249
315
  };
316
+ type AttemptDeployFailedReason = Extract<NotifyDeployStatus, {
317
+ outcome: "failed";
318
+ }>["failReason"];
250
319
 
251
320
  type NotifyTerminatedEphemeralEnvironmentInput = {
252
321
  environmentId: string;
@@ -455,4 +524,4 @@ declare function makeQaWolfSdk({ apiKey, serviceBase, userAgent, }: {
455
524
  pollCiGreenlightStatus: (args_0: PollCiGreenlightParams) => Promise<PollCiGreenlightStatus>;
456
525
  };
457
526
 
458
- export { type AttemptDeployAbortReason, type AttemptDeployFailedReason, type AttemptDeployHttpAbortReason, type AttemptDeployNonHttpAbortReason, type CiGreenlightStatus, type CiGreenlightStatusBase, type DeployConfig, type DeployStatus, type DeployStatusErrorResponse, type DeployStatusResponse, type DeployStatusSuccessResponse, type DeploySuccessSuiteAlreadyExistsResult, type DeploySuccessSuiteCreatedResult, type DeploySuccessSuiteCreationFailureResult, type DeploySuccessSuiteResult, type EphemeralDeployConfig, type FetchDriver, type GenerateSignedUrlAbortReason$1 as GenerateSignedUrlAbortReason, type GenerateSignedUrlConfig, type GenerateSignedUrlResponse, type GenerateSignedUrlStatus, type GitHubDeployConfig, type GitLabDeployConfig, type MakePollCiGreenlightStatusIteratorParams, type NotifyDeployStatus, type NotifyTerminatedEphemeralEnvironmentAbortReason, type NotifyTerminatedEphemeralEnvironmentFailedReason, type NotifyTerminatedEphemeralEnvironmentHttpAbortReason, type NotifyTerminatedEphemeralEnvironmentInput, type NotifyTerminatedEphemeralEnvironmentNonHttpAbortReason, type NotifyTerminatedEphemeralEnvironmentResult, type PollCiGreenlightAbortReason, type PollCiGreenlightHttpAbortReason, type PollCiGreenlightIteration, type PollCiGreenlightIteratorHttpAborted, type PollCiGreenlightIteratorNonHttpAborted, type PollCiGreenlightNonHttpAbortReason, type PollCiGreenlightParams, type PollCiGreenlightPollConfig, type PollCiGreenlightStatus, type PollCiGreenlightStatusIteration, type SdkDependencies, type TerminatedEphemeralEnvironmentSuccessResult, attemptNotifyDeploy, makeQaWolfSdk };
527
+ export { type AttemptDeployAbortReason, type AttemptDeployFailedReason, type AttemptDeployHttpAbortReason, type AttemptDeployNonHttpAbortReason, type CiGreenlightStatus, type CiGreenlightStatusBase, type DeployConfig, type DeployStatus, type DeployStatusErrorResponse, type DeployStatusResponse, type DeployStatusSuccessResponse, type DeploySuccessDeploymentSkipped, type DeploySuccessSkippedTrigger, type DeploySuccessSuiteAlreadyExistsResult, type DeploySuccessSuiteCreatedResult, type DeploySuccessSuiteCreationFailureResult, type DeploySuccessSuiteResult, type DeploySuccessWireResponse, type EphemeralDeployConfig, type FetchDriver, type GenerateSignedUrlAbortReason$1 as GenerateSignedUrlAbortReason, type GenerateSignedUrlConfig, type GenerateSignedUrlResponse, type GenerateSignedUrlStatus, type GitHubDeployConfig, type GitLabDeployConfig, type MakePollCiGreenlightStatusIteratorParams, type NotifyDeployStatus, type NotifyTerminatedEphemeralEnvironmentAbortReason, type NotifyTerminatedEphemeralEnvironmentFailedReason, type NotifyTerminatedEphemeralEnvironmentHttpAbortReason, type NotifyTerminatedEphemeralEnvironmentInput, type NotifyTerminatedEphemeralEnvironmentNonHttpAbortReason, type NotifyTerminatedEphemeralEnvironmentResult, type PollCiGreenlightAbortReason, type PollCiGreenlightHttpAbortReason, type PollCiGreenlightIteration, type PollCiGreenlightIteratorHttpAborted, type PollCiGreenlightIteratorNonHttpAborted, type PollCiGreenlightNonHttpAbortReason, type PollCiGreenlightParams, type PollCiGreenlightPollConfig, type PollCiGreenlightStatus, type PollCiGreenlightStatusIteration, type SdkDependencies, type TerminatedEphemeralEnvironmentSuccessResult, attemptNotifyDeploy, makeQaWolfSdk };
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
39
39
 
40
40
  // package.json
41
41
  var package_default = {
42
- version: "1.3.2"};
42
+ version: "2.0.0"};
43
43
 
44
44
  // src/lib/sdk/defaults/userAgent.ts
45
45
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
@@ -74,6 +74,126 @@ async function logFetchError({ log, methodName, response }) {
74
74
  }
75
75
  __name(logFetchError, "logFetchError");
76
76
 
77
+ // src/lib/sdk/domain/attemptDeploy/handleSuccessResponse.ts
78
+ async function handleSuccessResponse(data, log) {
79
+ const { environment, results } = data;
80
+ const skippedTriggers = data.skippedTriggers ?? [];
81
+ const skippedDeployment = data.skippedDeployment;
82
+ const isPreviewDeployment = environment && !environment.isStatic;
83
+ if (environment) {
84
+ const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
85
+ log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
86
+ }
87
+ if (results.length === 0) {
88
+ const skipMessages = [
89
+ ...skippedTriggers.map((trigger) => trigger.message),
90
+ ...skippedDeployment ? [
91
+ skippedDeployment.message
92
+ ] : []
93
+ ];
94
+ const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
95
+ if (isPreviewDeployment) {
96
+ log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`);
97
+ return {
98
+ environmentId: environment.id,
99
+ outcome: "success",
100
+ runId: void 0
101
+ };
102
+ }
103
+ if (skippedTriggers.length > 0) {
104
+ log.error(`\u{1F6AB} No run was created for this deployment.${skipDetail}`);
105
+ return {
106
+ failReason: "trigger-skipped",
107
+ outcome: "failed",
108
+ skippedTriggers
109
+ };
110
+ }
111
+ 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.`);
112
+ return {
113
+ outcome: "skipped",
114
+ skippedDeployment
115
+ };
116
+ }
117
+ if (!environment) {
118
+ log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
119
+ return {
120
+ failReason: "no-environment",
121
+ outcome: "failed"
122
+ };
123
+ }
124
+ const failedTrigger = results.find((result) => "failureReason" in result);
125
+ if (failedTrigger) {
126
+ log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureReason}". Please contact support.`);
127
+ return {
128
+ failReason: "run-could-not-be-created",
129
+ outcome: "failed"
130
+ };
131
+ }
132
+ const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
133
+ if (duplicateSuite) {
134
+ log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`);
135
+ return {
136
+ environmentId: environment.id,
137
+ outcome: "success",
138
+ runId: duplicateSuite.duplicateSuiteId
139
+ };
140
+ }
141
+ const successfulTrigger = results.find((result) => "createdSuiteId" in result);
142
+ if (!successfulTrigger) {
143
+ log.error(`\u{1F6AB} Run could not be created. Please contact support.`);
144
+ return {
145
+ failReason: "run-could-not-be-created",
146
+ outcome: "failed"
147
+ };
148
+ }
149
+ log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`);
150
+ return {
151
+ environmentId: environment.id,
152
+ outcome: "success",
153
+ runId: successfulTrigger.createdSuiteId
154
+ };
155
+ }
156
+ __name(handleSuccessResponse, "handleSuccessResponse");
157
+
158
+ // src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
159
+ function mapSuiteResult(result) {
160
+ if ("created_suite_id" in result) {
161
+ return {
162
+ createdSuiteId: result.created_suite_id,
163
+ triggerId: result.trigger_id
164
+ };
165
+ }
166
+ if ("failure_reason" in result) {
167
+ return {
168
+ failureReason: result.failure_reason,
169
+ triggerId: result.trigger_id
170
+ };
171
+ }
172
+ return {
173
+ duplicateSuiteId: result.duplicate_suite_id,
174
+ skipReason: result.skip_reason,
175
+ triggerId: result.trigger_id
176
+ };
177
+ }
178
+ __name(mapSuiteResult, "mapSuiteResult");
179
+ function mapDeploySuccessResponse(wire) {
180
+ return {
181
+ environment: wire.environment,
182
+ results: wire.results.map(mapSuiteResult),
183
+ skippedDeployment: wire.skipped_deployment && {
184
+ message: wire.skipped_deployment.message,
185
+ reason: wire.skipped_deployment.reason
186
+ },
187
+ skippedTriggers: wire.skipped_triggers?.map((trigger) => ({
188
+ environmentId: trigger.environment_id,
189
+ message: trigger.message,
190
+ reason: trigger.reason,
191
+ triggerId: trigger.trigger_id
192
+ }))
193
+ };
194
+ }
195
+ __name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
196
+
77
197
  // src/lib/sdk/domain/attemptDeploy/index.ts
78
198
  async function attemptNotifyDeploy(deps, apiConfig, config) {
79
199
  const { fetch: localFetch, log } = deps;
@@ -107,7 +227,8 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
107
227
  signal: AbortSignal.timeout(5 * 60 * 1e3)
108
228
  });
109
229
  if (!response.ok) return handleErrorResponse(response, log);
110
- const data = await response.json();
230
+ const wire = await response.json();
231
+ const data = mapDeploySuccessResponse(wire);
111
232
  return handleSuccessResponse(data, log);
112
233
  } catch {
113
234
  log.error(`\u{1F6AB} Network error from deploy_success, aborting.`);
@@ -139,68 +260,6 @@ async function handleErrorResponse(response, log) {
139
260
  };
140
261
  }
141
262
  __name(handleErrorResponse, "handleErrorResponse");
142
- async function handleSuccessResponse(data, log) {
143
- const { environment, results } = data;
144
- const isPreviewDeployment = environment && !environment.isStatic;
145
- if (environment) {
146
- const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
147
- log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
148
- }
149
- if (results.length === 0) {
150
- if (isPreviewDeployment) {
151
- log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.`);
152
- return {
153
- environmentId: environment.id,
154
- outcome: "success",
155
- runId: void 0
156
- };
157
- }
158
- 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.`);
159
- return {
160
- failReason: "no-matched-trigger",
161
- outcome: "failed"
162
- };
163
- }
164
- if (!environment) {
165
- log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
166
- return {
167
- failReason: "no-environment",
168
- outcome: "failed"
169
- };
170
- }
171
- const failedTrigger = results.find((t) => "failure_reason" in t);
172
- if (failedTrigger) {
173
- log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failure_reason}". Please contact support.`);
174
- return {
175
- failReason: "run-could-not-be-created",
176
- outcome: "failed"
177
- };
178
- }
179
- const duplicateSuite = results.find((t) => "duplicate_suite_id" in t);
180
- if (duplicateSuite) {
181
- log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicate_suite_id} already exists for this deployment.`);
182
- return {
183
- environmentId: environment.id,
184
- outcome: "success",
185
- runId: duplicateSuite.duplicate_suite_id
186
- };
187
- }
188
- const successfulTrigger = results.find((t) => "created_suite_id" in t);
189
- if (!successfulTrigger) {
190
- log.error(`\u{1F6AB} No trigger matched the request. Please contact support.`);
191
- return {
192
- failReason: "no-matched-trigger",
193
- outcome: "failed"
194
- };
195
- }
196
- log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.created_suite_id}`);
197
- return {
198
- environmentId: environment.id,
199
- outcome: "success",
200
- runId: successfulTrigger.created_suite_id
201
- };
202
- }
203
- __name(handleSuccessResponse, "handleSuccessResponse");
204
263
 
205
264
  // src/lib/api/generate-signed-url-for-run-inputs-executables.ts
206
265
  async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
@@ -605,7 +664,9 @@ ${urlInfo}`;
605
664
  }
606
665
  __name(buildSuccessLogMessage, "buildSuccessLogMessage");
607
666
  function buildFailedLogMessage(status) {
608
- const { blockingBugsCount, blockingBugUrls = [], nonBlockingBugsCount, nonBlockingBugUrls = [], relevantRunWithBugsUrl } = status;
667
+ const { blockingBugsCount, nonBlockingBugsCount, relevantRunWithBugsUrl, reproducedBugs } = status;
668
+ const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
669
+ const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
609
670
  const blockingBugList = blockingBugUrls.map((url) => `
610
671
  \u2022 ${url}`).join("");
611
672
  const nonBlockingBugList = nonBlockingBugUrls.map((url) => `