Search by

donut-org / donut

janpecha

Runs workflow files over unix commands

Package info

github.com/donut-org/donut

pkg:composer/donut-org/donut

Fund package maintenance!

Other

Statistics

Installs: 30

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-09-05 15:50 UTC

This package is auto-updated.

Last update: 2026-09-05 16:06:42 UTC


README

Build Status Downloads this Month Latest Stable Version License

Donate

Donut runs workflows described in JSON files. A workflow is a list of steps; each step runs one unix command, and the steps pass values to each other through a flat map of strings.

These workflows used to be bash scripts calling other bash scripts. Bash has no way to say this step needs these values — a mistyped variable name is an empty string, and an empty string is a perfectly good argument. Donut declares inputs up front, checks before it starts that every value a step reads is written by something earlier, and refuses to run a workflow that fails the check.

The documentation is in Czech; the messages Donut prints are English. docs/format-specifikace.md is the reference for the format.

Installation

Download the latest package or use Composer:

composer require donut-org/donut

Donut requires PHP 8.4 or later.

Donut reads blocks and workflows from a profile, not from the current directory. Create the default one:

mkdir -p ~/.config/donut/default/{blocks,workflows}

DONUT_PROFILE=name picks another profile, DONUT_HOME=path another root of profiles. To use a set that lives elsewhere — in a project repository, say — symlink it in: ln -s ~/projects/olw/donut ~/.config/donut/olw.

Example

Donut looks for blocks/ and workflows/ in the profile, by default ~/.config/donut/default/:

~/.config/donut/default/
	blocks/
		greet.json
	workflows/
		hello.json

A block wraps one command and declares what it needs:

{
	"name": "greet",
	"command": "echo",
	"args": [["Hello, {%name%}!"]],
	"inputs": {
		"name": { "required": true }
	}
}

A workflow is a list of steps, with its own inputs coming from the command line:

{
	"name": "hello",
	"description": "Greets someone.",
	"inputs": {
		"who": { "required": true, "description": "Whom to greet" }
	},
	"steps": [
		{
			"type": "run",
			"block": "greet",
			"in": { "name": "{%who%}" }
		}
	]
}

Run it. The first line is progress, written to stderr as each step runs; the second is the step's own output:

$ vendor/bin/donut hello --who=world
hello.json:steps[0]  greet
Hello, world!

And ask it what it takes:

$ vendor/bin/donut --list
  hello        Greets someone.

$ vendor/bin/donut hello --help
hello — Greets someone.

Inputs:
  --who=…          required   Whom to greet

Commands never go through a shell — Donut passes arguments to execve as a list, so a value containing a space, a quote or a semicolon is just a value.

Exit codes: 0 the workflow finished, 1 a step failed while running, 2 it never started (bad arguments, unknown workflow, failed validation). The difference is there for automation: 2 means retrying is pointless.

Documentation

In Czech:

docs/format-specifikace.md the format — blocks, workflows, the map, templates, validation
docs/zadani.md what this is for and what is left to build
docs/workflows/donut/ a real workload — 15 blocks and 4 workflows
docs/superpowers/specs/ the design document behind each part

License: New BSD License
Author: Jan Pecha, https://www.janpecha.cz/