Skip to content

feat: setup the post-bundle decorators#2733

Open
AlbinaBlazhko17 wants to merge 13 commits intomainfrom
feat/add-post-decorator-phase
Open

feat: setup the post-bundle decorators#2733
AlbinaBlazhko17 wants to merge 13 commits intomainfrom
feat/add-post-decorator-phase

Conversation

@AlbinaBlazhko17
Copy link
Copy Markdown
Contributor

@AlbinaBlazhko17 AlbinaBlazhko17 commented Apr 9, 2026

What/Why/How?

Currently we run remove-unused-components as a regular decorator in the same walk as bundle phase. This creates a lot of side issues with refs in this decorator, which is hard to fix right in the decorator. I decided to create postBundleDecorators and run them after doc is bundled.

What i did:

  • Added postBundleDecorators in types, constants and method config.
  • Added new phase called runPostBundleDecorators to walk through the doc one more time and invoke all postBundleDecorators on bundled doc.
  • Extended bundleDocument with a new phase.
  • Refactored remove-unused-components to track components by local pointer strings (#/components/schemas/Foo) instead of resolved Location objects. This removes the dependency on resolve() during ref tracking and makes "used-in" tracking simpler and more reliable when running post-bundle (where all refs are already inlined).
  • Added tests to fully cover cases with remove-unused-components.

Reference

Testing

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

Note

Medium Risk
Changes the bundling pipeline by adding a second post-bundle walk, which can affect decorator ordering and bundled output. Refactors remove-unused-components reference tracking, so regressions could lead to over/under-pruning components across specs.

Overview
Adds a new post-bundle decorator phase to bundleDocument, allowing decorators to run after bundling and $ref resolution via plugin-defined postBundleDecorators.

Moves the built-in remove-unused-components decorator for OAS2/OAS3 into this post-bundle phase and refactors it to track usage by component pointer keys instead of resolved Location/resolve() results, improving correctness for refs resolved during bundling.

Updates config/plugin types and built-in plugin wiring to support postBundleDecorators, and adds new e2e bundle fixtures/snapshots to cover tricky ref and recursion scenarios.

Reviewed by Cursor Bugbot for commit b4f9dc4. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: b4f9dc4

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

This PR includes changesets to release 3 packages
Name Type
@redocly/openapi-core Minor
@redocly/cli 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
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

CLI Version Mean Time ± Std Dev (s) Relative Performance (Lower is Faster)
cli-latest 3.569s ± 0.031s ▓▓▓ 1.09x
cli-next 3.262s ± 0.022s ▓ 1.00x (Fastest)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 79.89% (🎯 79%) 6726 / 8419
🔵 Statements 79.35% (🎯 79%) 6967 / 8780
🔵 Functions 82.92% (🎯 82%) 1365 / 1646
🔵 Branches 71.59% (🎯 71%) 4583 / 6401
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/bundle/bundle-document.ts 88% 92.3% 66.66% 88% 75, 95-106
packages/core/src/config/builtIn.ts 100% 100% 100% 100%
packages/core/src/config/config.ts 63.84% 62.89% 75% 64.24% 170-200, 226, 230, 234-252, 284, 302-322, 438-485
packages/core/src/decorators/oas2/index.ts 100% 100% 100% 100%
packages/core/src/decorators/oas2/remove-unused-components.ts 89.74% 89.65% 72.72% 88.57% 49, 95-105
packages/core/src/decorators/oas3/index.ts 100% 100% 100% 100%
packages/core/src/decorators/oas3/remove-unused-components.ts 97.72% 90.9% 100% 100% 20
Generated in workflow #9388 for commit b4f9dc4 by the Vitest Coverage Report Action

@AlbinaBlazhko17
Copy link
Copy Markdown
Contributor Author

@cursor review

@AlbinaBlazhko17 AlbinaBlazhko17 marked this pull request as ready for review April 10, 2026 06:47
@AlbinaBlazhko17 AlbinaBlazhko17 requested review from a team as code owners April 10, 2026 06:47
(location.absolutePointer.length === removed.length ||
location.absolutePointer[removed.length] === '/')
)
(sourceKey) => sourceKey === undefined || !removedKeys.has(sourceKey)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is confusing:

 sourceKey === undefined  

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed logic

const componentLevelLocalPointer = localPointer.split('/').slice(0, 4).join('/');
const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
const targetPointer = getContainingComponentKey(ref.$ref);
if (!targetPointer) return;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's not obvious that external refs are undefined, so we can skip registering it.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: OAS2 getContainingComponentKey lacks local-ref guard unlike OAS3
    • Added if (!pointer.startsWith('#/')) return so external/URL refs are ignored before parseRef strips the URI, matching OAS3 behavior and preventing mis-keying.

Create PR

Or push these changes by commenting:

@cursor push 72816a3152
Preview (72816a3152)
diff --git a/packages/core/src/decorators/oas2/remove-unused-components.ts b/packages/core/src/decorators/oas2/remove-unused-components.ts
--- a/packages/core/src/decorators/oas2/remove-unused-components.ts
+++ b/packages/core/src/decorators/oas2/remove-unused-components.ts
@@ -26,6 +26,7 @@
   }
 
   function getContainingComponentKey(pointer: string): string | undefined {
+    if (!pointer.startsWith('#/')) return;
     const [type, name] = parseRef(pointer).pointer;
     if (!type || !name) return undefined;
     if (!OAS2_COMPONENT_TYPES.includes(type as keyof Oas2Components)) return undefined;

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b4f9dc4. Configure here.

if (!type || !name) return undefined;
if (!OAS2_COMPONENT_TYPES.includes(type as keyof Oas2Components)) return undefined;
return `${type}/${name}`;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OAS2 getContainingComponentKey lacks local-ref guard unlike OAS3

Low Severity

The OAS2 getContainingComponentKey does not verify the pointer is a local ref before parsing, unlike the OAS3 version which guards with pointer.startsWith('#/components/'). Because parseRef strips the URI portion, an external ref like external.yaml#/definitions/Foo produces the same key (definitions/Foo) as the local #/definitions/Foo. When keepUrlRefs is enabled, unresolved external URL refs could be misidentified as references to local components, causing incorrect usedIn tracking.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b4f9dc4. Configure here.

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