@qawolf/ci-sdk 3.0.0 → 3.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/README.md CHANGED
@@ -11,9 +11,12 @@ to determine the status of your CI/CD step/job/action.
11
11
 
12
12
  ## Table of Contents
13
13
 
14
- - [Notify Deployment](#notify-deployment)
14
+ - [API Keys and Workspaces](#api-keys)
15
+ - [Report a Deployment](#report-deployment)
16
+ - [Wait for a Verdict](#wait-for-verdict)
17
+ - [Notify Deployment (deprecated)](#notify-deployment)
15
18
  - [Notify Preview Deployment (Pull Request / Merge Request Testing)](#notify-preview)
16
- - [Poll for CI Greenlight Status](#ci-greenlight)
19
+ - [Poll for CI Greenlight Status (deprecated)](#ci-greenlight)
17
20
  - [Advanced: Poll with Custom Control (Iterator)](#ci-greenlight-iterator)
18
21
  - [Notify a Terminated Ephemeral Environment](#notify-terminated-ephemeral-environment)
19
22
  - [Upload Run Input Artifacts](#upload-artifacts)
@@ -21,10 +24,302 @@ to determine the status of your CI/CD step/job/action.
21
24
  - [Versioning](#versioning)
22
25
  - [Changelog](#changelog)
23
26
 
27
+ <a id="api-keys"></a>
28
+
29
+ ## API Keys and Workspaces
30
+
31
+ Every call you make is authenticated by the `apiKey` you pass to `makeQaWolfSdk`, and every call
32
+ acts on exactly one workspace. How that workspace is determined depends on which kind of API key
33
+ you hold:
34
+
35
+ - A **workspace API key** (also called a team API key) already belongs to a single workspace. It
36
+ implies the workspace, so you never need to name one.
37
+ - An **organization API key** or a **user API key** can reach several workspaces, so it does not
38
+ imply one. You name the workspace by passing `workspaceId` on the call.
39
+
40
+ `workspaceId` is the ID of the workspace (team) you want the call to act on. If you are not sure
41
+ which value to use, ask your QA Wolf representative; `attemptNotifyDeploy` also reports the
42
+ workspace it selected, with its ID, in the warning described below.
43
+
44
+ ### Passing `workspaceId`
45
+
46
+ The field is accepted on every function that acts on a workspace:
47
+
48
+ ```ts
49
+ const { attemptNotifyDeploy, generateSignedUrlForRunInputsExecutablesStorage } =
50
+ makeQaWolfSdk({
51
+ apiKey: "qawolf_xxxxx",
52
+ });
53
+
54
+ // Organization and user API keys must name the workspace on each call.
55
+ await generateSignedUrlForRunInputsExecutablesStorage({
56
+ destinationFilePath: "unityexpo.ipa",
57
+ workspaceId: "your-workspace-id",
58
+ });
59
+
60
+ await attemptNotifyDeploy({
61
+ ...deployConfig,
62
+ workspaceId: "your-workspace-id",
63
+ });
64
+ ```
65
+
66
+ `notifyTerminatedEphemeralEnvironment` accepts `workspaceId` the same way.
67
+
68
+ ### What happens when it is missing
69
+
70
+ Omitting `workspaceId` with an organization or user API key is handled differently depending on the
71
+ call, so it is worth knowing which behavior to expect:
72
+
73
+ | Function | Behavior when `workspaceId` is omitted |
74
+ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `generateSignedUrlForTempTeamStorage` | Fails. The backend answers HTTP `400` with the message `workspaceId is a required query parameter for organization and user API keys`. |
76
+ | `generateSignedUrlForRunInputsExecutablesStorage` | Same as above — the backend answers HTTP `400`. |
77
+ | `attemptNotifyDeploy` | Succeeds against your organization's first-created workspace and returns a `warning` naming it. |
78
+ | `notifyTerminatedEphemeralEnvironment` | Succeeds against your organization's first-created workspace and returns a `warning` naming it. |
79
+
80
+ > ⚠️ The signed-URL functions **require** `workspaceId` for organization and user API keys — they do
81
+ > not fall back to a default workspace. A `400` from either function, especially when the API key and
82
+ > artifact name are otherwise correct, most often means `workspaceId` was not sent.
83
+
84
+ When this happens, the SDK logs the real status before it reports the outcome, so the log line is
85
+ the reliable signal:
86
+
87
+ ```
88
+ ❌ [callGenerateSignedUrlForRunInputsExecutablesStorage] 400 Bad Request undefined
89
+ 🚫 Unrecoverable error (status 0) when generating signed upload url: Network error, aborting request to generate signed URL. aborting. undefined
90
+ ```
91
+
92
+ The `400 Bad Request` on the first line is the actual response. The `status 0` and "Network error"
93
+ on the second line are misreported and do not indicate a transport problem — read the first line
94
+ and add `workspaceId`.
95
+
96
+ For the same reason, do not branch on `httpStatus` or `abortReason` to detect this case: a failed
97
+ signed-URL request currently reports `httpStatus: 0` and `abortReason: "client-network-error"`
98
+ whatever the real status was. Treat any `success: false` from these two functions as a request that
99
+ did not reach a signed URL, and check the logged status to find out why.
100
+
101
+ Passing `workspaceId` with a workspace API key is unnecessary but harmless, as long as the ID names
102
+ the key's own workspace. Naming a workspace the key cannot reach fails with HTTP `403`.
103
+
104
+ `workspaceId` support was added in `@qawolf/ci-sdk` 2.2.0. On earlier versions, use a
105
+ workspace-scoped API key.
106
+
107
+ <a id="report-deployment"></a>
108
+
109
+ ## Report a Deployment
110
+
111
+ Report a deployment through the public `deployment.reportStatus` API. A deployment's first `success` report is what evaluates your triggers, so this is the call that starts a run.
112
+
113
+ > ⚠️ Triggers only see deployments reported this way. `attemptNotifyDeploy` reports through the legacy `deploy_success` webhook and is deprecated.
114
+
115
+ ### Example
116
+
117
+ ```ts
118
+ import { detectProviderDeploymentId, makeQaWolfSdk } from "@qawolf/ci-sdk";
119
+
120
+ const { deployment } = makeQaWolfSdk({ apiKey: "qawolf_xxxxx" });
121
+
122
+ (async () => {
123
+ // `{ outcome: "detected", ci: "github", id: "18273645-2-deploy" }` on GitHub
124
+ // Actions, Jenkins, GitLab CI, CircleCI, Bitbucket Pipelines and Buildkite;
125
+ // `{ outcome: "unsupported" }` elsewhere, in which case compose the id from
126
+ // your own CI's variables.
127
+ const detected = detectProviderDeploymentId();
128
+ if (detected.outcome !== "detected") process.exit(1);
129
+
130
+ const result = await deployment.reportStatus({
131
+ environment: { name: "staging" },
132
+ metadata: {
133
+ commitSha: process.env.GITHUB_SHA,
134
+ ref: process.env.GITHUB_REF_NAME,
135
+ repository: process.env.GITHUB_REPOSITORY,
136
+ },
137
+ providerDeploymentId: detected.id,
138
+ status: "success",
139
+ workspaceId: "your-workspace-id",
140
+ });
141
+ if (result.outcome !== "reported") {
142
+ // `result.outcome` says why, and the refusal arms carry the server's
143
+ // explanation in `result.message` when it sent one.
144
+ process.exit(1);
145
+ }
146
+ console.log(
147
+ `Reported deployment ${result.deployment.id}: ${result.deployment.url}`,
148
+ );
149
+ })();
150
+ ```
151
+
152
+ The parameters are exactly the input of `deployment.reportStatus`; `ReportDeploymentParams` is derived from the published `@qawolf/api-contracts` package, so the SDK cannot drift from the API. Every field the legacy webhook accepted has an equivalent: `environmentVariables`, `deployTarget`, ephemeral environments through `environment.ephemeral` and `environment.baseEphemeralEnvironment`, and `metadata.repository` / `pullRequestNumber` / `commitSha` / `ref`.
153
+
154
+ ### `status`
155
+
156
+ You choose the status: `pending`, `success`, `failure` or `inactive`. Only a deployment's **first `success`** report evaluates triggers, and it is the only report that does. A `pending` report records the deployment and starts nothing, so report `pending` when the deploy begins if you want it visible, and report `success` when the deploy is actually live — that second call is what starts the run. A trigger added or unpaused after that first `success` does not make the deployment evaluate again; report the deployment under a new `providerDeploymentId` to evaluate it against the current triggers.
157
+
158
+ ### `providerDeploymentId`
159
+
160
+ This is the deployment's identity. Reports that share one update the same deployment, and the deployment stays bound to the environment its first report resolved.
161
+
162
+ It is required, and the SDK never guesses it. Beware that `GITHUB_RUN_ID` alone is **stable across re-runs** and **shared by every job in the workflow**: a re-run of a failed job, or a second job deploying alongside the first, would report into the deployment that already evaluated, create no run, and appear to hang. `detectProviderDeploymentId()` composes an id that is distinct for every job definition and every re-run:
163
+
164
+ | CI system | Detected on | Id |
165
+ | ------------------- | ------------------------------------------------- | -------------------------------------------------------------------------- |
166
+ | GitHub Actions | `GITHUB_ACTIONS` | `GITHUB_RUN_ID`-`GITHUB_RUN_ATTEMPT`-`GITHUB_JOB` |
167
+ | GitLab CI | `GITLAB_CI` | `CI_PIPELINE_ID`-`CI_JOB_ID` |
168
+ | CircleCI | `CIRCLECI` | `CIRCLE_WORKFLOW_ID`-`CIRCLE_BUILD_NUM` |
169
+ | Buildkite | `BUILDKITE` | `BUILDKITE_BUILD_ID`-`BUILDKITE_JOB_ID`-`BUILDKITE_RETRY_COUNT` |
170
+ | Jenkins | `JENKINS_URL` or `JENKINS_HOME` | `BUILD_TAG` |
171
+ | Bitbucket Pipelines | `BITBUCKET_WORKSPACE` or `BITBUCKET_BUILD_NUMBER` | `BITBUCKET_BUILD_NUMBER`-`BITBUCKET_STEP_UUID`-`BITBUCKET_STEP_RUN_NUMBER` |
172
+
173
+ Jenkins needs no job or attempt part: `BUILD_TAG` is `jenkins-<job name>-<build number>`, and Jenkins allocates a fresh build number for every re-run you can start from outside the build. Bitbucket needs all three, because rerunning only the failed steps keeps the build number while parallel steps share both it and the step run number.
174
+
175
+ The helper reports `{ outcome: "missing-variables", missingVariables }` when the CI system is recognized but a variable is absent, and `{ outcome: "unsupported" }` on any other CI, rather than guess an id that would not change on a re-run.
176
+
177
+ #### Concurrent deployments from one job definition
178
+
179
+ The guarantee stops at the job definition. Where one definition deploys more than once at a time, nothing in the environment tells the deployments apart, and the helper alone would hand them all the same id:
180
+
181
+ | CI system | What collides |
182
+ | -------------- | --------------------------------------------------------------------------------------------------- |
183
+ | GitHub Actions | Every leg of a `strategy.matrix` shares `GITHUB_JOB`, `GITHUB_RUN_ID` and `GITHUB_RUN_ATTEMPT`. |
184
+ | Jenkins | A declarative `matrix` or `parallel` block runs its branches inside one build, sharing `BUILD_TAG`. |
185
+ | CircleCI | A job run with `parallelism` shares `CIRCLE_BUILD_NUM` across its containers. |
186
+
187
+ GitLab CI, Buildkite and Bitbucket Pipelines are not affected: `CI_JOB_ID`, `BUILDKITE_JOB_ID` and `BITBUCKET_STEP_UUID` are already distinct for each parallel leg.
188
+
189
+ Pass a `discriminator` for those, and for any job that loops over several environments. It is appended to the composed id:
190
+
191
+ ```ts
192
+ const detected = detectProviderDeploymentId({
193
+ // In GitHub Actions: `discriminator: ${{ toJSON(matrix) }}` through an env
194
+ // variable, or just the one matrix value that names the deployment.
195
+ discriminator: process.env.DEPLOY_ENVIRONMENT,
196
+ });
197
+ ```
198
+
199
+ `detectProviderDeploymentId` also takes `env`, which defaults to `process.env`. Composing the `providerDeploymentId` yourself is always an option; the helper is a convenience, not a requirement.
200
+
201
+ ### `workspaceId`
202
+
203
+ `deployment.reportStatus` requires `workspaceId` for every key kind; a workspace API key must name its own workspace. See [API Keys and Workspaces](#api-keys).
204
+
205
+ ### Result
206
+
207
+ `deployment.reportStatus` returns a discriminated union and never throws for an outcome you are expected to branch on. Branch on `outcome` alone:
208
+
209
+ | `outcome` | Meaning |
210
+ | ----------------------- | ------------------------------------------------------------------------------------------ |
211
+ | `reported` | Carries `deployment`, the whole resource: `id`, `status` and `url`. |
212
+ | `invalid-request` | The report did not satisfy the contract. Carries `message`. |
213
+ | `unauthorized` | The API key is unknown, or may not act on this workspace. Carries `message`. |
214
+ | `not-found` | The workspace or environment named in the report does not exist. Carries `message`. |
215
+ | `conflict` | The `providerDeploymentId` is already bound to a different environment. Carries `message`. |
216
+ | `server-error` | QA Wolf failed to record the report; retrying is reasonable. Carries `httpStatus`. |
217
+ | `unexpected-status` | A status this SDK version does not map. Carries `httpStatus` and `message`. |
218
+ | `network-error` | The request never reached QA Wolf, or the connection dropped. |
219
+ | `invalid-response-body` | QA Wolf answered with a body the contract rejects. |
220
+
221
+ So `if (result.outcome !== "reported") process.exit(1)` is the whole check a gate needs.
222
+
223
+ The response carries the deployment only, not the resulting runs. Waiting for a verdict is not part of this call.
224
+
225
+ ### Migrating from `attemptNotifyDeploy`
226
+
227
+ ```diff
228
+ -const { attemptNotifyDeploy } = makeQaWolfSdk({ apiKey });
229
+ +const { deployment } = makeQaWolfSdk({ apiKey });
230
+
231
+ -const deploy = await attemptNotifyDeploy({
232
+ - branch: process.env.GITHUB_REF_NAME,
233
+ - deploymentType: "staging",
234
+ - hostingService: "GitHub",
235
+ - sha: process.env.GITHUB_SHA,
236
+ -});
237
+ -if (deploy.outcome === "skipped") process.exit(0);
238
+ -if (deploy.outcome !== "success") process.exit(1);
239
+ +const detected = detectProviderDeploymentId();
240
+ +if (detected.outcome !== "detected") process.exit(1);
241
+ +const deploy = await deployment.reportStatus({
242
+ + environment: { name: "staging" },
243
+ + metadata: { commitSha: process.env.GITHUB_SHA, ref: process.env.GITHUB_REF_NAME },
244
+ + providerDeploymentId: detected.id,
245
+ + status: "success",
246
+ + workspaceId: "your-workspace-id",
247
+ +});
248
+ +if (deploy.outcome !== "reported") process.exit(1);
249
+ ```
250
+
251
+ `detectProviderDeploymentId()` composes the run, attempt and job, so two jobs in one workflow do not report into the same deployment. Compose the id yourself only if you would rather not call it, and include the job.
252
+
253
+ Four things move: `deploymentType` becomes `environment.name`; `branch` / `sha` become `metadata`; `providerDeploymentId` is new and required (`deduplicationKey` is subsumed by it); `hostingService` disappears, since the code host comes from your integration. There is no `skipped` outcome: whether a trigger matched is not known at report time. `pollCiGreenlightStatus` takes a run id that only `attemptNotifyDeploy` returns; a pipeline that waits for a verdict moves to [`deployment.waitForVerdict`](#wait-for-verdict), which takes the deployment id this call returns.
254
+
255
+ <a id="wait-for-verdict"></a>
256
+
257
+ ## Wait for a Verdict
258
+
259
+ `deployment.waitForVerdict` blocks until QA Wolf can say whether a deployment's tests passed. Give it the deployment id [the report](#report-deployment) returned; working out which runs the deployment produced happens inside.
260
+
261
+ > ⚠️ Set `timeout` below your CI job's own ceiling. When the job's ceiling fires first, the job is killed with no run link and no explanation. When ours fires first, you get `timed-out` with the stage, the elapsed time, and every run URL we know about.
262
+
263
+ ### Example
264
+
265
+ ```ts
266
+ const { deployment } = makeQaWolfSdk({ apiKey: "qawolf_xxxxx" });
267
+
268
+ const report = await deployment.reportStatus({ /* ... */ status: "success" });
269
+ if (report.outcome !== "reported") process.exit(1);
270
+
271
+ const verdict = await deployment.waitForVerdict({
272
+ deploymentId: report.deployment.id,
273
+ timeout: 45 * 60 * 1000,
274
+ });
275
+
276
+ process.exit(["passed", "not-tested"].includes(verdict.outcome) ? 0 : 1);
277
+ ```
278
+
279
+ ### Result
280
+
281
+ Like every function here, it never throws. Branch on `outcome` alone:
282
+
283
+ | `outcome` | Meaning |
284
+ | ----------------------- | -------------------------------------------------------------------------------------------------------- |
285
+ | `passed` | Every run QA Wolf started passed. Carries `runs`. |
286
+ | `failed` | A run found a bug that is still open and serious enough to block. Carries `runs` and `blockingBugCount`. |
287
+ | `not-tested` | Nothing tested this deployment. Carries `reason` and a `triggers` note per trigger saying why. |
288
+ | `run-canceled` | A run was canceled, so there is no verdict. This is not a pass. Carries `runs`. |
289
+ | `timed-out` | The budget ran out. Carries `elapsedMs`, `lastStage` and any `runs` it had already looked up. |
290
+ | `canceled-by-caller` | The `signal` you passed was aborted. |
291
+ | `unauthorized` | The API key is unknown, or may not read this deployment. Carries `message`. |
292
+ | `not-found` | No such deployment. Carries `message`. |
293
+ | `server-error` | QA Wolf kept failing after every retry. Carries `httpStatus`. |
294
+ | `unexpected-status` | A status this SDK version does not map. Carries `httpStatus` and `message`. |
295
+ | `network-error` | The connection never came back within the retries. |
296
+ | `invalid-response-body` | QA Wolf answered with a body the contract rejects. |
297
+
298
+ `not-tested` is a separate answer from `passed` on purpose. A deployment that no trigger matched was not tested, which is a different fact from tests passing, and you decide what it is worth: exit 0 and keep the log line, or fail the build when you expect a run every time. `run-canceled` likewise means we cannot tell you.
299
+
300
+ The shape is `deploymentVerdictSchema`, published in `@qawolf/api-contracts`, so the same answer parses the same way wherever you read it.
301
+
302
+ ### Options
303
+
304
+ Every field below is optional and carries the default shown.
305
+
306
+ | Option | Default | Meaning |
307
+ | ---------------------- | ------- | ------------------------------------------------------------------------------------- |
308
+ | `timeout` | `2h` | Whole-operation budget. Set it below your CI job's ceiling. |
309
+ | `runAppearanceTimeout` | `5min` | Give up if no trigger has produced a run by then. Runs that did appear are waited on. |
310
+ | `triggerPollInterval` | `3s` | Between polls while a trigger is still producing a run. |
311
+ | `runPollInterval` | `30s` | Between polls while a run executes. |
312
+ | `maxRetries` | `10` | Network and server-error retries before giving up. |
313
+ | `retryInterval` | `10s` | Between those retries. |
314
+ | `onProgress` | — | Called on every stage change, for your build log. |
315
+ | `signal` | — | An `AbortSignal`. Aborting it yields `canceled-by-caller` instead of a rejection. |
316
+
24
317
  <a id="notify-deployment"></a>
25
318
 
26
319
  ## Notify Deployment
27
320
 
321
+ > ⚠️ **Deprecated.** This reports through the legacy `deploy_success` webhook, which triggers do not see. Use [Report a Deployment](#report-deployment) instead. It keeps working and there is no removal date.
322
+
28
323
  Notify us of a successful deployment to trigger a run.
29
324
 
30
325
  > ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/dd72e46ceb7f451dae4e9ef06f64a2cc).
@@ -135,22 +430,34 @@ const { attemptNotifyDeploy } = makeQaWolfSdk({
135
430
 
136
431
  > ⚠️ **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.
137
432
 
138
- Once enabled, to use PR/MR testing functionality:
433
+ Once enabled, report the preview deployment with [`deployment.reportStatus`](#report-deployment) and name the pull or merge request in `metadata`. The code host comes from your QA Wolf integration, so there is no `hostingService` to pass:
139
434
 
140
- 1. For `GitHub` repositories:
141
- - Preferably, use our [GitHub Action](https://github.com/marketplace/actions/notify-qa-wolf-on-deploy)
142
- - Pass `hostingService: "GitHub"`, `repository` information, and `pullRequestNumber` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
435
+ ```ts
436
+ await deployment.reportStatus({
437
+ deployTarget: previewUrl,
438
+ environment: { ephemeral: true, name: `pr-${pullRequestNumber}` },
439
+ metadata: {
440
+ commitSha: process.env.GITHUB_SHA,
441
+ pullRequestNumber,
442
+ ref: process.env.GITHUB_HEAD_REF,
443
+ repository: process.env.GITHUB_REPOSITORY,
444
+ },
445
+ providerDeploymentId: detected.id,
446
+ status: "success",
447
+ workspaceId: "your-workspace-id",
448
+ });
449
+ ```
143
450
 
144
- 2. For `GitLab` repositories:
145
- - Pass `hostingService: "GitLab"`, `repository` information, and `mergeRequestNumber` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
451
+ `metadata.pullRequestNumber` names a GitLab merge request too. For a preview with no code hosting integration, pass `deployTarget` and an ephemeral `environment` and leave the repository fields out. `environment.baseEphemeralEnvironment` names the environment an ephemeral one inherits its configuration from.
146
452
 
147
- 3. For `Ephemeral` deployments (no code hosting integration):
148
- - Pass `ephemeralEnvironment: true` and `deploymentUrl` while notifying a deployment as described in the [Notify Deployment](#notify-deployment) section
453
+ The [GitHub Action](https://github.com/marketplace/actions/notify-qa-wolf-on-deploy) and `attemptNotifyDeploy`'s `hostingService` / `mergeRequestNumber` / `ephemeralEnvironment` / `deploymentUrl` fields still work, but they report through the legacy `deploy_success` webhook that triggers do not see.
149
454
 
150
455
  <a id="ci-greenlight"></a>
151
456
 
152
457
  ## Poll for CI Greenlight Status
153
458
 
459
+ > ⚠️ **Deprecated.** This polls a run id that only `attemptNotifyDeploy` returns, so it keeps your pipeline on the legacy `deploy_success` webhook. It keeps working and there is no removal date.
460
+
154
461
  > ℹ️ [See the API documentation page for this endpoint](https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e).
155
462
 
156
463
  ```ts
@@ -407,6 +714,8 @@ const { generateSignedUrlForTempTeamStorage, attemptNotifyDeploy } =
407
714
  async function uploadRunArtifact(filePath: string): Promise<string> {
408
715
  const fileName = path.basename(filePath);
409
716
 
717
+ // Organization and user API keys must also pass `workspaceId` here, or the
718
+ // request fails with a 400. See "API Keys and Workspaces" above.
410
719
  const signedUrlResponse = await generateSignedUrlForTempTeamStorage({
411
720
  destinationFilePath: fileName,
412
721
  });
@@ -444,6 +753,10 @@ async function uploadRunArtifact(filePath: string): Promise<string> {
444
753
  `playgroundFileLocation` field with the same value as `interactiveRunFileLocation`. It is deprecated and
445
754
  will be removed in a future major version — use `interactiveRunFileLocation` instead.
446
755
 
756
+ Both functions require `workspaceId` when you authenticate with an organization or user API key.
757
+ See [API Keys and Workspaces](#api-keys) for details and for what a missing `workspaceId` looks
758
+ like.
759
+
447
760
  <a name="requirements"></a>
448
761
 
449
762
  ## Requirements
@@ -485,6 +798,17 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
485
798
 
486
799
  # Changelog
487
800
 
801
+ ## v3.2.0
802
+
803
+ - New `deployment.waitForVerdict` function, which blocks until QA Wolf can say whether a deployment's tests passed. It takes the deployment id `deployment.reportStatus` returned and resolves that to runs internally, so no run id crosses the boundary. It answers `passed`, `failed`, `not-tested`, `run-canceled`, `timed-out`, `canceled-by-caller` or one of the transport outcomes, and never throws. `not-tested` is deliberately distinct from `passed`, and `run-canceled` means no verdict rather than a pass. The whole-operation budget stays at 2 hours, matching `pollCiGreenlightStatus`. See [Wait for a Verdict](#wait-for-verdict).
804
+
805
+ ## v3.1.0
806
+
807
+ - New `deployment.reportStatus` function, which reports a deployment through the public `deployment.reportStatus` API — the one your triggers watch. A deployment's first `success` report is what starts a run. Its parameters are the API's own input, taken from the `@qawolf/api-contracts` package, so the SDK cannot drift from the API, and it returns `{ outcome: "reported", deployment }` carrying the deployment's `id`, `status` and `url`. A refusal comes back as one named outcome you can branch on — `invalid-request`, `unauthorized`, `not-found`, `conflict`, `server-error`, `unexpected-status`, `network-error` or `invalid-response-body` — so `if (result.outcome !== "reported")` is the whole check a CI gate needs. See [Report a Deployment](#report-deployment).
808
+ - New `detectProviderDeploymentId` function, which works out the `providerDeploymentId` for you from the variables your CI already sets on GitHub Actions, GitLab CI, CircleCI, Buildkite, Jenkins and Bitbucket Pipelines. The id it builds is different for every job definition and every re-run, so a retried job and two jobs deploying side by side each get their own deployment instead of reporting into one that has already started its run. Where one job definition deploys more than once at a time — a GitHub Actions or Jenkins matrix, a CircleCI job with `parallelism`, a loop over environments — no variable tells the legs apart, so pass a `discriminator` and it is appended to the id. On any other CI it returns `{ outcome: "unsupported" }` rather than guess. Passing `providerDeploymentId` yourself is still required; this helper is optional.
809
+ - Pull request and merge request testing is now documented on `deployment.reportStatus`: pass `metadata.repository` and `metadata.pullRequestNumber` instead of `hostingService`. See [Notify Preview Deployment](#notify-preview).
810
+ - `attemptNotifyDeploy` and `pollCiGreenlightStatus` are deprecated in favour of `deployment.reportStatus`. They report through the legacy `deploy_success` webhook, which triggers do not see. Both keep working unchanged, print no warning, and have no removal date.
811
+
488
812
  ## v3.0.0
489
813
 
490
814
  - **Breaking**: this package is now published as ESM only. The CommonJS build (`dist/index.cjs`) and the `require` entry in `exports` are gone, so `require("@qawolf/ci-sdk")` no longer resolves. Import the package with `import` from an ESM module, or load it from CommonJS with a dynamic `await import("@qawolf/ci-sdk")`.
@@ -519,6 +843,10 @@ This package follows the [SemVer](https://semver.org/) versioning scheme. Additi
519
843
  - `attemptNotifyDeploy`: a failed suite result now carries `failureCode` and `failureMessage` alongside the existing `failureReason`. `failureCode` is a stable code to branch on, such as `billing-prevented`, `environment-terminated`, `environment-not-ready`, `test-configuration-error`, or `internal-error`; treat it as an open set, since new codes are added over time. Note that `environment-terminated` is permanent while `environment-not-ready` clears on its own, so retrying is worthwhile only for the second. `failureMessage` is human-readable copy for logs, and the SDK's own failure log line now prints it instead of the internal reason.
520
844
  - `attemptNotifyDeploy`: `failureReason` is deprecated. It still ships unchanged and is safe to keep reading, but it carries an internal diagnostic whose values change without notice. Branch on `failureCode` and display `failureMessage` instead. Both are `undefined` when the SDK talks to a backend that predates them.
521
845
 
846
+ ## v2.2.0
847
+
848
+ - `generateSignedUrlForTempTeamStorage` and `generateSignedUrlForRunInputsExecutablesStorage`: both now accept an optional `workspaceId`, which names the workspace the signed URL is generated for. It is required when authenticating with an organization or user API key, since neither is tied to a single workspace, and without it the request fails with HTTP `400`. Omit it when authenticating with a workspace API key, which already implies one. This entry was missing from the changelog when 2.2.0 was released and is documented here retroactively; see [API Keys and Workspaces](#api-keys).
849
+
522
850
  ## v2.1.0
523
851
 
524
852
  - `attemptNotifyDeploy`: the `deploy_success` response is now validated at runtime instead of being trusted. A response with a non-JSON body, or a body that does not match the expected shape, returns `outcome: "aborted"` with the new `abortReason: "invalid-response-body"`. This is distinct from `"network-error"`, which now covers only transport and body-stream failures rather than also masking unparseable responses. A result the SDK does not recognize (for example a new result kind sent by a newer backend) returns `outcome: "failed"` with the new `failReason: "unknown-result"` instead of being reported as a success. Recognized results are unchanged, and older backends that omit newer fields remain fully supported.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { CiGreenlightStatus, CiGreenlightStatusBase, } from "./lib/api/index.js";
2
2
  export * from "./lib/sdk/dependencies.js";
3
3
  export * from "./lib/sdk/domain/attemptDeploy/types.js";
4
+ export * from "./lib/sdk/domain/detectProviderDeploymentId/index.js";
4
5
  export * from "./lib/sdk/index.js";