Bsdkrun.CI (bsdkrun_ex v0.6.0)

Copy Markdown View Source

CI workflows defined in code instead of YAML.

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

Bsdkrun.CI.workflow("test")
|> Bsdkrun.CI.on_push("main")
|> Bsdkrun.CI.deps(["elixir", "erlang"])
|> Bsdkrun.CI.env("MIX_ENV", "test")
|> Bsdkrun.CI.step("deps", "mix deps.get")
|> Bsdkrun.CI.step("test", "mix test")
|> Bsdkrun.CI.run()

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

Summary

Functions

Set the clone depth (default 1).

Add nixpkgs dependencies — the toolchain the steps run against.

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

Override the engine (nixery by default).

Set a workflow-level environment variable.

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

Add a pull_request trigger targeting the given branch(es).

Add a push trigger for the given branch(es).

Execute the workflow in a microVM, streaming output.

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

Skip the checkout entirely.

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

Start a CI workflow definition.

Render the workflow file.

Types

t()

@type t() :: %Bsdkrun.CI{
  clone_depth: term(),
  clone_skip: term(),
  deps: term(),
  engine: term(),
  env: term(),
  name: term(),
  steps: term(),
  when_: term()
}

Functions

clone_depth(wf, depth)

@spec clone_depth(t(), pos_integer()) :: t()

Set the clone depth (default 1).

deps(wf, packages)

@spec deps(t(), [String.t()]) :: t()

Add nixpkgs dependencies — the toolchain the steps run against.

deps_from(wf, registry, packages)

@spec deps_from(t(), String.t(), [String.t()]) :: t()

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

engine(wf, engine)

@spec engine(t(), String.t()) :: t()

Override the engine (nixery by default).

env(wf, key, value)

@spec env(t(), String.t(), String.t()) :: t()

Set a workflow-level environment variable.

file_name(ci)

@spec file_name(t()) :: String.t()

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

on_pull_request(wf, branches)

@spec on_pull_request(t(), String.t() | [String.t()]) :: t()

Add a pull_request trigger targeting the given branch(es).

on_push(wf, branches)

@spec on_push(t(), String.t() | [String.t()]) :: t()

Add a push trigger for the given branch(es).

run(wf, opts \\ [])

@spec run(
  t(),
  keyword()
) :: :ok | {:error, term()}

Execute the workflow in a microVM, streaming output.

The YAML never touches the repository — it goes to a temp file and bsdkrun ci run -f. Returns :ok, or {:error, exit_code} when a step fails.

save(wf, repo)

@spec save(t(), Path.t()) :: {:ok, Path.t()} | {:error, term()}

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

skip_clone(wf)

@spec skip_clone(t()) :: t()

Skip the checkout entirely.

step(wf, name, command, step_env \\ %{})

@spec step(t(), String.t(), String.t(), %{required(String.t()) => String.t()}) :: t()

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

workflow(name)

@spec workflow(String.t()) :: t()

Start a CI workflow definition.

yaml(wf)

@spec yaml(t()) :: String.t()

Render the workflow file.

Scalars are emitted as JSON strings — valid YAML by construction — and commands as literal blocks when safe, so the SDK needs no YAML dependency.