diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index aa3f7a0c5..5e18fb13d 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -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`. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9cb82ec3b..42351ef2b 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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; @@ -163,6 +163,8 @@ export type NormalizedInputOptions = { filters?: InputFiltersOption; }; +export type NormalizedOutputIndexFiles = false | Required; + export type OutputClientFunc = ( clients: GeneratorClients, ) => ClientGeneratorsBuilder; @@ -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; @@ -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; @@ -887,6 +894,7 @@ export type ClientBuilder = ( export type ClientFileBuilder = { path: string; content: string; + exposeIndexFile?: boolean; }; export type ClientExtraFilesBuilder = ( verbOptions: Record, diff --git a/packages/core/src/writers/schemas.ts b/packages/core/src/writers/schemas.ts index 854db0b5c..f0f242ff7 100644 --- a/packages/core/src/writers/schemas.ts +++ b/packages/core/src/writers/schemas.ts @@ -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 = ({ @@ -109,7 +113,7 @@ export const writeSchemas = async ({ isRootKey: boolean; specsName: Record; header: string; - indexFiles: boolean; + indexFiles: NormalizedOutputIndexFiles; }) => { await Promise.all( schemas.map((schema) => @@ -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); diff --git a/packages/orval/src/utils/options.ts b/packages/orval/src/utils/options.ts index 471468e07..e72ae11ab 100644 --- a/packages/orval/src/utils/options.ts +++ b/packages/orval/src/utils/options.ts @@ -3,6 +3,7 @@ import { ConfigExternal, createLogger, FormDataArrayHandling, + GeneratorSchema, GlobalMockOptions, GlobalOptions, HonoOptions, @@ -24,14 +25,15 @@ import { NormalizedMutator, NormalizedOperationOptions, NormalizedOptions, + NormalizedOutputIndexFiles, NormalizedOverrideOutput, NormalizedQueryOptions, OperationOptions, OptionsExport, OutputClient, OutputHttpClient, + OutputIndexFiles, OutputMode, - OutputOptions, OverrideOutput, PropertySortOrder, QueryOptions, @@ -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, @@ -430,6 +432,24 @@ export const normalizePath = (path: T, workspace: string) => { return upath.resolve(workspace, path); }; +const normalizeIndexFiles = ( + indexFiles: OutputIndexFiles | boolean | undefined = true, +): NormalizedOutputIndexFiles => { + if (isBoolean(indexFiles)) { + return indexFiles + ? { + workspace: (implementations: string[]) => implementations, + schemas: (schemas: GeneratorSchema[]) => schemas, + } + : false; + } + return { + workspace: (implementations: string[]) => implementations, + schemas: (schemas: GeneratorSchema[]) => schemas, + ...indexFiles, + }; +}; + const normalizeOperationsAndTags = ( operationsOrTags: { [key: string]: OperationOptions; diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index dd15e81a5..7ab75c10c 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -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) @@ -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', ); diff --git a/tests/configs/multi-client.config.ts b/tests/configs/multi-client.config.ts new file mode 100644 index 000000000..3d1ae7613 --- /dev/null +++ b/tests/configs/multi-client.config.ts @@ -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')); + }, + }, + }, + }, +}); diff --git a/tests/package.json b/tests/package.json index 1856d2b03..c4b3db5d2 100644 --- a/tests/package.json +++ b/tests/package.json @@ -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",