Skip to content

What is Spry

  • The problem Spry solves and why it matters for your daily workflow
  • How Markdown transforms from static text into an executable runtime
  • The technical architecture that makes this possible
  • Where Spry fits in your toolchain compared to Make, shell scripts, and CI/CD platforms
  • Real-world scenarios with concrete examples you can adapt

Why This Matters: The Documentation Problem You Already Have

Section titled “Why This Matters: The Documentation Problem You Already Have”

Every development team faces this: You write documentation with code examples, deployment instructions, or setup guides. Six months later, someone follows those instructions and nothing works. The codebase evolved, but the documentation stayed frozen in time.

This is not a discipline problem. It is a structural problem. Documentation and code live in separate systems with no automatic synchronization.

The cost is real:

  • New team members waste hours debugging outdated instructions
  • Deployment runbooks fail silently during incidents
  • README files become historical artifacts rather than living guides
  • CI/CD pipelines duplicate logic already written in docs

Spry is a TypeScript library built on Deno that transforms standard Markdown files into executable programs. Here is what that means technically:

DAG (Directed Acyclic Graph) — A fancy way of saying “a task dependency chart with no circular conflicts.” Simply means: “Task B needs Task A to finish first, and there are no loops where tasks wait for each other forever.”

Interpolation — Replacing placeholders (like ${variable}) with actual values when the task runs. Think of it like “fill in the blanks” for your scripts.

  1. Parsing: Spry reads your .md file and identifies fenced code blocks (the triple-backtick sections)
  2. Processing Instructions: Special syntax after the language identifier (like bash build --dep lint) tells Spry how to treat each block
  3. DAG Construction: Spry builds a Directed Acyclic Graph of task dependencies
  4. Execution: Tasks run in the correct order, with proper shell environments and interpolation

The key insight: Your Markdown file remains 100% valid Markdown. It renders correctly on GitHub, in VS Code preview, in documentation sites. Spry simply adds the capability to execute what was previously just text.

Think of Spry like a recipe book that can actually cook. Normally, a recipe just tells you what to do - you have to do the work yourself. With Spry, the recipe book can execute its own instructions. You write your documentation with code examples, and Spry can actually run those examples. If the recipe says “preheat oven” then “bake cake”, Spry makes sure the oven is preheated before it tries to bake.


Here is a real Spryfile that builds a TypeScript project:

# Build Pipeline
This document defines our build process. Run it with `./spry.ts task run build`.
## Code Quality
First, we format and lint the code:
```bash format --desc "Format all TypeScript files"
deno fmt
```
```bash lint --dep format --desc "Lint after formatting"
deno lint
```
## Compilation
Once code quality checks pass, we compile:
```bash build --dep lint --desc "Compile TypeScript to JavaScript"
deno task compile
```

Let’s break down the fence header bash lint --dep format --desc "Lint after formatting":

  1. bash - The language identifier. Tells Spry to execute this block using the bash shell. Spry supports bash, sh, zsh, powershell, python, typescript, sql, duckdb, and more.

  2. lint - The task name. This becomes a unique identifier you can reference from other tasks or run directly via CLI.

  3. --dep format - Dependency declaration. This task will not run until the format task completes successfully. You can specify multiple dependencies: --dep format --dep typecheck.

  4. --desc "..." - Human-readable description. Shows up when you run task ls to list available tasks.

The key insight: The text after the triple backticks is not just a language hint for syntax highlighting—it is a complete task specification.


When you run ./spry.ts task run build, here is what happens internally:

1. Parse Markdown → Extract all fenced code blocks
2. Identify Tasks → Find blocks with task names in their headers
3. Build Dependency Graph → Resolve --dep flags into a DAG
4. Topological Sort → Determine safe execution order
5. Execute in Waves → Run independent tasks in parallel when possible
6. Report Results → Show success/failure for each task

Don’t worry about the fancy name! Topological sort just means “put things in the right order so dependencies come first.”

Think of getting dressed: you can’t put on shoes before socks. Topological sort figures out: socks → shoes → coat (not coat → shoes → socks).

For Spry, it means: if Task B depends on Task A, then A always runs before B. Spry figures this order out automatically.


For our example above, the execution order is: formatlintbuild

This is identical to what tools like Make or Just do, except:

  • Your task definitions are embedded in prose that explains why they exist
  • The file renders as documentation in any Markdown viewer
  • There is no separate build file to maintain

Technical Deep Dive: The 4-Phase Lifecycle

Section titled “Technical Deep Dive: The 4-Phase Lifecycle”

Spry processes code blocks through four distinct phases:

PhaseInputOutputWhat Happens
SyntacticRaw MarkdownNotebookCodeCellParse fence headers, extract language, name, and raw attributes
ContextualNotebookCodeCellPlaybookCodeCellApply frontmatter configuration, resolve file-level settings
SemanticPlaybookCodeCellTaskDirectiveRecognize task types (TASK, CONTENT, PARTIAL), parse dependencies
ExecutableTaskDirectiveTaskResolve all interpolations, finalize shell configuration

This phased approach enables powerful debugging. If something goes wrong, you can identify exactly which phase failed and why.


Understanding where Spry fits helps you decide when to use it:

AspectMake/JustSpry
Task DefinitionRecipes in specialized syntaxFenced code blocks in Markdown
DocumentationComments, separate READMEProse is the documentation
DependenciesTarget prerequisites--dep flags
ReadabilityRequires learning Make syntaxStandard Markdown
RenderingPlain textRenders beautifully everywhere

Use Spry when: Your build logic benefits from explanation, you want self-documenting pipelines, or your team includes people who do not know Make syntax.

Stick with Make when: You need deep ecosystem integration, complex pattern rules, or your team is already fluent in Make.

AspectShell ScriptsSpry
StructureSequential executionDAG-based dependencies
DocumentationComments (often sparse)Markdown prose
Error HandlingManual set -e, trapsBuilt-in task failure handling
ReusabilitySource other scriptsImport from other Markdown files

Use Spry when: You have multi-step processes with dependencies, need to document the “why” alongside the “what”, or want to run subsets of steps easily.

Stick with scripts when: You have simple linear sequences, need maximum portability, or are working in environments where Deno is not available.

AspectJupyterSpry
File Format.ipynb (JSON).md (plain text)
Version ControlDifficult diffsClean diffs
Execution ModelCell-by-cell interactiveDAG-based batch
Output StorageEmbedded in fileSeparate or captured
Use CaseData explorationAutomation & documentation

Use Spry when: You want executable documentation, automation pipelines, or CI/CD logic.

Use Jupyter when: You need interactive exploration, inline visualizations, or iterative data analysis.


Spry is not a general-purpose tool—it is designed for specific scenarios where executable documentation provides clear value:

Your README includes setup instructions:

## Getting Started
Install dependencies:
```bash install --desc "Install project dependencies"
npm install
```
Run the development server:
```bash dev --dep install --desc "Start dev server"
npm run dev
```

Now someone can run ./spry.ts task run dev and either:

  • It works, confirming the docs are accurate
  • It fails, immediately revealing documentation drift

Infrastructure automation often lives in cryptic scripts. With Spry:

# Database Migration Playbook
## Pre-Migration Checks
Before touching the database, verify backups exist:
```bash check-backups --desc "Verify recent backup exists"
aws s3 ls s3://backups/db/ | grep $(date +%Y-%m-%d)
```
## Run Migration
Only proceed if backups are confirmed:
```bash migrate --dep check-backups --desc "Apply database migrations"
DATABASE_URL=$PROD_DB alembic upgrade head
```

The playbook documents the process and executes it. New team members read the explanation; the commands run exactly as documented.

Spry has first-class support for generating SQLPage web applications:

---
sqlpage-conf:
database_url: sqlite://app.db
---
# User Dashboard
```sql HEAD
SELECT 'card' as component, 'Users' as title;
```
```sql TAIL
SELECT username, email, created_at FROM users ORDER BY created_at DESC;
```

This generates a complete web application from Markdown. The HEAD and TAIL directives control SQL generation order.

4. Build Pipelines That Explain Themselves

Section titled “4. Build Pipelines That Explain Themselves”

Instead of a Makefile + separate documentation:

# CI Pipeline
## Why This Order?
We run linting before tests because syntax errors should be caught early.
We run unit tests before integration tests because they are faster and catch most issues.
```bash lint
deno lint
```
```bash unit-tests --dep lint
deno test tests/unit/
```
```bash integration-tests --dep unit-tests
deno test tests/integration/
```
```bash deploy --dep integration-tests --when "env.DEPLOY == 'true'"
./scripts/deploy.sh
```

Setting clear boundaries helps you make the right tool choice:

MisconceptionReality
”Spry replaces TypeScript/Python/Go”Spry orchestrates execution of code written in other languages. It does not replace your programming language.
”Spry is a CI/CD platform”Spry handles task logic. GitHub Actions, GitLab CI, Jenkins handle triggers, scaling, and infrastructure. You can run Spry inside CI/CD.
”Spry is like Jupyter for everything”Unlike Jupyter, Spry is optimized for batch automation, not interactive exploration. There are no inline outputs or visualizations.
”Spry is a templating engine”Spry has interpolation (${...}), but complex templating is better handled by dedicated tools like Jinja or Handlebars.

Spry is built on a principle from computer science history: Literate Programming, pioneered by Donald Knuth in the 1980s.

“Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.” — Donald Knuth

Traditional approach:

Code file → Does things → Comment explains (maybe)
Docs file → Describes things → Drifts from code

Spry approach:

Markdown file → Explains AND does things → Cannot drift (it runs)

When you write a Spryfile, you are simultaneously:

  • Documenting: Writing what needs to happen and why
  • Implementing: Creating the automation that makes it happen
  • Validating: Ensuring your documentation stays accurate (if it breaks, you know immediately)

  • Start small: Convert one existing shell script to a Spryfile. See how it feels before converting your entire build system.

  • Use descriptive task names: build-frontend is better than bf. You will thank yourself when running task ls.

  • Leverage dependencies for safety: If Task B should never run without Task A completing, express that with --dep. Do not rely on humans remembering the order.

  • Keep prose meaningful: Do not just describe what the code does (the code shows that). Explain why it does it, what could go wrong, and what to check if it fails.

  • Version control your Spryfiles: Since they are plain Markdown, you get clean diffs. You can see exactly how your automation logic evolved.