# How to Use stdin with the DESIGN.md CLI for Build Pipeline Integration

> Integrate DESIGN.md CLI into your build pipelines by piping content via stdin. Learn how to use the dash argument for seamless CI/CD execution.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Supply `-` as the file argument to read DESIGN.md content from standard input, enabling seamless integration with CI/CD pipelines and shell scripts.**

The [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) CLI provides first-class support for Unix-style piping by accepting `-` as a file path placeholder. This capability allows you to use **stdin with the DESIGN.md CLI** in automated build pipelines, avoiding the need to write temporary files to disk.

## How Stdin Works in the DESIGN.md CLI

The CLI treats the positional `file` argument as a path to a [`DESIGN.md`](https://github.com/google-labs-code/design.md/blob/main/DESIGN.md) document. When you supply the literal string `-`, the utility helper `readInput()` in [`packages/cli/src/utils.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/utils.ts) streams data directly from `process.stdin` instead of the filesystem.

### The readInput() Implementation

According to the source code in [`packages/cli/src/utils.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/utils.ts) (lines 42-51), `readInput()` performs a strict equality check against the string `-`. When matched, it asynchronously consumes all chunks from the `stdin` stream, concatenates them, and returns the result as a UTF-8 string. If the terminal is interactive (TTY), the function prints a hint to `stderr` prompting the user to finish input with Ctrl-D.

### Commands Supporting Stdin

All CLI commands that accept a DESIGN.md file path import and reuse this helper:

- **`lint`** – validates the document and returns JSON
- **`export`** – converts the document to other formats
- **`diff`** – compares two versions, where the first argument can be `-`

## Implementation Details

### Error Handling with FileReadError

When file system operations fail, the CLI wraps native errors using `FileReadError` (defined in [`packages/cli/src/utils.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/utils.ts) lines 19-35). This provides user-friendly error messages while preserving the stack trace for debugging pipeline failures.

### Argument Declaration

The argument definition in [`packages/cli/src/commands/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/commands/lint.ts) (lines 24-28) explicitly documents the feature: "Path to DESIGN.md (use "-" for stdin)". This pattern is consistent across [`export.ts`](https://github.com/google-labs-code/design.md/blob/main/export.ts) and [`diff.ts`](https://github.com/google-labs-code/design.md/blob/main/diff.ts), ensuring uniform behavior across the toolchain.

## Practical Examples for Build Pipelines

### Pipe a File into the CLI

Unix pipelines allow you to stream an existing file directly without temporary storage:

```bash
cat path/to/DESIGN.md | design lint -

```

The CLI reads the piped content, validates it, and prints a JSON report to stdout.

### Inline Heredoc for CI Scripts

For ephemeral documents in shell scripts, use a heredoc:

```bash
design export - <<'EOF'
title: My Project
primary: true
tokens:
  - name: brand
    value: '#FF5733'
EOF

```

This supplies a minimal DESIGN.md document directly to the `export` command.

### GitHub Actions Integration

In CI environments, you can pipe secrets or previous step outputs:

```yaml
- name: Lint DESIGN.md from pipeline
  run: |
    echo "${{ secrets.DESIGN_MD_CONTENT }}" | design lint -
  env:
    DESIGN_MD_CONTENT: ${{ steps.fetch_design.outputs.content }}

```

The command exits with code 1 if validation fails, causing the job to fail immediately.

### Combine with JSON Processing Tools

Parse the JSON output for further decision-making:

```bash
cat DESIGN.md | design lint - | jq '.summary.errors'

```

This extracts the error count for conditional pipeline logic.

### Diff Command with Piped Input

Compare a previous version from stdin against a current file:

```bash
echo "$OLD_DESIGN" | design diff - ./DESIGN.md

```

The first argument (`-`) reads the "before" state from stdin while the second points to the current file on disk.

## Summary

- Supply `-` as the file path to read **DESIGN.md** content from **stdin** across all CLI commands.
- The `readInput()` helper in [`packages/cli/src/utils.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/utils.ts) (lines 42-51) handles the streaming logic and TTY detection.
- Supported commands include `lint`, `export`, and `diff`, enabling full pipeline integration.
- In CI/CD environments, combine with heredocs, pipes, or secrets to avoid writing sensitive files to disk.
- Exit codes propagate correctly, allowing standard CI failure mechanisms to function.

## Frequently Asked Questions

### Can I use stdin with all DESIGN.md CLI commands?

Yes. Any command that accepts a file path argument—including `lint`, `export`, and `diff`—supports the `-` convention. The `readInput()` utility is shared across these commands in `packages/cli/src/commands/`.

### How does the CLI handle interactive stdin input?

When `stdin.isTTY` is true (interactive terminal), `readInput()` prints a hint to `stderr` instructing you to finish input with Ctrl-D. In non-interactive pipelines, it silently consumes all available data until EOF.

### What happens if stdin is empty or malformed?

If the stream ends without data, `readInput()` returns an empty string, which typically causes validation commands like `lint` to report parse errors. The CLI exits with code 1 and surfaces the error via `FileReadError` or validation-specific error messages.

### Can I use stdin for both files in the diff command?

No. The `diff` command only supports stdin for the first (left-hand) argument. The second argument must be a path to a file on disk. You can work around this by writing the second version to a temporary file or using process substitution if your shell supports it.