bsdkrun/ci

CI workflows defined in code instead of YAML.

The builder produces exactly the file bsdkrun ci (and tangled’s spindle) consumes — yaml is that file, save commits it to .tangled/workflows/, and run executes it in a microVM without a file ever touching the repository:

ci.workflow("test")
|> ci.on_push(["main"])
|> ci.deps(["gleam", "erlang"])
|> ci.env("CI_FROM", "sdk")
|> ci.step("deps", "gleam deps download")
|> ci.step("test", "gleam test")
|> ci.run()

Code is the source of truth and YAML the wire format, in that order — which is why save writes a generated-file header: a hand-edit there will be overwritten by the next save.

Types

pub type Step {
  Step(
    name: String,
    command: String,
    env: dict.Dict(String, String),
  )
}

Constructors

  • Step(
      name: String,
      command: String,
      env: dict.Dict(String, String),
    )

A workflow under construction.

pub type Workflow {
  Workflow(
    name: String,
    engine: String,
    when: List(#(List(String), List(String))),
    deps: dict.Dict(String, List(String)),
    env: dict.Dict(String, String),
    steps: List(Step),
    clone_depth: option.Option(Int),
    clone_skip: Bool,
  )
}

Constructors

  • Workflow(
      name: String,
      engine: String,
      when: List(#(List(String), List(String))),
      deps: dict.Dict(String, List(String)),
      env: dict.Dict(String, String),
      steps: List(Step),
      clone_depth: option.Option(Int),
      clone_skip: Bool,
    )

Values

pub fn clone_depth(wf: Workflow, depth: Int) -> Workflow

Set the clone depth (default 1).

pub fn deps(wf: Workflow, packages: List(String)) -> Workflow

Add nixpkgs dependencies — the toolchain the steps run against.

pub fn deps_from(
  wf: Workflow,
  registry: String,
  packages: List(String),
) -> Workflow

Add dependencies from a custom registry (a flake reference).

pub fn engine(wf: Workflow, engine: String) -> Workflow

Override the engine (nixery by default).

pub fn env(wf: Workflow, key: String, value: String) -> Workflow

Set a workflow-level environment variable.

pub fn file_name(wf: Workflow) -> String

The workflow file name save writes: <name>.yml.

pub fn on_pull_request(
  wf: Workflow,
  branches: List(String),
) -> Workflow

Add a pull_request trigger targeting the given branches.

pub fn on_push(wf: Workflow, branches: List(String)) -> Workflow

Add a push trigger for the given branches.

pub fn run(wf: Workflow) -> Result(Nil, error.Error)

Execute the workflow in a microVM against the current directory, streaming output. The YAML never touches the repository — it goes to a temp file and bsdkrun ci run -f.

pub fn run_in(
  wf: Workflow,
  dir: option.Option(String),
) -> Result(Nil, error.Error)

run against an explicit repository directory.

pub fn save(
  wf: Workflow,
  repo: String,
) -> Result(String, simplifile.FileError)

Write into <repo>/.tangled/workflows/ and return the path.

pub fn skip_clone(wf: Workflow) -> Workflow

Skip the checkout entirely.

pub fn step(
  wf: Workflow,
  name: String,
  command: String,
) -> Workflow

Append a step; steps run serially in one VM, from the workspace root.

pub fn step_env(
  wf: Workflow,
  name: String,
  command: String,
  env: dict.Dict(String, String),
) -> Workflow

Append a step with step-scoped environment variables.

pub fn workflow(name: String) -> Workflow

Start a CI workflow definition.

pub fn yaml(wf: Workflow) -> String

Render the workflow file.

Scalars are emitted as JSON strings — valid YAML by construction — and commands as literal blocks when safe, so no YAML library is needed.

Search Document