How to Use the design.md CLI Programmatically: JavaScript and TypeScript API Guide
Yes, you can use the design.md CLI programmatically by importing functions from the @google/design.md/linter module, which exposes the same lint() and runLinter() APIs that power the command-line interface.
The google-labs-code/design.md repository provides both a command-line interface built on the citty framework and a fully-featured JavaScript/TypeScript API. While the design.md command handles file I/O and user interaction, all core functionality—including linting, diffing, and report generation—is exported from source modules that you can integrate directly into build pipelines, CI checks, or custom Node.js scripts.
Public API Architecture
The package exposes its programmatic interface through the exports field in packages/cli/package.json. The "./linter" conditional export maps to dist/linter/index.js, making the underlying engine accessible without spawning a child process.
In packages/cli/src/linter/index.ts, the library re-exports the primary public entry points:
lint– Validates a DESIGN.md document string and returns aLintReport(lines 15‑18).runLinter– Executes the complete linting pipeline with file resolution (line 35).DEFAULT_RULES– The rule set configuration used by the CLI.- Emitter handlers – For customizing report output streams.
The CLI implementation in packages/cli/src/index.ts (lines 16‑35) defines the command structure using citty, but delegates all actual processing to these same exported functions.
Core Programmatic Functions
lint()
The lint() function is the foundational API for validating DESIGN.md content. According to packages/cli/src/linter/index.ts (lines 15‑18), it accepts a document string and returns a LintReport object containing findings and summary statistics.
The CLI command in packages/cli/src/commands/lint.ts (lines 36‑40) uses readInput to read the file or stdin, then passes the content string directly to lint(). You can call the same function in your code to validate dynamically generated content without writing to disk.
runLinter()
For file-based workflows, runLinter() defined in packages/cli/src/linter/index.ts (line 35) provides a higher-level interface that handles file resolution and runs the complete pipeline. This function mirrors the internal logic of the CLI's lint command but accepts a file path and options object rather than parsing command-line arguments.
Utility Helpers
The packages/cli/src/utils.ts module exports formatting and diffing utilities used by both the CLI and programmatic consumers:
formatOutput(lines 46‑52) – Serializes aLintReportinto JSON or Markdown formats matching the CLI's--formatoptions.diffMaps(lines 58‑84) – Compares two design system states for the diff functionality.serializeDesignSystem(lines 58‑84) – Converts design system objects into comparable Map structures.
Programmatic Usage Examples
Lint a DESIGN.md String in Memory
Import lint from the linter module and formatOutput from utils to replicate the CLI's behavior without file I/O:
import { lint } from '@google/design.md/linter';
import { formatOutput } from '@google/design.md/utils';
const designMd = `
# My Design System
## Colors
- primary: #ff0000
`;
const report = lint(designMd);
// JSON output (equivalent to design.md lint --format json)
console.log(formatOutput(report, { format: 'json' }));
// Markdown output (equivalent to design.md lint --format markdown)
console.log(formatOutput(report, { format: 'markdown' }));
This example uses the same lint function called by the CLI in packages/cli/src/commands/lint.ts, but operates directly on a string variable.
Compare Design System Versions Programmatically
Reproduce the design.md diff command using the serialization and comparison utilities:
import { diffMaps, serializeDesignSystem } from '@google/design.md/utils';
import { parseDesignSystem } from '@google/design.md/linter';
import { readFileSync } from 'node:fs';
const before = parseDesignSystem(readFileSync('v1/DESIGN.md', 'utf-8'));
const after = parseDesignSystem(readFileSync('v2/DESIGN.md', 'utf-8'));
const diff = diffMaps(
new Map(Object.entries(serializeDesignSystem(before))),
new Map(Object.entries(serializeDesignSystem(after)))
);
console.log('Added:', diff.added);
console.log('Removed:', diff.removed);
console.log('Modified:', diff.modified);
This leverages diffMaps and serializeDesignSystem from packages/cli/src/utils.ts (lines 58‑84) to compute differences between design system versions without invoking the CLI process.
Run the Full Linter Pipeline
For scenarios requiring the complete rule engine with custom configuration:
import { runLinter, DEFAULT_RULES } from '@google/design.md/linter';
const options = {
rules: DEFAULT_RULES,
// additional configuration options
};
runLinter('path/to/DESIGN.md', options).then(report => {
console.log('Error count:', report.summary.errors);
console.dir(report.findings, { depth: null });
});
The runLinter function exported from packages/cli/src/linter/index.ts (line 35) executes the same pipeline as the CLI's lint command, returning a Promise that resolves with the complete report object.
How the CLI Consumes the API
The command-line interface in packages/cli/src/index.ts (lines 16‑35) defines the citty command structure, but each command delegates to the public API. For example, the lint command implementation in packages/cli/src/commands/lint.ts (lines 36‑40) performs the following:
- Resolves input via
readInput(handling file paths or stdin). - Calls
lint(content)with the resolved string. - Passes the result to
formatOutputfor serialization.
This architecture ensures that the programmatic API and CLI are functionally identical—any update to the linting logic immediately benefits both interfaces.
Summary
- The design.md package exports a public API from
@google/design.md/linterthat includeslint(),runLinter(), andDEFAULT_RULES. - All CLI functionality is available programmatically through functions defined in
packages/cli/src/linter/index.tsandpackages/cli/src/utils.ts. - The CLI is a thin wrapper around these exports, using citty for argument parsing in
packages/cli/src/index.tsbut delegating execution to the same functions you can import directly. - Utility functions like
formatOutputanddiffMapsallow you to replicate CLI output formats and comparison logic in your own code.
Frequently Asked Questions
Do I need to install the CLI separately to use the programmatic API?
No. Installing the npm package @google/design.md provides both the binary and the library exports. The package.json declares conditional exports ("./linter") that map to dist/linter/index.js, allowing you to import the API directly while the CLI binary remains available for command-line usage.
What is the difference between lint() and runLinter()?
The lint() function accepts a DESIGN.md content string and returns a validation report immediately, making it ideal for testing generated content or in-memory strings. The runLinter() function accepts a file path and options object, handling file I/O and running the complete pipeline—effectively mirroring the CLI's lint command but returning a Promise instead of writing to stdout.
How do I format the output to match the CLI exactly?
Import formatOutput from @google/design.md/utils (defined in packages/cli/src/utils.ts, lines 46‑52). Pass your LintReport object and specify the format option ('json' or 'markdown') to generate strings identical to the CLI's --format output. This function is what the CLI uses internally to serialize results before printing.
Can I customize which rules run when using the API programmatically?
Yes. When calling runLinter(), pass a rules array in the options object. You can import DEFAULT_RULES from @google/design.md/linter to use the standard configuration, or construct a custom array of rule objects to enable only specific validations. The lint() function also accepts options for rule configuration, allowing fine-grained control over the validation logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →