Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions docs/src/pages/reference/configuration/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,25 +294,40 @@ Same as the tags mode if you don't use the `schemas` property only one file will

### indexFiles

Type: `Boolean`
Type: `Boolean | Object`

Valid values: true or false. Defaults to true.
Valid values:

Specify whether to place `index.ts` in `schemas` generation.
- `true` (default): Generates index files for both workspace and schemas
- `false`: Disables index file generation
- Object with optional `workspace` and `schemas` functions to customize index file generation

Example:
The `indexFiles` option allows you to control the generation of index files that export all available components. When set to an object, you can customize the behavior for both workspace and schemas separately.

```js
module.exports = {
petstore: {
output: {
schemas: 'src/gen/model',
indexFiles: false,
indexFiles: {
workspace(implementations) {
// Filter out model files from workspace index
return implementations.filter((impl) => !impl.includes('models'));
},
schemas(schemas) {
// Filter schemas to include in index
return schemas.filter((schema) => schema.name !== 'Error');
},
},
},
},
};
```

The `workspace` function receives an array of implementation paths and should return the filtered/augmented array of paths to include in the workspace index file.

The `schemas` function receives an array of schema objects and should return the filtered array of schemas to include in the schemas index file.

### title

Type: `String` or `Function`.
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type NormalizedOutputOptions = {
tsconfig?: Tsconfig;
packageJson?: PackageJson;
headers: boolean;
indexFiles: boolean;
indexFiles: NormalizedOutputIndexFiles;
baseUrl?: string | BaseUrlFromSpec | BaseUrlFromConstant;
allParamsOptional: boolean;
urlEncodeParameters: boolean;
Expand Down Expand Up @@ -163,6 +163,8 @@ export type NormalizedInputOptions = {
filters?: InputFiltersOption;
};

export type NormalizedOutputIndexFiles = false | Required<OutputIndexFiles>;

export type OutputClientFunc = (
clients: GeneratorClients,
) => ClientGeneratorsBuilder;
Expand Down Expand Up @@ -230,7 +232,7 @@ export type OutputOptions = {
tsconfig?: string | Tsconfig;
packageJson?: string;
headers?: boolean;
indexFiles?: boolean;
indexFiles?: boolean | OutputIndexFiles;
baseUrl?: string | BaseUrlFromSpec | BaseUrlFromConstant;
allParamsOptional?: boolean;
urlEncodeParameters?: boolean;
Expand Down Expand Up @@ -291,6 +293,11 @@ export const OutputMode = {

export type OutputMode = (typeof OutputMode)[keyof typeof OutputMode];

export interface OutputIndexFiles {
workspace?: (implementations: string[]) => string[];
schemas?: (schemas: GeneratorSchema[]) => GeneratorSchema[];
}

export type OutputDocsOptions = {
configPath?: string;
} & Partial<TypeDocOptions>;
Expand Down Expand Up @@ -887,6 +894,7 @@ export type ClientBuilder = (
export type ClientFileBuilder = {
path: string;
content: string;
exposeIndexFile?: boolean;
};
export type ClientExtraFilesBuilder = (
verbOptions: Record<string, GeneratorVerbOptions>,
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/writers/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import fs from 'fs-extra';
import { generateImports } from '../generators';
import { GeneratorSchema, NamingConvention } from '../types';
import {
GeneratorSchema,
NamingConvention,
NormalizedOutputIndexFiles,
} from '../types';
import { upath, conventionName } from '../utils';

const getSchema = ({
Expand Down Expand Up @@ -109,7 +113,7 @@ export const writeSchemas = async ({
isRootKey: boolean;
specsName: Record<string, string>;
header: string;
indexFiles: boolean;
indexFiles: NormalizedOutputIndexFiles;
}) => {
await Promise.all(
schemas.map((schema) =>
Expand Down Expand Up @@ -164,7 +168,8 @@ export const writeSchemas = async ({
? fileExtension.slice(0, -3)
: fileExtension;

const importStatements = schemas
const importStatements = indexFiles
.schemas(schemas)
.filter((schema) => {
const name = conventionName(schema.name, namingConvention);

Expand Down
24 changes: 22 additions & 2 deletions packages/orval/src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ConfigExternal,
createLogger,
FormDataArrayHandling,
GeneratorSchema,
GlobalMockOptions,
GlobalOptions,
HonoOptions,
Expand All @@ -24,14 +25,15 @@ import {
NormalizedMutator,
NormalizedOperationOptions,
NormalizedOptions,
NormalizedOutputIndexFiles,
NormalizedOverrideOutput,
NormalizedQueryOptions,
OperationOptions,
OptionsExport,
OutputClient,
OutputHttpClient,
OutputIndexFiles,
OutputMode,
OutputOptions,
OverrideOutput,
PropertySortOrder,
QueryOptions,
Expand Down Expand Up @@ -196,7 +198,7 @@ export const normalizeOptions = async (
tsconfig,
packageJson,
headers: outputOptions.headers ?? false,
indexFiles: outputOptions.indexFiles ?? true,
indexFiles: normalizeIndexFiles(outputOptions.indexFiles),
baseUrl: outputOptions.baseUrl,
unionAddMissingProperties:
outputOptions.unionAddMissingProperties ?? false,
Expand Down Expand Up @@ -430,6 +432,24 @@ export const normalizePath = <T>(path: T, workspace: string) => {
return upath.resolve(workspace, path);
};

const normalizeIndexFiles = (
indexFiles: OutputIndexFiles | boolean | undefined = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to have the default value of the param here

): NormalizedOutputIndexFiles => {
if (isBoolean(indexFiles)) {
return indexFiles
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a const defaultIndexFiles or similar and use it here and in the case where indexFiles is not a boolean

? {
workspace: (implementations: string[]) => implementations,
schemas: (schemas: GeneratorSchema[]) => schemas,
}
: false;
}
return {
workspace: (implementations: string[]) => implementations,
schemas: (schemas: GeneratorSchema[]) => schemas,
...indexFiles,
Copy link
Contributor

@AllieJonsson AllieJonsson Apr 25, 2025

Choose a reason for hiding this comment

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

If a user puts

{
  schemas: undefined
}

in the config, we will get undefined here. I'd suggest doing something like

const defaultIndex = {
  workspace: (implementations: string[]) => implementations,
  schemas: (schemas: GeneratorSchema[]) => schemas,
};

return {
  workspace: indexFiles.workspace ?? defaultIndex.workspace,
  schemas: indexFiles.schemas ?? defaultIndex.schemas,
};

};
};

const normalizeOperationsAndTags = (
operationsOrTags: {
[key: string]: OperationOptions;
Expand Down
20 changes: 18 additions & 2 deletions packages/orval/src/write-specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,28 @@ export const writeSpecs = async (
);
}

const exposedFiles = builder.extraFiles.filter(
(file) => file.exposeIndexFile,
);
if (exposedFiles.length) {
imports.push(
...exposedFiles.map((file) =>
upath.relativeSafe(
workspacePath,
getFileInfo(file.path).pathWithoutExtension,
),
),
);
}

if (output.indexFiles) {
const indexFile = upath.join(workspacePath, '/index.ts');

if (await fs.pathExists(indexFile)) {
const data = await fs.readFile(indexFile, 'utf8');
const importsNotDeclared = imports.filter((imp) => !data.includes(imp));
const importsNotDeclared = output.indexFiles
.workspace(imports)
.filter((imp) => !data.includes(imp));
await fs.appendFile(
indexFile,
uniq(importsNotDeclared)
Expand All @@ -139,7 +155,7 @@ export const writeSpecs = async (
} else {
await fs.outputFile(
indexFile,
uniq(imports)
uniq(output.indexFiles.workspace(imports))
.map((imp) => `export * from '${imp}';`)
.join('\n') + '\n',
);
Expand Down
62 changes: 62 additions & 0 deletions tests/configs/multi-client.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { defineConfig } from 'orval';

export default defineConfig({
reactQuery: {
input: '../specifications/petstore.yaml',
output: {
client: 'react-query',
mode: 'tags-split',
schemas: '../models',
target: './will-not-exists.ts',
workspace: '../generated/multi-client/react-query',
indexFiles: {
workspace(implementations) {
return implementations.filter((impl) => !impl.includes('models'));
},
},
},
},
zod: {
input: '../specifications/petstore.yaml',
output: {
client({ zod }) {
return {
title: zod.title,
footer: zod.footer,
client: zod.client,
header: zod.header,
dependencies: zod.dependencies,
async extraFiles(verbOptions, output, context) {
return [
...(zod.extraFiles
? await zod.extraFiles(verbOptions, output, context)
: []),
{
path: context.output.workspace + '/extrafiles/index.ts',
content: 'export * from "./file1";\nexport * from "./file2";\n',
exposeIndexFile: true,
},
{
path: context.output.workspace + '/extrafiles/file1.ts',
content: 'export const extraFile1 = "extraFile1"',
},
{
path: context.output.workspace + '/extrafiles/file2.ts',
content: 'export const extraFile2 = "extraFile2"',
},
];
},
};
},
mode: 'tags-split',
schemas: '../models',
target: './will-not-exists.ts',
workspace: '../generated/multi-client/zod',
indexFiles: {
workspace(implementations) {
return implementations.filter((impl) => !impl.includes('models'));
},
},
},
},
});
1 change: 1 addition & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"generate:fetch": "yarn orval --config ./configs/fetch.config.ts",
"generate:mcp": "yarn orval --config ./configs/mcp.config.ts",
"generate:hono": "rm -rf generated/hono && yarn orval --config ./configs/hono.config.ts",
"generate:multi-client": "yarn orval --config ./configs/multi-client.config.ts",
"build": "tsc"
},
"author": "Victor Bury",
Expand Down