How to Compare Two DESIGN.md Files to Detect Changes and Regressions
The design.md CLI provides a dedicated diff command that parses two DESIGN.md files into design-system models and computes a structural diff to detect changes, returning exit code 1 when regressions are found.
The google-labs-code/design.md repository includes a built-in comparison tool that allows you to compare two DESIGN.md files to identify token changes, component modifications, and quality regressions. By leveraging the existing linter infrastructure, the diff command transforms markdown files into structured design-system models and performs granular map-based comparisons. This functionality is essential for design system governance, enabling teams to validate changes before merging pull requests or deploying updates.
How the DESIGN.md Diff Command Works
The diff implementation follows a seven-step pipeline defined in packages/cli/src/commands/diff.ts. Understanding this flow helps you interpret results and debug comparison issues.
1. Input Reading and Validation
The process begins with readInput in packages/cli/src/utils.ts, which loads the before and after files from disk or stdin. If a file cannot be read, the utility throws a FileReadError and halts execution.
2. Design System Parsing
Both files are parsed using the lint function exposed from packages/cli/src/linter/index.js. This generates a DesignSystemState object containing immutable Map instances for colors, typography, rounded corners, spacing, and component definitions.
3. Component Serialization
Because components are stored as Map<string, ComponentDef>, the helper serializeComponents (located in packages/cli/src/commands/diff.ts) converts these complex objects into plain JavaScript objects. This normalization allows components to be compared using the same logic as primitive tokens.
4. Structural Map Diffing
The core comparison logic resides in diffMaps within packages/cli/src/utils.ts. This utility walks the before and after maps simultaneously, returning three arrays:
added— keys present only in the after fileremoved— keys present only in the before filemodified— keys present in both but with differing values
5. Report Generation
The command aggregates results into a JSON structure containing:
- tokens: Per-category diffs for colors, typography, rounded, and spacing
- findings: Lint summary for each file plus the delta of errors and warnings
- regression: A boolean flag set to
truewhen the after file contains more errors or warnings than the before file
6. Output Formatting
The formatOutput utility in packages/cli/src/utils.ts renders the report as either JSON (default) or Markdown (--format markdown), making it suitable for both automated parsing and human review.
7. Exit Code Signaling
The CLI exits with code 1 when a regression is detected, otherwise 0. This behavior enables seamless integration with CI pipelines and pre-commit hooks.
CLI Usage Examples
The most common way to compare two DESIGN.md files is via the command line interface registered in packages/cli/src/index.ts.
Generate a JSON Diff Report
design.md diff path/to/before.DESIGN.md path/to/after.DESIGN.md \
--format json > diff-report.json
Generate a Markdown Report for PR Comments
design.md diff path/to/before.DESIGN.md path/to/after.DESIGN.md \
--format markdown > diff-report.md
Both commands print the diff to stdout and return exit code 1 if the after file introduces new lint errors or warnings, indicating a regression.
Programmatic API for Custom Workflows
You can replicate the diff logic in Node.js scripts by importing the internal utilities. This approach is useful for custom reporting or integration with existing build tools.
import { readInput } from 'design.md/packages/cli/src/utils.js';
import { lint } from 'design.md/packages/cli/src/linter/index.js';
import { diffMaps } from 'design.md/packages/cli/src/utils.js';
import { serializeComponents } from 'design.md/packages/cli/src/commands/diff.js';
// Load files
const beforeText = await readInput('before.DESIGN.md');
const afterText = await readInput('after.DESIGN.md');
// Parse into design system models
const beforeReport = lint(beforeText);
const afterReport = lint(afterText);
// Compare token categories
const tokenDiff = {
colors: diffMaps(beforeReport.designSystem.colors, afterReport.designSystem.colors),
typography: diffMaps(beforeReport.designSystem.typography, afterReport.designSystem.typography),
rounded: diffMaps(beforeReport.designSystem.rounded, afterReport.designSystem.rounded),
spacing: diffMaps(beforeReport.designSystem.spacing, afterReport.designSystem.spacing),
};
// Compare components (requires serialization)
tokenDiff.components = diffMaps(
serializeComponents(beforeReport.designSystem.components),
serializeComponents(afterReport.designSystem.components),
);
console.log(JSON.stringify(tokenDiff, null, 2));
The serializeComponents helper converts the internal Map<string, ComponentDef> structure into plain objects, enabling the shallow comparison used by diffMaps.
Detecting Regressions in CI/CD
The exit code behavior makes the diff command ideal for automated quality gates. The following bash script demonstrates how to fail a build when design regressions are detected:
#!/usr/bin/env bash
set -euo pipefail
design.md diff "$BASE_DESIGN" "$HEAD_DESIGN" --format json > diff.json
if grep -q '"regression":true' diff.json; then
echo "🚨 Design regression detected!"
exit 1
fi
echo "✅ No design regressions."
This script captures the JSON output, checks the regression boolean field, and fails with exit code 1 if the after state degrades design system quality.
Key Implementation Details
According to the source code in google-labs-code/design.md, the diff logic is deliberately map-centric. The linter already represents the design system as immutable Map data structures, so diffMaps performs a shallow JSON-string comparison of values. This architectural choice guarantees that nested property changes—such as a component's border radius value—are detected without requiring a computationally expensive full tree-diff algorithm.
Summary
- The
design.md diffcommand inpackages/cli/src/commands/diff.tsprovides native support to compare two DESIGN.md files. - Input handling uses
readInputfrompackages/cli/src/utils.tsto load files or stdin. - Parsing relies on the
lintfunction inpackages/cli/src/linter/index.jsto generateDesignSystemStatemodels. - Comparison utilizes
diffMapsinpackages/cli/src/utils.tsto identify added, removed, and modified tokens and components. - Component handling requires
serializeComponentsto normalizeMap<string, ComponentDef>into comparable plain objects. - Regression detection compares lint error/warning counts and sets exit code
1when quality degrades. - Output formats include JSON for automation and Markdown for human-readable reports.
Frequently Asked Questions
What exit code does the diff command return when a regression is detected?
The CLI exits with code 1 when the after file contains more lint errors or warnings than the before file, and 0 otherwise. This behavior is implemented in packages/cli/src/commands/diff.ts and enables straightforward integration with CI pipelines that expect non-zero exit codes on failure.
How does the diff command handle component definitions?
Components are stored internally as Map<string, ComponentDef> objects. The serializeComponents helper function in packages/cli/src/commands/diff.ts converts these maps into plain JavaScript objects before comparison. This allows the generic diffMaps utility to detect changes to component properties using shallow value comparison.
Can I use the diff functionality programmatically without the CLI?
Yes. You can import readInput, lint, diffMaps, and serializeComponents from their respective source files in packages/cli/src/ to build custom comparison workflows. This approach lets you process DESIGN.md diffs within Node.js scripts while maintaining full control over output formatting and error handling.
What format options are available for the diff output?
The formatOutput utility in packages/cli/src/utils.ts supports two formats: JSON (default) for machine-readable structured data, and Markdown (--format markdown) for human-readable reports suitable for pull request comments or documentation. Both formats include the same underlying data: token diffs, lint findings, and the regression boolean.
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 →