feat(cli): add tree command#2869
Conversation
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>
…ck steps) 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>
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>
🦋 Changeset detectedLatest commit: f8f3f83 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Performance Benchmark (Lower is Faster)
|
RomanHotsiy
left a comment
There was a problem hiding this comment.
This seems to only show details about split spec.
I think we need it not about the file strcuture but about the OpenAPI iteslef.
E.g. if there is one spec in one file I would expect affected to still work listing me the path names.
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>
…llback 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>
Dynamic imports inside the test body made vitest transform the whole untransformed dependency subtree within the test, exceeding the 5000ms per-test budget when istanbul coverage is enabled. Static top-level imports move that cost to the file-load phase, which has no timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the `treeview` language to the example code fences in tree.md (matching eject.md / translate.md house style) to satisfy markdownlint MD040. Stop tracking the internal agentic planning/spec docs under docs/superpowers/: they were accidentally committed into the published documentation and caused all vale and linkcheck failures plus most markdownlint errors. Nothing references them; they remain available locally but are no longer published. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… merge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… trim comments to project style
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
| if (nodeIds.has(mapped.id)) { | ||
| addIds([mapped.id]); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Root change omits dependents
Medium Severity
When --affected-by targets the document root via a JSON pointer such as #/, only the root node id is added to the changed set. filterAffected then walks reverse dependencies from that single seed, so nothing below the root is included. Structure mode treats the root file path as affecting the whole graph, but the pointer form and --files with the root file path do not, so impact output is nearly empty and inconsistent with the docs.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d6e78f8. Configure here.
| if (apis.length > 1) { | ||
| return exitWithError( | ||
| 'The tree command shows the structure of one API description at a time. Pass a single API, or use --files for the multi-API file-level graph.' | ||
| ); |
There was a problem hiding this comment.
Default tree fails multiple config APIs
Medium Severity
Running redocly tree with no API argument loads every API from the Redocly config via getFallbackApisOrExit, then structure mode errors when more than one API is returned. Docs say the command uses the config API for default structure view, but multi-API configs fail instead of showing one tree or a clear selection path (unlike --files, which supports multiple APIs).
Reviewed by Cursor Bugbot for commit fb2f569. Configure here.
| config, | ||
| types: getTypes(specVersion), | ||
| externalRefResolver, | ||
| }); |
There was a problem hiding this comment.
Bundle errors silently ignored
Medium Severity
Default structure mode always bundles the API before walking it, but only uses the bundled document and never inspects bundleDocument’s returned problems. Bundler errors or warnings are dropped, so the command can exit successfully while printing a tree that omits or misrepresents content that failed to bundle.
Reviewed by Cursor Bugbot for commit 7aa745b. Configure here.
| roots: graph.roots.filter((root) => affected.has(root)), | ||
| nodes: graph.nodes.filter((node) => affected.has(node.id)), | ||
| edges: graph.edges.filter((edge) => affected.has(edge.from) && affected.has(edge.to)), | ||
| }; |
There was a problem hiding this comment.
Path affected-by omits operations
Medium Severity
Structure mode reuses filterAffected, which only walks edges backward. Path and operation nodes sit on the parent→child spine, so --affected-by on a path (for example /users) keeps the root and path but drops that path’s operations and their component chains, contradicting documented path impact analysis.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 40a8f05. Configure here.
| if (nodeIds.has(input) && rel !== rootId) { | ||
| addIds([input]); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Relative affected-by inputs fail
Low Severity
After resolving an --affected-by value to a cwd-relative path, structure mode only checks nodeIds.has(input). Inputs like ./schemas/Pet normalize to schemas/Pet but never match the node id, so they warn as unknown even when the shorthand id exists.
Reviewed by Cursor Bugbot for commit 40a8f05. Configure here.
40a8f05 to
f8f3f83
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 6 total unresolved issues (including 5 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f8f3f83. Configure here.
| .map((node) => node.id); | ||
| const summary = | ||
| totalOperations > 0 | ||
| ? `${affectedOperations} of ${totalOperations} operations affected · affected paths: ${affectedPaths.join(', ') || 'none'}` |
There was a problem hiding this comment.
Path parameter over-counts operations
Medium Severity
The affected summary counts every operation node in the filtered subgraph. Reverse impact traversal follows structural edges from path to operation, so a change to a path-level parameter component still counts that path’s operations as affected, contradicting the documented case where only the path is impacted and the summary can read 0 of N operations affected.
Reviewed by Cursor Bugbot for commit f8f3f83. Configure here.


What/Why/How?
Adds a
redocly treecommand that prints the structure of an API description — itspaths, operations, and the component
$refchains between them — for quick orientationin any API (single- or multi-file) and
$refimpact analysis in CI and code review.The default mode renders the document structure (root → paths → operations → component
chains);
--affected-by <component|path|file>shows only the impacted part plus anaffected-operations summary;
--filesswitches to the file-level$refgraph; and--formatoutputsstylish(default),json, ormermaid. Works fully with OpenAPI2.0/3.x; AsyncAPI and Arazzo render as a flat list of top-level
$ref'd components.Reference
Testing
tree-demo.yaml
Run
treeRun
tree --format=mermaidflowchart LR n0["/owners/{ownerId}"] n1["/pets"] n2["/pets/{petId}"] n3["GET /owners/{ownerId}"] n4["GET /pets"] n5["GET /pets/{petId}"] n6["POST /pets"] n7["parameters/PetId"] n8["schemas/Address"] n9["schemas/NewPet"] n10["schemas/Owner"] n11["schemas/Pet"] n12["tree-demo.yaml"]:::root n0 --> n3 n1 --> n4 n1 --> n6 n2 --> n5 n2 --> n7 n3 --> n10 n4 --> n11 n5 --> n11 n6 --> n9 n6 --> n11 n9 --> n11 n10 --> n8 n10 --> n10 n11 --> n8 n12 --> n0 n12 --> n1 n12 --> n2 classDef root font-weight:boldRun
tree --format=jsonJSON file
{ "roots": [ "tree-demo.yaml" ], "nodes": [ { "id": "/owners/{ownerId}", "resolved": true, "kind": "path", "file": "tree-demo.yaml" }, { "id": "/pets", "resolved": true, "kind": "path", "file": "tree-demo.yaml" }, { "id": "/pets/{petId}", "resolved": true, "kind": "path", "file": "tree-demo.yaml" }, { "id": "GET /owners/{ownerId}", "resolved": true, "kind": "operation", "file": "tree-demo.yaml" }, { "id": "GET /pets", "resolved": true, "kind": "operation", "file": "tree-demo.yaml" }, { "id": "GET /pets/{petId}", "resolved": true, "kind": "operation", "file": "tree-demo.yaml" }, { "id": "POST /pets", "resolved": true, "kind": "operation", "file": "tree-demo.yaml" }, { "id": "parameters/PetId", "resolved": true, "kind": "component", "file": "tree-demo.yaml" }, { "id": "schemas/Address", "resolved": true, "kind": "component", "file": "tree-demo.yaml" }, { "id": "schemas/NewPet", "resolved": true, "kind": "component", "file": "tree-demo.yaml" }, { "id": "schemas/Owner", "resolved": true, "kind": "component", "file": "tree-demo.yaml" }, { "id": "schemas/Pet", "resolved": true, "kind": "component", "file": "tree-demo.yaml" }, { "id": "tree-demo.yaml", "resolved": true, "kind": "root", "file": "tree-demo.yaml", "root": true } ], "edges": [ { "from": "/owners/{ownerId}", "to": "GET /owners/{ownerId}", "refs": [] }, { "from": "/pets", "to": "GET /pets", "refs": [] }, { "from": "/pets", "to": "POST /pets", "refs": [] }, { "from": "/pets/{petId}", "to": "GET /pets/{petId}", "refs": [] }, { "from": "/pets/{petId}", "to": "parameters/PetId", "refs": [ "#/components/parameters/PetId" ] }, { "from": "GET /owners/{ownerId}", "to": "schemas/Owner", "refs": [ "#/components/schemas/Owner" ] }, { "from": "GET /pets", "to": "schemas/Pet", "refs": [ "#/components/schemas/Pet" ] }, { "from": "GET /pets/{petId}", "to": "schemas/Pet", "refs": [ "#/components/schemas/Pet" ] }, { "from": "POST /pets", "to": "schemas/NewPet", "refs": [ "#/components/schemas/NewPet" ] }, { "from": "POST /pets", "to": "schemas/Pet", "refs": [ "#/components/schemas/Pet" ] }, { "from": "schemas/NewPet", "to": "schemas/Pet", "refs": [ "#/components/schemas/Pet" ] }, { "from": "schemas/Owner", "to": "schemas/Address", "refs": [ "#/components/schemas/Address" ] }, { "from": "schemas/Owner", "to": "schemas/Owner", "refs": [ "#/components/schemas/Owner" ] }, { "from": "schemas/Pet", "to": "schemas/Address", "refs": [ "#/components/schemas/Address" ] }, { "from": "tree-demo.yaml", "to": "/owners/{ownerId}", "refs": [] }, { "from": "tree-demo.yaml", "to": "/pets", "refs": [] }, { "from": "tree-demo.yaml", "to": "/pets/{petId}", "refs": [] } ] }Run
tree --affected-by <component|path|file>Run
--affected-by PetRun
--affected-by Pet --affected-by AddressRun
tree --filesRun
treefor split filesScreenshots (optional)
Check yourself
Security
Note
Low Risk
Additive, read-only CLI analysis with heavy test coverage; the only cross-command change is skipping config-lint output for mermaid-formatted runs.
Overview
Adds
redocly tree, a new CLI command that visualizes API descriptions as dependency graphs for orientation and change-impact review.Default mode (one API) bundles the spec, walks paths/operations, and follows
$refchains into components (OpenAPI 2/3; AsyncAPI/Arazzo degrade to a flatter component list).--filesbuilds a cross-file$refgraph and can merge multiple APIs.--affected-bynarrows the graph upstream from changed pointers, names, or files, with operation/path or file summaries.--formatsupportsstylish, pipe-safejson, andmermaid.Wiring includes yargs registration, v2 docs/sidebar, a minor changeset, broad unit and e2e snapshot tests, and a small
linttweak so config-lint is skipped whenformatismermaid(same as other single-payload formats).Reviewed by Cursor Bugbot for commit f8f3f83. Bugbot is set up for automated code reviews on this repo. Configure here.