From 1b69a39db1c981d01009438db106e464fd0d0639 Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Wed, 5 Nov 2025 14:17:13 +0100 Subject: [PATCH 1/2] Keep DTO as suffix when creating pascalCase --- .../src/transform/components-object.ts | 2 +- .../test/transform/components-object.test.ts | 43 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/openapi-typescript/src/transform/components-object.ts b/packages/openapi-typescript/src/transform/components-object.ts index 9d1678b4a..980ac6c10 100644 --- a/packages/openapi-typescript/src/transform/components-object.ts +++ b/packages/openapi-typescript/src/transform/components-object.ts @@ -101,7 +101,7 @@ export default function transformComponentsObject(componentsObject: ComponentsOb if (!shouldSkipEnumSchema) { const componentKey = changeCase.pascalCase(singularizeComponentKey(key)); - let aliasName = `${componentKey}${changeCase.pascalCase(name)}`; + let aliasName = `${componentKey}${changeCase.pascalCase(name, { suffixCharacters: "DTO" })}`; // Add counter suffix (e.g. "_2") if conflict in name let conflictCounter = 1; diff --git a/packages/openapi-typescript/test/transform/components-object.test.ts b/packages/openapi-typescript/test/transform/components-object.test.ts index f1d9fb781..3ce578592 100644 --- a/packages/openapi-typescript/test/transform/components-object.test.ts +++ b/packages/openapi-typescript/test/transform/components-object.test.ts @@ -757,7 +757,48 @@ export type Item = components['schemas']['Item']; export type Document = components['schemas']['Document']; export type Error = components['schemas']['Error']; `, - options: { ...DEFAULT_OPTIONS, rootTypes: true, rootTypesNoSchemaPrefix: true }, + options: { + ...DEFAULT_OPTIONS, + rootTypes: true, + rootTypesNoSchemaPrefix: true, + }, + }, + ], + [ + "options > rootTypes: true but keep DTO", + { + given: { + schemas: { + ItemDTO: { + type: "object", + required: ["name", "url"], + properties: { + name: { type: "string" }, + url: { type: "string" }, + }, + }, + }, + }, + want: `{ + schemas: { + ItemDTO: { + name: string; + url: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type ItemDTO = components['schemas']['ItemDTO']; +`, + options: { + ...DEFAULT_OPTIONS, + rootTypes: true, + rootTypesNoSchemaPrefix: true, + }, }, ], [ From 37d070bf7c8a84fe185e2e1af941dd4e1b52ab48 Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Fri, 14 Nov 2025 09:59:06 +0100 Subject: [PATCH 2/2] Add flag to specify to keep casing of the root exports --- docs/cli.md | 1 + packages/openapi-typescript/bin/cli.js | 7 +++++++ packages/openapi-typescript/src/index.ts | 1 + .../openapi-typescript/src/transform/components-object.ts | 5 +++-- packages/openapi-typescript/src/types.ts | 3 +++ packages/openapi-typescript/test/test-helpers.ts | 1 + .../test/transform/components-object.test.ts | 3 ++- 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index fb41bd941..08b9d469f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -121,6 +121,7 @@ The following flags are supported in the CLI: | `--path-params-as-types` | | `false` | Allow dynamic string lookups on the `paths` object | | `--root-types` | | `false` | Exports types from `components` as root level type aliases | | `--root-types-no-schema-prefix` | | `false` | Do not add "Schema" prefix to types at the root level (should only be used with --root-types) | +| `--root-types-keep-casing` | | `false` | Do not convert root type names to pascal case | | `--make-paths-enum` | | `false` | Generate ApiPaths enum for all paths | | `--generate-path-params` | | `false` | Generate path parameters for all paths where they are undefined by schema | diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index c3a589dc5..799a95223 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -33,6 +33,7 @@ Options --root-types (optional) Export schemas types at root level --root-types-no-schema-prefix (optional) Do not add "Schema" prefix to types at the root level (should only be used with --root-types) + --root-types-keep-casing Keep casing of root types (should only be used with --root-types) --make-paths-enum Generate ApiPaths enum for all paths `; @@ -63,6 +64,10 @@ if (args.includes("--root-types-no-schema-prefix") && !args.includes("--root-typ // biome-ignore lint/suspicious/noConsole: this is a CLI console.warn("--root-types-no-schema-prefix has no effect without --root-types flag"); } +if (args.includes("--root-types-keep-casing") && !args.includes("--root-types")) { + // biome-ignore lint/suspicious/noConsole: this is a CLI + console.warn("--root-types-keep-casing has no effect without --root-types flag"); +} const flags = parser(args, { boolean: [ @@ -84,6 +89,7 @@ const flags = parser(args, { "pathParamsAsTypes", "rootTypes", "rootTypesNoSchemaPrefix", + "rootTypesKeepCasing", "makePathsEnum", "generatePathParams", ], @@ -147,6 +153,7 @@ async function generateSchema(schema, { redocly, silent = false }) { pathParamsAsTypes: flags.pathParamsAsTypes, rootTypes: flags.rootTypes, rootTypesNoSchemaPrefix: flags.rootTypesNoSchemaPrefix, + rootTypesKeepCasing: flags.rootTypesKeepCasing, makePathsEnum: flags.makePathsEnum, generatePathParams: flags.generatePathParams, redocly, diff --git a/packages/openapi-typescript/src/index.ts b/packages/openapi-typescript/src/index.ts index dabd4e81b..26a1d8cbf 100644 --- a/packages/openapi-typescript/src/index.ts +++ b/packages/openapi-typescript/src/index.ts @@ -81,6 +81,7 @@ export default async function openapiTS( immutable: options.immutable ?? false, rootTypes: options.rootTypes ?? false, rootTypesNoSchemaPrefix: options.rootTypesNoSchemaPrefix ?? false, + rootTypesKeepCasing: options.rootTypesKeepCasing ?? false, injectFooter: [], pathParamsAsTypes: options.pathParamsAsTypes ?? false, postTransform: typeof options.postTransform === "function" ? options.postTransform : undefined, diff --git a/packages/openapi-typescript/src/transform/components-object.ts b/packages/openapi-typescript/src/transform/components-object.ts index 980ac6c10..0a5bdecac 100644 --- a/packages/openapi-typescript/src/transform/components-object.ts +++ b/packages/openapi-typescript/src/transform/components-object.ts @@ -101,14 +101,15 @@ export default function transformComponentsObject(componentsObject: ComponentsOb if (!shouldSkipEnumSchema) { const componentKey = changeCase.pascalCase(singularizeComponentKey(key)); - let aliasName = `${componentKey}${changeCase.pascalCase(name, { suffixCharacters: "DTO" })}`; + const componentName = ctx.rootTypesKeepCasing && key === "schemas" ? name : changeCase.pascalCase(name); + let aliasName = `${componentKey}${componentName}`; // Add counter suffix (e.g. "_2") if conflict in name let conflictCounter = 1; while (rootTypeAliases[aliasName] !== undefined) { conflictCounter++; - aliasName = `${componentKey}${changeCase.pascalCase(name)}_${conflictCounter}`; + aliasName = `${componentKey}${componentName}_${conflictCounter}`; } const ref = ts.factory.createTypeReferenceNode(`components['${key}']['${name}']`); if (ctx.rootTypesNoSchemaPrefix && key === "schemas") { diff --git a/packages/openapi-typescript/src/types.ts b/packages/openapi-typescript/src/types.ts index 0020bc03e..bd7d55e94 100644 --- a/packages/openapi-typescript/src/types.ts +++ b/packages/openapi-typescript/src/types.ts @@ -668,6 +668,8 @@ export interface OpenAPITSOptions { rootTypes?: boolean; /** (optional) Do not add Schema prefix to types at root level */ rootTypesNoSchemaPrefix?: boolean; + /** (optional) Keep casing of root types */ + rootTypesKeepCasing?: boolean; /** * Configure Redocly for validation, schema fetching, and bundling * @see https://redocly.com/docs/cli/configuration/ @@ -705,6 +707,7 @@ export interface GlobalContext { propertiesRequiredByDefault: boolean; rootTypes: boolean; rootTypesNoSchemaPrefix: boolean; + rootTypesKeepCasing: boolean; redoc: RedoclyConfig; silent: boolean; transform: OpenAPITSOptions["transform"]; diff --git a/packages/openapi-typescript/test/test-helpers.ts b/packages/openapi-typescript/test/test-helpers.ts index 13d46568a..444ffd918 100644 --- a/packages/openapi-typescript/test/test-helpers.ts +++ b/packages/openapi-typescript/test/test-helpers.ts @@ -25,6 +25,7 @@ export const DEFAULT_CTX: GlobalContext = { propertiesRequiredByDefault: false, rootTypes: false, rootTypesNoSchemaPrefix: false, + rootTypesKeepCasing: false, redoc: await createConfig({}, { extends: ["minimal"] }), resolve($ref) { return resolveRef({}, $ref, { silent: false }); diff --git a/packages/openapi-typescript/test/transform/components-object.test.ts b/packages/openapi-typescript/test/transform/components-object.test.ts index 3ce578592..d0cd85e94 100644 --- a/packages/openapi-typescript/test/transform/components-object.test.ts +++ b/packages/openapi-typescript/test/transform/components-object.test.ts @@ -765,7 +765,7 @@ export type Error = components['schemas']['Error']; }, ], [ - "options > rootTypes: true but keep DTO", + "options > rootTypes: true but keep casing", { given: { schemas: { @@ -798,6 +798,7 @@ export type ItemDTO = components['schemas']['ItemDTO']; ...DEFAULT_OPTIONS, rootTypes: true, rootTypesNoSchemaPrefix: true, + rootTypesKeepCasing: true, }, }, ],