@qawolf/ci-sdk
TypeScript icon, indicating that this package has built-in type declarations

0.21.0 • Public • Published

QAWolf CI SDK

⚠️ This is an experimental package. Please report any issues you encounter to your support channel. It should still provide a better experience than using our internal GraphQL API directly.

This package provides a TypeScript (CSM and ESM compatible) SDK to interact with the QA Wolf Customer-facing API.

It exposes two functions associated with the two central endpoints, showcased in the examples below. There is also an experimental VCS Branch Testing (aka PR Testing) feature, encapsulated in its own object, that is described at the bottom of this document.

Note that these functions do not throw. They yield a result object that contains the outcome of the operation. This outcome should be scrutinized to determine the status of your CI/CD step/job/action.

Example: Trigger Run on Deployment

ℹ️ See the API documentation page for this endpoint.

import { type DeployConfig, makeQaWolfSdk } from "@qawolf/ci-sdk";

// Edit this to your needs.
const deployConfig: DeployConfig = {
  branch: undefined,
  commitUrl: undefined,
  deduplicationKey: undefined,
  deploymentType: undefined,
  deploymentUrl: undefined,
  hostingService: undefined,
  sha: undefined,
  variables: undefined,
};

const { attemptNotifyDeploy } = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
});

(async () => {
  const result = await attemptNotifyDeploy(deployConfig);
  if (result.outcome !== "success") {
    // Fail the job.
    process.exit(1);
  }
  const runId = result.runId;
  // Store the runId as an output of the job to be used in a CI-greenlight job.
  // This will depend on the CI platform you are using.
})();

Example: Poll for CI Greenlight Status

ℹ️ See the API documentation page for this endpoint.

import { makeQaWolfSdk } from "@qawolf/ci-sdk";

const { pollCiGreenlightStatus } = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
});

(async () => {
  // Retrieve runId from the previous job.
  const { outcome } = await pollCiGreenlightStatus({
    runId,
    // Optional: Callback to be called when the run stage changes.
    // See https://qawolf.notion.site/1b170576efea411fa785842a71e7c99e for
    // documentation on these run stages.
    onRunStageChanged: (current, previous) => {
      console.log(current, previous);
    },
    // Optional: Defaults to false. When set to true, the polling operation
    // will abort when the run is superseded.
    abortOnSuperseded: false,
  });
  if (outcome !== "success") {
    // Fail the job.
    // This will depend on the CI platform you are using.
    // You can also distinguish between "failed" and "aborted" outcomes.
    // Only "failed" outcome indicates bugs were found.
    process.exit(1);
  }
  // Continue CI.
})();

Example: Upload Run Input Artifacts

import { type DeployConfig, makeQaWolfSdk } from "@qawolf/ci-sdk";
import fs from "fs/promises";
import path from "path";

const { generateSignedUrlForTempTeamStorage, attemptNotifyDeploy } =
  makeQaWolfSdk({
    apiKey: "qawolf_xxxxx",
  });

(async () => {
  const playgroundFileLocation = await uploadRunArtifact("");

  if (playgroundFileLocation) {
    const deployConfig: DeployConfig = {
      branch: undefined,
      commitUrl: undefined,
      deduplicationKey: undefined,
      deploymentType: undefined,
      deploymentUrl: undefined,
      hostingService: undefined,
      sha: undefined,
      variables: {
        RUN_INPUT_PATH: playgroundFileLocation,
      },
    };

    const result = await attemptNotifyDeploy(deployConfig);
    if (result.outcome !== "success") {
      // Fail the job.
      process.exit(1);
    }
    const runId = result.runId;
  }
})();

async function uploadRunArtifact(filePath: string): Promise<string> {
  const fileName = path.basename(filePath);

  const signedUrlResponse = await generateSignedUrlForTempTeamStorage({
    destinationFilePath: fileName,
  });

  if (
    signedUrlResponse?.success &&
    signedUrlResponse.playgroundFileLocation &&
    signedUrlResponse.uploadUrl
  ) {
    const fileBuffer = await fs.readFile(filePath);
    const url = signedUrlResponse.uploadUrl;

    try {
      const response = await fetch(url, {
        method: "PUT",
        body: fileBuffer,
        headers: {
          "Content-Type": "application/octet-stream",
        },
      });

      if (!response.ok) {
        return "";
      }
    } catch (error) {
      return "";
    }
    return signedUrlResponse.playgroundFileLocation;
  }
  return "";
}

VCS Branch Testing (Experimental)

⚠️ This section is not covered by SemVer and will change.

ℹ️ This feature must be activated by QA Wolf. Please reach out to a QA Wolf representative to enable it, and help you with the setup.

Highlights and Definitions

VCS Branch Testing allows you to set up QA Wolf ephemeral environments associated with preview deployments (builds) of your product, which code is hosted in VCS branches (e.g. Git branches). It can be understood as "PR Testing", however the SDK does not need the concept of PR or MR to operate.

ℹ️ Read our introduction to VCS Branch Testing to get familiar with core concepts. We also provide a more detailed SDK guide over here.

Usage

This SDK exposes an experimental VCS Branch Testing object:

import { makeQaWolfSdk } from "@qawolf/ci-sdk";

const vcsBranchTestingSdk = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
}).experimental_vcsBranchTesting;

The above object contains the following methods:

  • notifyVCSBranchBuildDeployed;
  • notifyVCSBranchMergeCanceled;
  • notifyVCSBranchMergeCompleted.

Notify Build Deployed notifyVCSBranchBuildDeployed

import {
  makeQaWolfSdk,
  arbitraryStringToEnvironmentAlias,
} from "@qawolf/ci-sdk";

const { notifyVCSBranchBuildDeployed } = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
}).experimental_vcsBranchTesting;

// Mappings let QA Wolf know how to associate base VCS branches to environments.
const baseEnvironmentsMapping = [
  {
    environmentAlias: "develop",
    vcsBranch: "main",
  },
];

(async () => {
  const baseVcsBranch = "main";
  const headVcsBranch = "feature/foo";
  const headPreviewUrl = "https://feature-foo.example.com";
  const headVcsCommitId = "abcdef";
  // Optional. It will show up in the UI when provided. If an integration with
  // a code hosting service is already set up, you don't need to provide this field.
  const optionalHeadCommitUrl = "https://.../commits/abcdef";
  // Optional. It is required for a code hosting service integration to function
  // properly. When that is the case, comments will be posted in the PR/MR describing
  // the outcome of tests in the head environment.
  const optionalPullOrMergeRequestNumber = 123;
  // The alias must be stable for the lifetime of the VCS Branch / Environment.
  // It also should be unique across your organization, accounting for different
  // repositories.
  const headEnvironmentAlias = arbitraryStringToEnvironmentAlias(
    `org/repo/${headVcsBranch}`,
  );
  // The name of the environment, as it will show-up in the QA Wolf UI.
  const headEnvironmentName = `org/repo/${headVcsBranch}`;
  // Environment variables are not optional. There must be at least one locator
  // for the deployed application. Since this deployment is entirely handled by
  // customers, we cannot provide more generic guidance.
  const headEnvironmentVariables = {
    URL: headPreviewUrl,
  };
  // Obviously, configuration object should be adapted to your needs, and
  // most are dynamic and shall be derived from context. This is just an example.
  const { outcome } = await notifyVCSBranchBuildDeployed({
    baseEnvironmentsMapping,
    baseVcsBranch: "main",
    headEnvironmentAlias,
    // If you are not sure how to build the environment name, just use the
    // alias. This name will show up in the UI though.
    headEnvironmentName,
    headVcsBranch,
    headVcsCommitId,
    headCommitUrl: optionalHeadCommitUrl,
    headEnvironmentVariables,
    pullOrMergeRequestNumber: optionalPullOrMergeRequestNumber,
  });
  if (outcome !== "success") {
    // Fail the job.
    process.exit(1);
  }
  // Continue CI.
})();

Notify Merge Canceled notifyVCSBranchMergeCanceled

import { makeQaWolfSdk } from "@qawolf/ci-sdk";

const { notifyVCSBranchMergeCanceled } = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
}).experimental_vcsBranchTesting;

(async () => {
  const headVcsBranch = "feature/foo";
  // The alias must be stable for the lifetime of the VCS Branch / Environment.
  // It also should be unique across your organization, accounting for different
  // repositories.
  const headEnvironmentAlias = arbitraryStringToEnvironmentAlias(
    `org/repo/${headVcsBranch}`,
  );
  const result = await notifyVCSBranchMergeCanceled({
    headEnvironmentAlias,
  });
  if (result.outcome !== "success") {
    if (result.abortReason === "head-environment-not-found") {
      console.warn(
        `Head environment not found. Skipping! The QA Wolf environment was either never created or it was manually deleted.`,
      );
      return;
    }
    // Fail the job.
    process.exit(1);
  }
  // Continue CI.
})();

Notify Merge Completed notifyVCSBranchMergeCompleted

import {
  makeQaWolfSdk,
  arbitraryStringToEnvironmentAlias,
} from "@qawolf/ci-sdk";

const { notifyVCSBranchMergeCompleted } = makeQaWolfSdk({
  apiKey: "qawolf_xxxxx",
}).experimental_vcsBranchTesting;

// Mappings let QA Wolf know how to associate base VCS branches to environments.
const baseEnvironmentsMapping = [
  {
    environmentAlias: "develop",
    vcsBranch: "main",
  },
];

(async () => {
  const headVcsBranch = "feature/foo";
  // The alias must be stable for the lifetime of the VCS Branch / Environment.
  // It also should be unique across your organization, accounting for different
  // repositories.
  const headEnvironmentAlias = arbitraryStringToEnvironmentAlias(
    `org/repo/${headVcsBranch}`,
  );
  const baseVcsBranch = "main";

  const { outcome } = await notifyVCSBranchMergeCompleted({
    baseEnvironmentsMapping,
    headVcsBranch,
    headEnvironmentAlias,
    baseVcsBranch,
  });
  if (outcome !== "success") {
    // Fail the job.
    process.exit(1);
  }
  // Continue CI.
})();

Requirements

This packages will work out of the box with NodeJS ≥ 18. If you are using an older NodeJS version, you will need to pass a fetch polyfill function to makeQaWolfSdk. We recommend undici for this purpose, see below snippet:

import { fetch } from "undici";

const sdk = makeQaWolfSdk(
  {
    apiKey: "qawolf_xxxxx",
  },
  { fetch },
);

Versioning

This package follows the SemVer versioning scheme. Additional notes:

  • Versions below 1.0.0 still follow this scheme.
  • We recommend depending on the ^ range operator for this package, as it will not introduce breaking changes and guarantee an up-to-date API usage version.
  • We will provide a changelog for each release, which will be available in the Changelog section below.
  • This package major version will be bumped when an API breaking change is introduced. This won't happen too often, and we will reach out to you and give advance notice when it does.
  • Only top-level exports are considered part of the public API and covered by SemVer.
  • Addition of new fields in the API response types are not considered breaking changes.
  • Logs and debug messages are not considered part of the public API and can change at any time.

Changelog

v0.21.0

  • Make baseEnvironmentsMapping optional in notifyVCSBranchBuildDeployed and notifyVCSBranchMergeCompleted.
  • Send baseVcsBranch when requesting VCS branch testing and VCS branch merge completion.

v0.20.0

  • pollCiGreenlightStatus: bug data fields are now available in the "underReview" run stage.
  • pollCiGreenlightStatus: new otherBlockingBugsInEnvironment response field. Some runs with a subset of workflows in environment will not reproduce blocking bugs in this environment. You can now see these other blocking bugs with this field.

v0.19.0

  • Add abortOnSuperseded option to pollCiGreenlightStatus to allow for aborting the polling operation when the run is superseded.

v0.18.0

  • Add generateSignedUrlForRunInputsExecutablesStorage function to generate a signed url used to upload files to the Run Inputs Executables bucket.

v0.17.1

  • Add detailed feedback when generateSignedUrlForTempTeamStorage fails.

v0.17.0

  • Add generateSignedUrlForTempTeamStorage function to be used in customer's CI pipeline.

v0.16.0

  • Document experimental VCS Branch/PR Testing features.
  • Deprecate experimental_testPreview and experimental_removeEnvironment. These methods will be removed in an upcoming release.

v0.15.8

  • Fix vcsBranchTesting error handling.

v0.15.7

  • Set the default timeout for fetch calls to 60 seconds.

v0.15.6

  • Add a new dependency, @qawolf/ci-utils, and move the LogDriver interface to it.
  • Add new experimental features to the SDK in experimental_vcsBranchTesting. These features are not available to customers nor documented yet, but will be advertised in a near future.

v0.15.5

  • Throw a runtime error when fetch is not defined. This would be the case for setups with a NodeJS version < 18.

v0.15.4

  • Fix broken ESM build due to lacking .js extension in some built files.

v0.15.0

  • New experimental_testPreview and experimental_deleteEnvironment methods.

v0.14.0

  • New failReason, abortReason and httpStatus fields added to the attemptNotifyDeploy function result object to provide more context on why the operation failed or was aborted.
  • Exported missing TypeScript typings describing result objects and fields for both functions.

v0.13.0

  • New pollTimeout parameter for pollCiGreenlightStatus to allow for a custom timeout in milliseconds for the polling operation. It now defaults to two hours.
  • New abortReason and httpStatus fields added to the PollCiGreenlightStatus type with "aborted" outcome to provide more context on why the poll was aborted.

v0.12.1

  • Export dependencies types from root module.
  • Refine DeployConfig.hostingService type to match API requirements.
  • Fix inaccessible changelog file from NPM.

v0.12.0

  • Define a more restrictive LogDriver interface for easy integration with GHA core interface.

v0.11.0

  • Support reproducedBugs field from CI-greenlight API.

⚠️ These are the last breaking changes brought to the 0.x major version. These are being introduced while the SDK hasn't been advertised to the public yet. Future changes will follow the SemVer versioning scheme.

BREAKING CHANGES:

  • pollCiGreenlightStatus and attemptNotifyDeploy now return a "result" object with an outcome field that can be either "success", "failed" or "aborted", instead of exiting the process with a non-zero code. This will provide more flexibility to the user to decide how to handle the outcome.

v0.10.3

BREAKING CHANGES:

  • Renamed attemptDeploy to attemptNotifyDeploy .

v0.10.2

  • Avoid logging dots after URL names in the logs. Dots can confuse terminal URL detection.

v0.10.1

  • Fix TypeScript types visibility.

v0.10.0

Initial release.

Supported Endpoints

  • /api/deploy_success
  • /api/v0/ci-greenlight/[root-run-id]

Readme

Keywords

none

Package Sidebar

Install

npm i @qawolf/ci-sdk

Weekly Downloads

4,960

Version

0.21.0

License

MIT

Unpacked Size

484 kB

Total Files

444

Last publish

Collaborators

  • atchyut.pulavarthi
  • matt.teran
  • mzqawolf
  • jsamr
  • denis-sokolov
  • jperl
  • flaurida