Docs/CI

Pierre CI

Pierre’s CI is a simple, scriptable (TypeScript), event based system, that allows you to run jobs and manage rich integrations with Pierre’s UI.

Install

$ npm i pierre

What it looks like

At it’s simplest this is Pierre CI:

// .pierre/ci/helloworld.ts

import { run } from 'pierre';

export default async () => {
  await run(`echo "hello world"`);
};

Every top-level file in .pierre/ci is automatically run as a job. To create a new job, create a new file. All top-level jobs are run in parallel. Files included in nested directories (e.g. .pierre/ci/util) are not run as jobs.

Jobs are named after file filename (e.g. vercel.ts will be named Vercel).

Jobs are executed every time a branch detects a new push.

Jobs

Job files in Pierre export a default function or array of functions.

If you return an array, each function will be evaluated serially:

export default [firstTask, secondTask, thirdTask];

Each Job is called with an id and a branch:

export default async ({ id, branch }) => {
  if (branch.name === '🍔') throw new Error('No burgers allowed');
  console.log('Burger free zone');
};

Everything logged from the default job will be streamed to the jobs pages in Pierre (e.g, https://pierre.co/[team]/[repo]/jobs/[…branch]).

Failing a job

There are a few ways to fail a job. The first, and the most recommended, is to simply throw an error:

export default async () => {
  throw new Error('This job failed… 😢');
};

This will result in the job failing with an exit code of 1. You can alternatively return a custom error code to indicate certain failure states, eg.

export default async () => {
  return 123;
};

Note: An exit code of 0 indicates success, while anything else indicates failure.

Run

Pierre exports a special function for executing commands in your container called run. Run automatically logs special information to your UI and makes your logs more readable.

import { run } from "pierre"

export default () => {
  await run("tsc -p ./tsconfig.json --noEmit", {
    label: "Run typescript typechecker"
  })
}

The run command also takes the following options:

export interface RunOptions {
  // Optional cwd to set
  cwd?: string;

  // When set to `true`, avoid passing through `process.env`
  clearEnv?: boolean;

  // Additional environment variables to set
  env?: Record<string, string>;

  // What exit code to assert for each command
  expectedCode?: number;

  // If set to true then no assertions are made on expected exit code
  allowAnyCode?: boolean;

  // Whether we should pipe stdout and stderr
  pipe?: boolean;

  // Optional label to print
  label?: string;

  // Timeout in milliseconds the command has to run
  timeout?: number;
}

Run itself provides a couple of return objects. Useful for extracting specific results or processing output for strings.

export interface RunResult {
	exitCode: number;
	stdmerged: string;
	stderr: string;
	stdout: string;
}

Configuration

You can provide a custom configuration file for each job. This file is located at .pierre/ci/[job].config.json. This file is also merged with the global configuration file at .pierre/ci/base.config.json.

Machine Size

Each job runs on a machine with 1 core and 2048MB of memory by default. You can provide a custom machine description in the configuration file though by using the size property.

{
  "size": "large"
}

The machine size can be one of the following:

SizeMemoryCores
micro2048MB1
small4096MB1
medium8192MB2
large16384MB4

You can also specify custom machine sizes with specific memory and core requirements:

{
  "size": {
    "memory_mb": 4096,
    "cores": 2
  }
}

When specifying custom sizes, the following constraints apply:

  • CPU cores must be one of: 1, 2, 4, 8, or 16
  • Memory must be between 2048MB and 8192MB per CPU core
    • Minimum memory: 2048MB × cores
    • Maximum memory: 8192MB × cores

If you specify values outside these ranges, they will be automatically adjusted to the nearest valid value.

Custom Image

By default each job runs on a standard image based off of Ubuntu 24.04 with:

node 22.14.0
npm 10.9.2
pnpm 10.6.3
yarn (follows packageManager in package.json)
bun 1.2.5
go 1.24.1
php 8.3.6
python 3.12.3
ruby 3.2.3
rustup
aws-cli
chromium binary, plus dependencies for chromium, firefox, and webkit for test tools that install/vendor their own browsers

If you need to run an image with a different set of tools, you can provide a custom image in the configuration file using the image property.

{
  "image": "node@sha256:a81372dcb7a0d4a183f453f04a8eba1f47ff44089294dcf73e5e368ec56d58d4"
}

The image provided must have a bash shell available, curl and git installed. It is recommended to use a specific version of the image (rather than the latest tag for example). When running we will look for a machine with the same image name provided in the configuration file. Using a tag could lead to different runners having different versions of the image at the same time.

Environment Variables

You can provide non-secret environment variables in the configuration file using the env property.

{
  "env": {
    "FOO": "bar"
  }
}

Extends

You can extend the configuration of a job by using the extends property. This will merge the configuration of the current job with the configuration of the job specified in the extends property.

{
  "extends": "large.config.json",
  "env": {
    "FOO": "bar"
  }
}

This looks for the file .pierre/ci/large.config.json and merges the configuration with the current job.

Directory structure

When starting a job will checkout your repo into the directory /code and run your job from there.

K/V Store

Pierre has it’s own Redis-powered KV store. This is useful for quickly storing information between CI runs such as performance metrics, bundle sizes, and other notes.

import { Store } from 'pierre';

export default async () => {
  await Store.set('dog', '🐕');
  await Store.get('dog');
};

Secrets

Secrets are managed through repo settings in Pierre and are made available in your local container as env variables accessible in process.env and on the command line via $.

import { run } from "pierre"

export default () => {
  await run("echo $VERCEL_ACCESS_TOKEN");
}

Joyful code review

Pierre wants you to enjoy code review with your team. So, we built a ~NEW~*~ Git platform to do just that. Get started today for free.

We're in public beta! Join our Discord to share feedback and chat with the Pierre team.