Skip to content

feat: add bundle --use-titles-for-component-names flag#2851

Open
kanoru3101 wants to merge 33 commits into
mainfrom
feat/duplicate-component-names-via-title-flag
Open

feat: add bundle --use-titles-for-component-names flag#2851
kanoru3101 wants to merge 33 commits into
mainfrom
feat/duplicate-component-names-via-title-flag

Conversation

@kanoru3101

@kanoru3101 kanoru3101 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Added an opt-in --use-titles-for-component-names flag to bundle: inlined Schema components are named from each schema's title (PascalCased format) instead of the file basename.

Reference

#2797

Testing

With flag

Screenshot 2026-06-03 at 22 07 20

Error missing title

Screenshot 2026-06-03 at 23 32 15

Error invalid characters

Screenshot 2026-06-03 at 23 35 14

Warn title collision

Screenshot 2026-06-10 at 11 01 39

Warn two title collision

Screenshot 2026-06-10 at 11 02 47

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

Low Risk
Opt-in bundle flag with default off; changes only schema component naming and bundler diagnostics, with broad test coverage and no auth or data-path impact.

Overview
Adds an opt-in --use-titles-for-component-names flag to redocly bundle (CLI, core API, docs, changeset). When enabled, inlined Schema components are named from each schema’s title (trimmed, then PascalCased on spaces) instead of $ref basename / pointer, so names like AuthorityModel vs AuthorityRequest avoid brittle Authority / Authority-2 collisions.

The bundler reports errors for missing title or titles that can’t produce a valid component key (validated via shared COMPONENT_NAME_PATTERN in oas-types, also reused by spec-components-invalid-map-name). Title collisions reuse --component-renaming-conflicts-severity (warn/error/off) and include a from pointer to the first schema; on title problems it falls back to basename naming. Parameters and other non-schema components keep the existing basename logic.

New toPascalCase utility, unit/e2e tests and fixtures, and help snapshot updates cover the behavior.

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

@changeset-bot

changeset-bot Bot commented Jun 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 17f3012

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

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 81.26% (🎯 81%) 7297 / 8979
🔵 Statements 80.62% (🎯 80%) 7584 / 9407
🔵 Functions 84.43% (🎯 84%) 1465 / 1735
🔵 Branches 72.91% (🎯 72%) 4935 / 6768
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/bundle.ts 87.8% 76.66% 100% 87.8% 129-133, 140-144, 162
packages/core/src/oas-types.ts 100% 100% 100% 100%
packages/core/src/bundle/bundle-document.ts 90.47% 91.66% 100% 90.47% 81-92
packages/core/src/bundle/bundle-visitor.ts 73.61% 69.73% 100% 73.61% 36, 40-48, 59-63, 70, 79, 87, 92-113, 184-196, 213-214, 232-233, 247-248, 281
packages/core/src/rules/oas3/spec-components-invalid-map-name.ts 61.53% 100% 54.54% 61.53% 45-65
packages/core/src/utils/to-pascal-case.ts 100% 100% 100% 100%
Generated in workflow #10185 for commit 17f3012 by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

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

@kanoru3101 kanoru3101 changed the title feat: Bundle base on title feat: add bundle --use-titles-for-component-names flag Jun 3, 2026
@kanoru3101 kanoru3101 self-assigned this Jun 3, 2026
@kanoru3101 kanoru3101 added the snapshot Create experimental release PR label Jun 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

⚠️ 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 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

⚠️ 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 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

⚠️ 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 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

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

@kanoru3101 kanoru3101 marked this pull request as ready for review June 8, 2026 10:16
@kanoru3101 kanoru3101 requested review from a team as code owners June 8, 2026 10:16
Comment thread packages/core/src/bundle/bundle-visitor.ts Outdated
@kanoru3101 kanoru3101 added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

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

Comment thread packages/core/src/__tests__/bundle.test.ts Outdated
['API v2 User', 'APIV2User'],
[' padded ', 'Padded'],
['', ''],
])('converts %j to %j', (input, expected) => {

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.

What is %j?

@kanoru3101 kanoru3101 Jun 11, 2026

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.

The names $input and $expected have been changed for clarity

Comment thread packages/core/src/bundle/bundle-visitor.ts Outdated
return { name, prevName };
}

function componentNameFromTitle(

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.

Let's check if we can reuse the existing logic.

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.

I tried collapsing it into a single if inside getComponentName without the extracted helper, but it comes out worse: the title path has failure modes the basename path doesn't (missing title / unusable chars / collision) and on failure it falls back to the basename, so you either duplicate the -N loop or scatter useTitle && checks through one big function. Keeping componentNameFromBasename as the shared primitive avoids both while leaving the default logic untouched.

'bundle',
'--use-titles-for-component-names',
'--component-renaming-conflicts-severity',
'error',

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.

LEt's add off as well.

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.

Added test with off

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

Copy link
Copy Markdown
Contributor

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

Install with NPM:

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

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0df08e9. Configure here.

Comment thread packages/core/src/bundle/bundle-visitor.ts
@kanoru3101 kanoru3101 requested a review from tatomyr June 11, 2026 10:06
let components: Record<string, ComponentsGroup>;
let rootLocation: Location;

const firstSchemaLocationByName = new Map<string, Location>();

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.

firstSchemaLocationByName exists only to populate the collision's from — the "referenced from …" link that points at the first schema which already took the name. It can't be derived the way the basename path handles a conflict, because the two collisions behave differently:

  • Basename collision auto-resolves: it appends -N and reports a rename warning at the current location. It never references the other schema, so it needs no location tracking.
  • Title collision is a user-fixable error — the fix is to rename one of the two conflicting titles, so the message points from at the other (first) schema to help the user find both.

@tatomyr tatomyr left a comment

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.

LGTM

@kanoru3101 kanoru3101 requested a review from RomanHotsiy June 11, 2026 12:06
@drwicid

drwicid commented Jun 11, 2026

Copy link
Copy Markdown

Question — Should lint also evaluate the effective derived component name?

My understanding is that bundled inlined schemas already receive derived component names today, traditionally based on the referenced file basename or $ref pointer segment. This option adds another derivation strategy for schemas: when --use-titles-for-component-names is enabled, the component name can instead come from the schema’s title, with fallback to the existing basename/pointer logic when the title is missing or invalid.

Given that the bundle process already has to resolve an effective component name either way, would it make sense for the lint/rule phase to expose or reuse that same derived-name calculation?

Linting can certainly validate the presence or shape of title fields directly. However, that only covers one input to the naming strategy. What seems more useful is linting against the final effective name that bundle would produce, regardless of whether that name came from title or from the traditional basename / $ref pointer fallback.

That would allow rules such as component-name-unique to detect collisions before bundling, including collisions introduced by title-derived names as well as collisions that still occur through fallback naming. It would also let validity and naming/casing rules apply to the actual component key that will be emitted, with diagnostics reported against the original schema files and source locations.

This seems preferable to requiring a second lint pass against the generated bundled file, where diagnostics are tied to generated output and are less directly traceable to the originating schema.

I understand the lifecycle split: bundle runs preprocessors, rules, and decorators, while lint runs preprocessors and rules but not decorators. Is keeping effective component-name resolution bundle-only an intentional boundary, or would sharing the derived-name helper with lint be reasonable so these checks can happen before bundling?

| --remove-unused-components | boolean | Remove unused components from the `bundle` output. |
| --skip-decorator | [string] | Ignore certain decorators. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
| --use-titles-for-component-names | boolean | Build Schema component names from each schema's `title` field. See [Use titles for component names](#use-titles-for-component-names). |

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.

Let's change it to --component-names-strategy with options like "basename" (default) or "title"?

@tatomyr what do you think?

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.

Sounds even better to me. What about "filename" instead of "basename"?

return {
key,
problem: {
message: 'Schema must define a `title` to build a component name.',

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.

Need to clarify it is required only when using this option or someone may get confused:

Suggested change
message: 'Schema must define a `title` to build a component name.',
message: 'Schema must define a `title` when using --use-titles-for-component-names.',

},
};
}
if (!new RegExp(COMPONENT_NAME_PATTERN).test(key)) {

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.

do we need it? should we just replace any unsupported symbol ourselves with dashes?

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.

I'll change it to replace and add to docs about it

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.

5 participants