Commands

Run shell commands on a job's machine, with retries that skip work that already passed.

Inngest CI is an Inngest Labs project: early, moving fast, and shaped by your feedback. What's Labs?

$ runs a command on the job's machine. A non-zero exit code throws CommandFailedError, which fails the job.

TypeScript
const test = ci.job("test", async () => {
  await checkout();
  await $`pnpm install`;
  await $`pnpm --filter ${pkg} test`;
});

$ runs without a shell. The template's text is split on spaces and quotes are not parsed, so pass a value with spaces as an interpolation. Each interpolated value becomes one argument with no quoting, unless it follows text with no space, as in --filter=${pkg}, where it joins that argument. Arrays spread into several arguments, and false, null, and undefined are dropped. $.sh runs /bin/sh -c and escapes interpolated values, for pipes and &&.

Retries

.retries(n) runs a failed command up to n more times on the same machine. Each attempt is its own step in the trace.

TypeScript
await $`pnpm test`.retries(2);

Each command is a step. If the run resumes after a failure elsewhere, such as a GitHub API 503, Inngest replays the saved results and does not run finished commands again. Inngest does not retry a whole run because a command failed. If a command still fails after its retries, the job's check fails and the run ends.

Options

Options chain onto the command:

TypeScript
await $`pnpm lint`.nothrow();
await $`pnpm test`.env({ CI: "true" }).timeout("10m");
await $`pnpm exec playwright test`.as("e2e");

const sha = await $`git rev-parse HEAD`.text();
  • .nothrow() returns the result with its exit code instead of throwing.
  • .env(), .cwd(), and .timeout() set the environment, directory, and time limit.
  • .as(name) replaces the command text in the step name, as in test › e2e.
  • .text(), .lines(), and .json() return stdout in a convenient shape.

Reference lists every method, the result fields, and .background().

Share commands between jobs

Any function can run commands. It uses the calling job's machine:

TypeScript
export async function install() {
  await checkout();
  await $`pnpm install --frozen-lockfile`;
}

const test = ci.job("test", async () => {
  await install();
  await $`pnpm test`;
});

$ outside a job throws CiUsageError. Output arrives when the command ends, not while it runs.

Next steps

  • Machines covers where commands run.
  • Jobs covers how commands group into units of work.
  • Reference lists every command method.