Skip to content

feat(cli): add tree command#2869

Draft
kanoru3101 wants to merge 34 commits into
mainfrom
feat/graph-command
Draft

feat(cli): add tree command#2869
kanoru3101 wants to merge 34 commits into
mainfrom
feat/graph-command

Conversation

@kanoru3101

@kanoru3101 kanoru3101 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Adds a redocly tree command that prints the structure of an API description — its
paths, operations, and the component $ref chains between them — for quick orientation
in any API (single- or multi-file) and $ref impact 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 an
affected-operations summary;
--files switches to the file-level $ref graph; and
--format outputs stylish (default), json, or mermaid. Works fully with OpenAPI
2.0/3.x; AsyncAPI and Arazzo render as a flat list of top-level $ref'd components.

Reference

Testing

tree-demo.yaml
# tree-demo.yaml
openapi: 3.0.3
info:
  title: Pet Store (tree demo)
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: listPets
      responses:
        '200':
          description: A list of pets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
    post:
      operationId: createPet
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewPet'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
  /pets/{petId}:
    parameters:
      - $ref: '#/components/parameters/PetId'
    get:
      operationId: getPet
      responses:
        '200':
          description: A pet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
  /owners/{ownerId}:
    get:
      operationId: getOwner
      responses:
        '200':
          description: An owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Owner'
components:
  parameters:
    PetId:
      name: petId
      in: path
      required: true
      schema:
        type: string
  schemas:
    Pet:
      type: object
      properties:
        name:
          type: string
        home:
          $ref: '#/components/schemas/Address'
    NewPet:
      type: object
      properties:
        seed:
          $ref: '#/components/schemas/Pet'
    Owner:
      type: object
      properties:
        address:
          $ref: '#/components/schemas/Address'
        friends:
          type: array
          items:
            $ref: '#/components/schemas/Owner'
    Address:
      type: object
      properties:
        city:
          type: string
    Orphan:
      type: object
      properties:
        unused:
          type: boolean

Run tree

Screenshot 2026-06-17 at 10 28 13

Run tree --format=mermaid

Screenshot 2026-06-17 at 10 33 03
flowchart 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:bold
Loading

Run tree --format=json

JSON 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 Pet

Screenshot 2026-06-17 at 10 38 49

Run --affected-by Pet --affected-by Address

Screenshot 2026-06-17 at 10 41 48

Run tree --files

Screenshot 2026-06-17 at 11 02 53 Screenshot 2026-06-17 at 11 02 28

Screenshots (optional)

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

kanoru3101 and others added 12 commits June 11, 2026 16:38
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-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7efffd9

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

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 81.29% (🎯 81%) 7644 / 9403
🔵 Statements 80.67% (🎯 80%) 7957 / 9863
🔵 Functions 84.29% (🎯 84%) 1524 / 1808
🔵 Branches 73.38% (🎯 73%) 5166 / 7040
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/types.ts 100% 100% 100% 100%
packages/cli/src/commands/lint.ts 94.33% 86.84% 100% 94.33% 76-78, 143, 176
packages/cli/src/commands/tree/build-graph.ts 100% 94.44% 100% 100%
packages/cli/src/commands/tree/build-structure.ts 96.51% 84.31% 100% 98.76% 91, 107, 116
packages/cli/src/commands/tree/filter-affected.ts 100% 100% 100% 100%
packages/cli/src/commands/tree/index.ts 0% 0% 0% 0% 47-239
packages/cli/src/commands/tree/match-affected-by.ts 100% 100% 100% 100%
packages/cli/src/commands/tree/node-id.ts 100% 100% 100% 100%
packages/cli/src/commands/tree/print/json.ts 0% 100% 0% 0% 4
packages/cli/src/commands/tree/print/mermaid.ts 100% 75% 100% 100%
packages/cli/src/commands/tree/print/stylish.ts 100% 100% 100% 100%
Generated in workflow #10362 for commit 7efffd9 by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x ± 0.01 ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)
cli-next ▓ 1.00x (Fastest) ▓ 1.00x ± 0.01 ▓ 1.01x ± 0.01

@RomanHotsiy RomanHotsiy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@kanoru3101 kanoru3101 self-assigned this Jun 12, 2026
kanoru3101 and others added 11 commits June 12, 2026 19:10
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>
@kanoru3101 kanoru3101 changed the title feat: dependency graph command feat: tree command Jun 15, 2026
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>
@kanoru3101 kanoru3101 changed the title feat: tree command feat: add tree command to CLI Jun 15, 2026
@kanoru3101 kanoru3101 changed the title feat: add tree command to CLI feat(cli): add tree command Jun 15, 2026
kanoru3101 and others added 2 commits June 15, 2026 15:34
The absolute-ref to node-id rule, the codepoint sort comparator, and the
operation-method set each had duplicate copies across build-graph.ts and
build-structure.ts. Move them to node-id.ts as the single source so the
two graph builders cannot drift.

Also fix build-graph's edge-refs sort to use the codepoint comparator
(was the default .sort()), matching the determinism the module documents,
and drop a redundant narrating comment in build-structure.

No behavior change: 58 tree unit tests and 11 e2e snapshots pass unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kanoru3101 kanoru3101 added the snapshot Create experimental release PR label Jun 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1781680214 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1781680214

⚠️ Note: This is a development build and may contain unstable features.

@kanoru3101 kanoru3101 added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jun 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1781680815 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1781680815

⚠️ Note: This is a development build and may contain unstable features.

@kanoru3101 kanoru3101 added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jun 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1781681068 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1781681068

⚠️ Note: This is a development build and may contain unstable features.

kanoru3101 and others added 7 commits June 17, 2026 11:25
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iants, duplicate e2e snapshot)

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

snapshot Create experimental release PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants