Skip to content

Feat/diff command#2940

Draft
vadyvas wants to merge 14 commits into
mainfrom
feat/diff-command
Draft

Feat/diff command#2940
vadyvas wants to merge 14 commits into
mainfrom
feat/diff-command

Conversation

@vadyvas

@vadyvas vadyvas commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Adds a new experimental redocly diff <base> <revision> command that compares two API descriptions and reports what was added, removed, and changed.

  • Structural diff for every supported spec (OpenAPI 2/3.0/3.1/3.2, AsyncAPI, Arazzo, Overlay, OpenRPC) by reusing the existing openapi-core type trees — no per-type hand-written traversal.
  • Breaking-change classification (breaking / warning / non-breaking) for OpenAPI 3.x via a polarity-aware, lint-style rule registry (worst verdict wins).
  • Pipeline: each side is bundled and collected (via walkDocument) into a flat stable-pointer map → two-pass compare into a change list → classify. List items with a natural identity (e.g. parameters by in+name) are matched by identity, so reordering is not reported as a change. Shared components are diffed once; whether a component change is breaking is derived from where it's used (request/response), via a usage index.
  • Output formats: stylish (default), json (versioned schema), markdown (PR comments), html (self-contained report).
  • CI gate: --fail-on breaking|warning|none (default breaking) sets the exit code.

Usage:

redocly diff v1/openapi.yaml v2/openapi.yaml

redocly diff https://example.com/openapi.yaml ./openapi.yaml --format=json

redocly diff main-openapi.yaml pr-openapi.yaml --fail-on=warning

redocly diff v1.yaml v2.yaml --format=html -o diff-report.html

Reference

Testing

Screenshots (optional)

image

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

vadyvas and others added 4 commits July 7, 2026 00:06
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compare two API descriptions and report added, removed, and changed
parts. Structural diff works for every supported spec type via the
existing openapi-core type trees; breaking-change classification
(breaking / warning / non-breaking) applies to OpenAPI 3.x.

The diff engine lives entirely in the CLI package and consumes only the
public @redocly/openapi-core API (walkDocument, type trees, bundle) —
packages/core is untouched. Pipeline: collect each side into a flat
stable-pointer map, two-pass compare into a change list, then classify
with a polarity-aware lint-style rule registry (worst verdict wins).

Supports stylish, json, markdown, and html output and a --fail-on CI
gate. Marked [experimental]; 14 starter rules documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fe59519

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

};

function escapeCell(value: string): string {
return value.replace(/\|/g, '\\|').replace(/\n/g, ' ');
tatomyr and others added 10 commits July 7, 2026 19:45
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… verdicts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ons, and path-param matching

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e case

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vadyvas

vadyvas commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@RomanHotsiy
short overview of how the diff command works and what it looks like. interested in your feedback on the design

flowchart LR
    A["base + revision"] --> C["collect ×2<br/>(walkDocument → flat maps)"] --> D["compare<br/>(set diff)"] --> E["classify<br/>(polarity + rules)"] --> F["report<br/>(stylish/json/md/html)"]
    C -. "$ref edges" .-> U[UsageIndex] -. polarity .-> E
Loading

Inputs for now are files or urls - resolved through the existing BaseResolver; comparing against git revisions isn't supported

  1. Collect — each side (base and revision) is bundled and walked with the existing walkDocument + type trees into a flat Map<stablePointer, NodeEntry>. Pointers are stable: list items are keyed by identity (e.g. params by in+name), not array index, so reordering isn't a change; $ref are kept as attributes, so a shared schema is diffed once at its component path.
  2. Compare — dumb two-pass set-diff over the union of keys: only in base → removed, only in revision → added, in both → shallow property diff. A removed/added subtree collapses into one change.
  3. Classify — verdicts are binary: breaking / non-breaking. The key concept is polarity: the same change flips meaning by direction (a property becoming required breaks requests but is safe in responses). Polarity is derived from the pointer; for shared components it comes from the UsageIndex, where the component is referenced (request/response), transitively.
    Rules are tiny lint-style {id, description, visit()} objects in a registry (granular, since the rule set will likely grow)
  4. Reportstylish, json, markdown, html.
redocly diff base.yaml revision.yaml 
redocly diff base.yaml revision.yaml --fail-on=breaking --format=json
GET /pets
  ✖ breaking      changed  parameters/{query:limit} · required
      Parameter became required. (parameter-became-required)

3 breaking, 1 non-breaking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants