Skip to content

Proposed syntax for v1

INFO

Most of the syntax proposed on this site is already implemented. Not all features are covered here, just the main ones.

This syntax aims to be as simple as possible whilst covering most ci/cd use cases.

There are plenty of advanced features such as user inputs, delaying workflows, etc... which aren't covered in this document

Workflows

A workflow file is the entry point for each of our pipelines. We define a workflow in a project with a new .pandaci/[name].workflow.ts.

We run workflows using the DENO runtime. This enables us to run a given workflow without needing to first install all dependencies (and irrelevant dependencies e.g. in a monorepo). This would be really slow!

Jobs

We define jobs within a workflow. They describe the runner (e.g. a vm) in which we are to run our tasks.

When self-hosting, the default runner is 'local' which runs the job directly on the api backend.

You can specify a self-hosted runner by providing a http(s) address for the runner e.g. 'https://my-panda-ci-runner.com'

ts
import { job } from "@pandaci/core";

job("dev - deploy", { runner: "local" }, () => {
  // ...
});

Tasks

A job contains 1 or more tasks. These are where we run our commands.

A task can either be docker or native

Docker tasks

Specify a docker image and all the commands within the task will be executed within that docker image.

Since this image is run inside a job, multiple tasks can communicate with one another. We can use tasks to spin up temporary services such as a database for integration testing

ts
import { job, task } from "@pandaci/core";

job("dev - deploy", () => {
  task.docker("Build", { image: "node:22" }, () => {
    // ...
  });
});

Docker volumes

To share data between tasks or cache files, we can create volumes which can be added to a task.

A volume can optionally be mounted to a path directly on the job runner using the host option. This can enable persistent data between runs.

ts
import { job, task, volume } from "@pandaci/core";

job("dev - deploy", () => {
  const cache = volume({ name: "cache", host: ".cache" });
  task.docker("Build", { image: "node:22", volumes: [cache(".cache")] }, () => {
    // ...
  });
});

Native tasks

These tasks run the shell commands directly in the job/runner.

ts
import { job, task } from "@pandaci/core";

job("dev - deploy", () => {
  task.native("Build", () => {
    // ...
  });
});

Exec

These are the commands/functions we are running inside our tasks.

Shell commands

Runs the given command

ts
import { job, task, $ } from "@pandaci/core";

job("dev - deploy", () => {
  task.native("Build", () => {
    $`echo 'Hello World'`;
  });
});

$ api

  • $.cwd("./src") Sets the directory to run the command in
  • .text() Parses the given output into a string
  • .json() Parses the given output into a json
  • .nothrow() Avoids throwing exceptions on non 0 exit codes
  • .throws() Ensures we throw on a non 0 exit code (default)

Evaluating Typescript

We can pass ts/js code which will be executed in the task

ts
job("dev - deploy", () => {
  task.native("Build", () => {
    $.evaluate(() => {
      console.log("hello world");
    });
  });
});

This ts/js will need to be serializable so in order to use any external inputs, we will need to pass them in as variables.