Skip to content
Merged
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
50 changes: 49 additions & 1 deletion docs/content/docs/reference/configuration/output.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ import statements change.
package must also expose `./pet.zod` (e.g., `@acme/models/pet.zod`).
- If using faker schema factories
(`mock: { generators: [{ type: 'faker', schemas: true }] }`),
the package must also export `./index.faker`.
the package must also export `./index.faker`. When the package can't expose a
sub-path (e.g. `importPath` resolves to a single barrel file via tsconfig
path mappings), set [`schemasImportPath`](#schemasimportpath) on the faker
generator to point faker factories at a separate import path.
- If using factory methods (`factoryMethods`), each schema is imported
individually regardless of `indexFiles`.
- When `importPath` is set, the relative-path computation in
Expand Down Expand Up @@ -558,10 +561,55 @@ The Faker generator emits the same `get<Op>ResponseMock` factories MSW would emi
|--------|------|---------|-------------|
| `type` | `'faker'` | required | Discriminator for Faker-only output. |
| `path` | `String` | `undefined` | Output directory for this generator's mock files. Overrides the shared `mock.path` when set. When provided in `single` or `tags` mode, mock code is written to separate files (relative to `path`) instead of being inlined into the implementation file. |
| `schemas` | `Boolean` | `false` | Emit a consolidated mock factory file (`get<SchemaName>Mock`) for every entry under `components/schemas`. |
| `schemasImportPath` | `String` | `undefined` | Package specifier for importing the schema-level faker factories emitted by `schemas: true` (e.g. `@acme/models/fakers`). When set, used verbatim instead of appending `/index.faker` to `schemas.importPath` — useful when the production barrel can't expose a sub-path export. Requires `schemas: true` and `schemas.importPath`. Only applies when `schemas: true` is set on the same generator. |
| `operationResponses` | `Boolean` | `true` | Emit per-operation response mock factories (the historical behavior). Set to `false` together with `schemas: true` to get only the consolidated schema factories. |
| `useExamples` | `Boolean` | `false` | Use OpenAPI examples to seed response values. |
| `generateEachHttpStatus` | `Boolean` | `false` | Generate response factories for every documented status code. |
| `locale` | `String` | `'en'` | Faker.js locale. |
| `preferredContentType` | `String` | `undefined` | Preferred content type when an operation lists more than one. |
| `arrayItems` | `Boolean` | `false` | Emit reusable mock factories for object-like array item schemas in operation responses. |

#### `schemasImportPath`

Only applies when `schemas: true` is set on the same faker generator (requires
both `schemas: true` and `schemas.importPath`). When `schemas.importPath`
resolves to a single barrel file (e.g. via tsconfig path mappings), appending
`/index.faker` produces an unresolvable sub-path. `schemasImportPath` lets you
point faker factories at a separate import path so you can expose them through a
dedicated barrel:

```ts title="orval.config.ts"
export default defineConfig({
petstore: {
output: {
target: './libs/client/sdk/generated',
schemas: {
path: './libs/data-layer/sdk/generated',
importPath: '@acme/data-layer/sdk',
},
mock: {
path: './libs/client/sdk/mocks',
generators: [
{
type: 'faker',
schemas: true,
schemasImportPath: '@acme/data-layer/sdk/fakers',
},
],
},
},
},
});
```

```ts
// Without schemasImportPath (default — joins importPath with /index.faker):
import { getPetMock } from '@acme/data-layer/sdk/index.faker'; // may not resolve

// With schemasImportPath: '@acme/data-layer/sdk/fakers':
import { getPetMock } from '@acme/data-layer/sdk/fakers';
```

## indexFiles

Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,13 @@ export interface FakerMockOptions extends CommonMockOptions {
// `components/schemas` (one `get<SchemaName>Mock` per schema). Defaults to
// `false` — schema factories are opt-in to preserve existing output.
schemas?: boolean;
// Package specifier for importing the schema-level faker factories (the
// `get<SchemaName>Mock` functions emitted when `schemas: true`). When set,
// it is used verbatim as the schema factory import path instead of appending
// `/index.faker` to `schemas.importPath`. This lets consumers expose fakers
// through a dedicated barrel separate from the production type barrel.
// Requires `schemas.importPath` to also be set.
schemasImportPath?: string;
// Emit per-operation response mock factories (the historical behavior).
// Defaults to `true`. Set to `false` together with `schemas: true` to get
// only the consolidated schema factories.
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/writers/generate-imports-for-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,72 @@ describe('generateImportsForBuilder', () => {
},
]);
});

it('should use faker schemasImportPath verbatim for schemaFactory imports', () => {
const output = createMockOutput({
indexFiles: false,
fileExtension: '.ts',
schemas: {
path: '/libs/models',
type: 'typescript',
importPath: '@acme/models',
},
mock: {
indexMockFiles: false,
generators: [
{
type: 'faker',
schemas: true,
schemasImportPath: '@acme/models/fakers',
},
],
},
});
const imports: GeneratorImport[] = [
{ name: 'createUser', schemaFactory: true },
{ name: 'createPet', schemaFactory: true },
];

const result = generateImportsForBuilder(output, imports, '@acme/models');

expect(result).toEqual([
{
exports: [
{ name: 'createUser', schemaFactory: true },
{ name: 'createPet', schemaFactory: true },
],
dependency: '@acme/models/fakers',
},
]);
});

it('should fall back to index.faker join when schemasImportPath is not set', () => {
const output = createMockOutput({
indexFiles: false,
fileExtension: '.ts',
schemas: {
path: '/libs/models',
type: 'typescript',
importPath: '@acme/models',
},
mock: {
indexMockFiles: false,
generators: [{ type: 'faker', schemas: true }],
},
});
const imports: GeneratorImport[] = [
{ name: 'createUser', schemaFactory: true },
];

const result = generateImportsForBuilder(output, imports, '@acme/models');

expect(result).toEqual([
{
exports: [{ name: 'createUser', schemaFactory: true }],
dependency: '@acme/models/index.faker',
},
]);
});
});

describe('naming conventions', () => {
Expand Down
56 changes: 47 additions & 9 deletions packages/core/src/writers/generate-imports-for-builder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { uniqueBy } from 'remeda';

import type {
GeneratorDependency,
GeneratorImport,
NormalizedOutputOptions,
import {
type FakerMockOptions,
type GeneratorDependency,
type GeneratorImport,
OutputMockType,
type NormalizedMocksConfig,
type NormalizedOutputOptions,
} from '../types';
import { conventionName, getImportExtension, isObject, upath } from '../utils';
import {
conventionName,
getImportExtension,
isFunction,
isObject,
upath,
} from '../utils';

export function generateImportsForBuilder(
output: NormalizedOutputOptions,
Expand All @@ -27,6 +36,19 @@ export function generateImportsForBuilder(
const schemaFactoryImportExtension = isPackageImport
? ''
: getImportExtension(output.fileExtension, output.tsconfig);

// When the faker generator configures a dedicated `schemasImportPath`, use
// it verbatim. This is needed because `schemas.importPath` is a package
// barrel specifier (e.g. `@acme/models`) that may resolve to a single file
// via tsconfig path mappings — appending `/index.faker` produces an
// unresolvable sub-path in that case.
const schemaFactoryDependency =
getFakerSchemasImportPath(output.mock) ??
upath.joinSafe(
relativeSchemasPath,
`index.faker${schemaFactoryImportExtension}`,
);

const schemaFactoryDeps: GeneratorDependency[] =
schemaFactoryImports.length > 0
? [
Expand All @@ -35,10 +57,7 @@ export function generateImportsForBuilder(
schemaFactoryImports,
(entry) => `${entry.name}|${entry.alias ?? ''}`,
),
dependency: upath.joinSafe(
relativeSchemasPath,
`index.faker${schemaFactoryImportExtension}`,
),
dependency: schemaFactoryDependency,
},
]
: [];
Expand Down Expand Up @@ -113,3 +132,22 @@ export function generateImportsForBuilder(

return [...schemaImports, ...schemaFactoryDeps, ...otherImports];
}

/**
* Extracts the faker generator's `schemasImportPath` from the normalized mock
* config, if one is configured. Returns `undefined` when there is no faker
* generator with schema factories enabled, or when `schemasImportPath` is not
* set.
*/
function getFakerSchemasImportPath(
mock: NormalizedMocksConfig | undefined,
): FakerMockOptions['schemasImportPath'] | undefined {
if (!mock) {
return undefined;
}
const faker = mock.generators.find(
(g): g is FakerMockOptions =>
!isFunction(g) && g.type === OutputMockType.FAKER && g.schemas === true,
);
return faker?.schemasImportPath;
}
Loading