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.
$ npm i pierre
At it’s simplest this is Pierre CI:
// .pierre/ci/helloworld.ts
import { run } from 'pierre';
export const label = 'Hello world';
export default async () => {
await run(`echo "hello world"`);
};
Every top-level file in .pierre/ci
is automatically run as a job in our
default Docker container.
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.
Provide a label to give your job a name, otherwise the filename will be used.
Job handlers are executed every time a branch detects a new push. In the future we will likely add additional events for “comments”, “merges”, etc.
Job files in Pierre export a default function or array of functions.
If you return an array, each function will be evaluated serially:
export const label = 'Serial Tasks';
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]
).
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.
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;
}
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
.
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:
Size | Memory | Cores |
---|---|---|
micro | 2048MB | 1 |
small | 4096MB | 1 |
medium | 8192MB | 2 |
large | 16384MB | 4 |
By default each job runs on a standard image with:
node 20.15.0
npm 10.7.0
pnpm 9.4.0
yarn (follows packageManager in package.json)
bun 1.1.17
go 1.21.6
php 8.2.20
python 3.12.4 (miniconda)
ruby 3.1.2p20
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.
You can provide non-secret environment variables in the configuration file using the env
property.
{
"env": {
"FOO": "bar"
}
}
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.
When starting a job will checkout your repo into the directory /code
and run your job from there.
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 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");
}
Pierre wants you to enjoy your code reviews. So, we built a ~*~NEW~*~ git platform. Join the waitlist to get early access.
Orrr… skip the line! Join our Discord for early access