@qawolf/ci-sdk 2.0.0 → 2.2.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
@@ -134,6 +134,12 @@ type GenerateSignedUrlStatus = {
134
134
  type GenerateSignedUrlAbortReason$1 = "client-network-error" | "sdk-argument-validation-failed" | "400-invalid-request" | "401-invalid-credentials" | "403-forbidden" | "5XX-server-error" | "XXX-other-http-code";
135
135
  type GenerateSignedUrlConfig = {
136
136
  destinationFilePath: string;
137
+ /**
138
+ * The workspace to generate the URL for. Required when authenticating with an
139
+ * organization API key, which is not tied to a single workspace. Omit it when
140
+ * authenticating with a workspace API key, which already implies one.
141
+ */
142
+ workspaceId?: string;
137
143
  };
138
144
  type GenerateSignedUrlResponse = {
139
145
  errorMessage?: string;
@@ -144,41 +150,6 @@ type GenerateSignedUrlResponse = {
144
150
  success: boolean;
145
151
  };
146
152
 
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
-
177
- /**
178
- * Refer to our official documentation for more information:
179
- * https://qawolf.notion.site/dd72e46ceb7f451dae4e9ef06f64a2cc
180
- */
181
-
182
153
  type BaseDeployConfig = {
183
154
  branch: string | undefined;
184
155
  commitUrl: string | undefined;
@@ -187,6 +158,12 @@ type BaseDeployConfig = {
187
158
  deploymentUrl: string | undefined;
188
159
  sha: string | undefined;
189
160
  variables: Record<string, string> | undefined;
161
+ /**
162
+ * The workspace this deployment belongs to. Required when authenticating with
163
+ * an organization API key, which is not tied to a single workspace. Omit it
164
+ * when authenticating with a workspace API key, which already implies one.
165
+ */
166
+ workspaceId?: string;
190
167
  };
191
168
  type GitHubDeployConfig = BaseDeployConfig & {
192
169
  hostingService: "GitHub" | undefined;
@@ -209,6 +186,39 @@ type EphemeralDeployConfig = BaseDeployConfig & {
209
186
  ephemeralEnvironment: true;
210
187
  };
211
188
  type DeployConfig = GitHubDeployConfig | GitLabDeployConfig | EphemeralDeployConfig;
189
+
190
+ type WireSuiteResult = {
191
+ created_suite_id: string;
192
+ trigger_id: string;
193
+ } | {
194
+ failure_reason: string;
195
+ trigger_id: string;
196
+ } | {
197
+ duplicate_suite_id: string;
198
+ skip_reason: string;
199
+ trigger_id: string;
200
+ } | {
201
+ trigger_id: string;
202
+ };
203
+ type DeploySuccessWireResponse = {
204
+ environment: {
205
+ alias?: string;
206
+ id: string;
207
+ isStatic: boolean;
208
+ } | undefined;
209
+ results: WireSuiteResult[];
210
+ skipped_deployment?: {
211
+ message: string;
212
+ reason: string;
213
+ };
214
+ skipped_triggers?: {
215
+ environment_id: string;
216
+ message: string;
217
+ reason: string;
218
+ trigger_id: string;
219
+ }[];
220
+ };
221
+
212
222
  type DeploySuccessSuiteCreatedResult = {
213
223
  createdSuiteId: string;
214
224
  triggerId: string;
@@ -222,7 +232,11 @@ type DeploySuccessSuiteAlreadyExistsResult = {
222
232
  skipReason: string;
223
233
  triggerId: string;
224
234
  };
225
- type DeploySuccessSuiteResult = DeploySuccessSuiteCreatedResult | DeploySuccessSuiteCreationFailureResult | DeploySuccessSuiteAlreadyExistsResult;
235
+ type DeploySuccessSuiteUnrecognizedResult = {
236
+ triggerId: string;
237
+ unrecognized: true;
238
+ };
239
+ type DeploySuccessSuiteResult = DeploySuccessSuiteCreatedResult | DeploySuccessSuiteCreationFailureResult | DeploySuccessSuiteAlreadyExistsResult | DeploySuccessSuiteUnrecognizedResult;
226
240
  type DeploySuccessSkippedTrigger = {
227
241
  environmentId: string;
228
242
  message: string;
@@ -255,7 +269,7 @@ type DeployStatusErrorResponse = {
255
269
  };
256
270
  type DeployStatusResponse = DeployStatusSuccessResponse | DeployStatusErrorResponse;
257
271
  type AttemptDeployHttpAbortReason = "4XX-client-error" | "401-unauthorized" | "403-forbidden" | "5XX-server-error" | "network-error";
258
- type AttemptDeployNonHttpAbortReason = "network-error";
272
+ type AttemptDeployNonHttpAbortReason = "network-error" | "invalid-response-body";
259
273
  type AttemptDeployAbortReason = AttemptDeployHttpAbortReason | AttemptDeployNonHttpAbortReason;
260
274
  type NotifyDeployStatus = {
261
275
  /**
@@ -293,7 +307,12 @@ type NotifyDeployStatus = {
293
307
  outcome: "skipped";
294
308
  skippedDeployment: DeploySuccessDeploymentSkipped | undefined;
295
309
  } | {
296
- failReason: "no-environment" | "run-could-not-be-created";
310
+ /**
311
+ * `unknown-result` means the response carried a result arm this SDK
312
+ * version does not recognize (the backend is newer). The caller should
313
+ * upgrade the SDK; the outcome could not be classified.
314
+ */
315
+ failReason: "no-environment" | "run-could-not-be-created" | "unknown-result";
297
316
  /**
298
317
  * A run could not be created.
299
318
  */
@@ -317,13 +336,24 @@ type AttemptDeployFailedReason = Extract<NotifyDeployStatus, {
317
336
  outcome: "failed";
318
337
  }>["failReason"];
319
338
 
339
+ /**
340
+ * The workspace that owns the environment. Required when authenticating with an
341
+ * organization API key, which is not tied to a single workspace. Omit it when
342
+ * authenticating with a workspace API key, which already implies one.
343
+ *
344
+ * Absent from the `environmentId` lookup, where the environment identifies its
345
+ * own workspace.
346
+ */
347
+ type WorkspaceLookup = {
348
+ workspaceId?: string;
349
+ };
320
350
  type NotifyTerminatedEphemeralEnvironmentInput = {
321
351
  environmentId: string;
322
- } | {
352
+ } | ({
323
353
  deploymentUrl: string;
324
- } | {
354
+ } & WorkspaceLookup) | ({
325
355
  environmentAlias: string;
326
- };
356
+ } & WorkspaceLookup);
327
357
  type NotifyTerminatedEphemeralEnvironmentFailedReason = "environmentNotFound" | "missingLookup" | "alreadyTerminated";
328
358
  type NotifyTerminatedEphemeralEnvironmentHttpAbortReason = "4XX-client-error" | "401-unauthorized" | "403-forbidden" | "409-conflict" | "5XX-server-error" | "network-error";
329
359
  type NotifyTerminatedEphemeralEnvironmentNonHttpAbortReason = "network-error";
@@ -524,4 +554,4 @@ declare function makeQaWolfSdk({ apiKey, serviceBase, userAgent, }: {
524
554
  pollCiGreenlightStatus: (args_0: PollCiGreenlightParams) => Promise<PollCiGreenlightStatus>;
525
555
  };
526
556
 
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 };
557
+ 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 DeploySuccessSuiteUnrecognizedResult, 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: "2.0.0"};
42
+ version: "2.2.0"};
43
43
 
44
44
  // src/lib/sdk/defaults/userAgent.ts
45
45
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
@@ -121,6 +121,14 @@ async function handleSuccessResponse(data, log) {
121
121
  outcome: "failed"
122
122
  };
123
123
  }
124
+ const unrecognizedResult = results.find((result) => "unrecognized" in result);
125
+ if (unrecognizedResult) {
126
+ log.error(`\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`);
127
+ return {
128
+ failReason: "unknown-result",
129
+ outcome: "failed"
130
+ };
131
+ }
124
132
  const failedTrigger = results.find((result) => "failureReason" in result);
125
133
  if (failedTrigger) {
126
134
  log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureReason}". Please contact support.`);
@@ -169,10 +177,16 @@ function mapSuiteResult(result) {
169
177
  triggerId: result.trigger_id
170
178
  };
171
179
  }
180
+ if ("duplicate_suite_id" in result) {
181
+ return {
182
+ duplicateSuiteId: result.duplicate_suite_id,
183
+ skipReason: result.skip_reason,
184
+ triggerId: result.trigger_id
185
+ };
186
+ }
172
187
  return {
173
- duplicateSuiteId: result.duplicate_suite_id,
174
- skipReason: result.skip_reason,
175
- triggerId: result.trigger_id
188
+ triggerId: result.trigger_id,
189
+ unrecognized: true
176
190
  };
177
191
  }
178
192
  __name(mapSuiteResult, "mapSuiteResult");
@@ -194,11 +208,61 @@ function mapDeploySuccessResponse(wire) {
194
208
  }
195
209
  __name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
196
210
 
211
+ // src/lib/sdk/domain/attemptDeploy/parseDeploySuccessWireResponse.ts
212
+ function isRecord(value) {
213
+ return typeof value === "object" && value !== null && !Array.isArray(value);
214
+ }
215
+ __name(isRecord, "isRecord");
216
+ function isWireSuiteResult(value) {
217
+ if (!isRecord(value)) return false;
218
+ if (typeof value.trigger_id !== "string") return false;
219
+ if ("created_suite_id" in value) return typeof value.created_suite_id === "string";
220
+ if ("failure_reason" in value) return typeof value.failure_reason === "string";
221
+ if ("duplicate_suite_id" in value) {
222
+ return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
223
+ }
224
+ return true;
225
+ }
226
+ __name(isWireSuiteResult, "isWireSuiteResult");
227
+ function isEnvironment(value) {
228
+ if (!isRecord(value)) return false;
229
+ if (typeof value.id !== "string") return false;
230
+ if (typeof value.isStatic !== "boolean") return false;
231
+ if (value.alias !== void 0 && typeof value.alias !== "string") return false;
232
+ return true;
233
+ }
234
+ __name(isEnvironment, "isEnvironment");
235
+ function isSkippedDeployment(value) {
236
+ if (!isRecord(value)) return false;
237
+ return typeof value.message === "string" && typeof value.reason === "string";
238
+ }
239
+ __name(isSkippedDeployment, "isSkippedDeployment");
240
+ function isSkippedTrigger(value) {
241
+ if (!isRecord(value)) return false;
242
+ return typeof value.environment_id === "string" && typeof value.message === "string" && typeof value.reason === "string" && typeof value.trigger_id === "string";
243
+ }
244
+ __name(isSkippedTrigger, "isSkippedTrigger");
245
+ function isDeploySuccessWireResponse(value) {
246
+ if (!isRecord(value)) return false;
247
+ if (value.environment !== void 0 && !isEnvironment(value.environment)) return false;
248
+ if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult)) return false;
249
+ if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment)) return false;
250
+ if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger))) return false;
251
+ return true;
252
+ }
253
+ __name(isDeploySuccessWireResponse, "isDeploySuccessWireResponse");
254
+ function parseDeploySuccessWireResponse(value) {
255
+ return isDeploySuccessWireResponse(value) ? value : "invalid";
256
+ }
257
+ __name(parseDeploySuccessWireResponse, "parseDeploySuccessWireResponse");
258
+
197
259
  // src/lib/sdk/domain/attemptDeploy/index.ts
198
260
  async function attemptNotifyDeploy(deps, apiConfig, config) {
199
261
  const { fetch: localFetch, log } = deps;
262
+ let response;
263
+ let bodyText;
200
264
  try {
201
- const response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
265
+ response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
202
266
  body: JSON.stringify({
203
267
  branch: config.branch,
204
268
  commit_url: config.commitUrl,
@@ -214,7 +278,8 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
214
278
  repository: config.repository
215
279
  },
216
280
  sha: config.sha,
217
- variables: config.variables
281
+ variables: config.variables,
282
+ workspaceId: config.workspaceId
218
283
  }),
219
284
  headers: {
220
285
  Authorization: `Bearer ${apiConfig.apiKey}`,
@@ -227,9 +292,7 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
227
292
  signal: AbortSignal.timeout(5 * 60 * 1e3)
228
293
  });
229
294
  if (!response.ok) return handleErrorResponse(response, log);
230
- const wire = await response.json();
231
- const data = mapDeploySuccessResponse(wire);
232
- return handleSuccessResponse(data, log);
295
+ bodyText = await response.text();
233
296
  } catch {
234
297
  log.error(`\u{1F6AB} Network error from deploy_success, aborting.`);
235
298
  return {
@@ -238,8 +301,29 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
238
301
  outcome: "aborted"
239
302
  };
240
303
  }
304
+ let rawBody;
305
+ try {
306
+ rawBody = JSON.parse(bodyText);
307
+ } catch {
308
+ return abortInvalidResponseBody(log, "the response body was not JSON");
309
+ }
310
+ const wire = parseDeploySuccessWireResponse(rawBody);
311
+ if (wire === "invalid") {
312
+ return abortInvalidResponseBody(log, "the response body did not match the expected shape");
313
+ }
314
+ const data = mapDeploySuccessResponse(wire);
315
+ return handleSuccessResponse(data, log);
241
316
  }
242
317
  __name(attemptNotifyDeploy, "attemptNotifyDeploy");
318
+ function abortInvalidResponseBody(log, detail) {
319
+ log.error(`\u{1F6AB} Invalid response from deploy_success (${detail}), aborting.`);
320
+ return {
321
+ abortReason: "invalid-response-body",
322
+ httpStatus: void 0,
323
+ outcome: "aborted"
324
+ };
325
+ }
326
+ __name(abortInvalidResponseBody, "abortInvalidResponseBody");
243
327
  async function handleErrorResponse(response, log) {
244
328
  const status = response.status;
245
329
  let abortReason;
@@ -261,10 +345,28 @@ async function handleErrorResponse(response, log) {
261
345
  }
262
346
  __name(handleErrorResponse, "handleErrorResponse");
263
347
 
348
+ // src/lib/api/signed-url.ts
349
+ var signedUrlPaths = {
350
+ runInputsExecutables: "/api/v0/run-inputs-executables-signed-urls",
351
+ teamStorage: "/api/v0/team-storage-signed-url"
352
+ };
353
+ function buildSignedUrl({ destinationFilePath, endpoint, serviceBase, workspaceId }) {
354
+ const url = new URL(signedUrlPaths[endpoint], serviceBase);
355
+ if (destinationFilePath !== void 0) url.searchParams.set("file", destinationFilePath);
356
+ if (workspaceId !== void 0) url.searchParams.set("workspaceId", workspaceId);
357
+ return url;
358
+ }
359
+ __name(buildSignedUrl, "buildSignedUrl");
360
+
264
361
  // src/lib/api/generate-signed-url-for-run-inputs-executables.ts
265
- async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
362
+ async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
266
363
  try {
267
- const response = await localFetch(new URL(`${serviceBase}/api/v0/run-inputs-executables-signed-urls?file=${encodeURIComponent(destinationFilePath)}`), {
364
+ const response = await localFetch(buildSignedUrl({
365
+ destinationFilePath,
366
+ endpoint: "runInputsExecutables",
367
+ serviceBase,
368
+ workspaceId
369
+ }), {
268
370
  headers: {
269
371
  Authorization: `Bearer ${apiKey}`,
270
372
  "Content-Type": "application/json",
@@ -342,9 +444,14 @@ async function generateSignedUrlForRunInputsExecutablesStorage(deps, apiConfig,
342
444
  __name(generateSignedUrlForRunInputsExecutablesStorage, "generateSignedUrlForRunInputsExecutablesStorage");
343
445
 
344
446
  // src/lib/api/generate-signed-url-for-team-storage.ts
345
- async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
447
+ async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
346
448
  try {
347
- const response = await localFetch(new URL(`${serviceBase}/api/v0/team-storage-signed-url?file=${destinationFilePath}`), {
449
+ const response = await localFetch(buildSignedUrl({
450
+ destinationFilePath,
451
+ endpoint: "teamStorage",
452
+ serviceBase,
453
+ workspaceId
454
+ }), {
348
455
  headers: {
349
456
  Authorization: `Bearer ${apiKey}`,
350
457
  "Content-Type": "application/json",