Spry LogoSpry Docs

What is Spry

Spry is a TypeScript library built on Deno that transforms standard Markdown files into executable programs

What You Will Discover

  • 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

The Real Problem

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

What Spry Actually Does

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

Key Terms You'll Need

DAG (Directed Acyclic Graph)

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.

How It Works

Parsing

Spry reads your .md file and identifies fenced code blocks (the triple-backtick sections)

Processing Instructions

Special syntax after the language identifier (like bash build --dep lint) tells Spry how to treat each block

DAG Construction

Spry builds a Directed Acyclic Graph of task dependencies

Execution

Tasks run in the correct order, with proper shell environments and interpolation

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.

In Simple Terms

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.

A Concrete Example

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
```

What's Happening in This Code?

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

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.

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

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

--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.

The Execution Model

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

What's "Topological Sort"?

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

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.

How Spry Compares to Tools You Know

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.

Where Spry Excels

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

1. Self-Verifying Documentation

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

2. Literate DevOps Playbooks

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.

3. SQL Notebooks with SQLPage

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

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
```

What Spry is NOT

Setting clear boundaries helps you make the right tool choice:

Common Misconceptions

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.

The Philosophy Behind Spry

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)

Pro Tips

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.

How is this guide?

Last updated on

On this page