Commit 162a96c
authored
feat(schemas): add splitByTags option to organize schemas by tag (#3595)
* feat(schemas): add splitByTags option to organize schemas by tag
Adds schemas.splitByTags and schemas.sharedDirName options that organize
generated schema files into per-tag subdirectories when using
output.mode: 'tags-split'. Schemas referenced by multiple tags are placed
in a shared directory (default '_shared').
Closes #3592
* fix(schemas): allow splitByTags with schemas.importPath
The importPath guard was unnecessary — when indexFiles is true (the
default), operation and factory files import schemas through the barrel
index, which re-exports tag subdirectories. The importPath option just
replaces the barrel specifier (e.g. @acme/models instead of ../model).
* refactor(types): make splitByTags required in NormalizedSchemaOptions
normalizeSchemasOption always materializes splitByTags as a boolean, so
the optional designation weakened type safety for downstream consumers.
* feat(schemas): support splitByTags with zod schemas
Thread schemaTagMap through writeZodSchemas, writeZodSchemasReusable,
and writeZodSchemasFromVerbs so zod schemas are organized into per-tag
subdirectories the same way TypeScript schemas are.
- buildSiblingImports computes cross-directory paths when schemaTagMap is set
- Verb schemas are routed to their operation's tag directory
- writeZodSchemaTagsSplitBarrel writes per-tag + root barrel indexes
- Remove zod rejection guard from write-specs.ts
* fix(zod): use getImportExtension for NodeNext in write-zod-specs
Replace fileExtension.replace(/\.ts$/, '') with getImportExtension at
all 4 remaining sites in write-zod-specs.ts:
- writeZodSchemaIndex: add tsconfig param, use getImportExtension
- writeZodSchemaTagsSplitBarrel: fix importExt (was using old pattern
alongside getImportExtension for indexImportExt in the same function)
- writeZodSchemasReusable: use output.tsconfig
- writeZodSchemasFromVerbs: use output.tsconfig
Add tsconfig?: Tsconfig to WriteZodOutputOptions interface and pass
output.tsconfig from all writeZodSchemaIndex call sites.
Aligns with #3603 which applies the same fix to the non-zod writers.
* refactor(hono): pass full handler file path to generateModuleSpecifier
Construct the complete handler file path (including tag name and extension)
upfront and pass it to generateModuleSpecifier, instead of passing only the
directory and manually appending the filename and extension afterward.
Matches the pattern already used by the per-operation handler block above.
* fix(hono): construct composite route handler imports per output mode
Handler files are written flat (tag.handlers.ts) in tags mode but in a
subdirectory (tag/tag.handlers.ts) in tags-split mode. The composite route
import construction assumed tags-split layout unconditionally, producing
wrong import paths for tags mode.
* fix(zod): handle ../ mutator paths + cleanup test fixtures + import SHARED_DIR
- adjustMutatorPathForDir: prepend ../ to ../-prefixed mutator paths to
account for tag subdirectory depth (previously only handled ./-prefixed)
- schemas-tags-split: import SHARED_DIR from schema-tag-mapper instead of
redefining as local ROOT constant (single source of truth)
- schemas-tags-split.test: centralize temp dir cleanup in afterEach hook
so cleanup runs even when assertions throw
- schema-tag-mapper.test: add regression test verifying imports are matched
by GeneratorImport.name (TS identifier) not schemaName
* test(schemas): add #3592 splitByTags regression tests
Pin the end-to-end behaviors of schemas.splitByTags that the snapshot
suite alone cannot express intent for:
- tags-split + splitByTags places per-tag schemas under <tag>/ subdirs
with their own barrels, while cross-tag-shared schemas stay at the
model root. Pagination is the interesting case: shared indirectly via
PetList and StoreList, both tag-scoped.
- Endpoint and faker files import via the '../model' barrel rather than
reaching into a specific tag subdir, keeping tag files agnostic to
where a schema physically lives.
- mode: 'split' + splitByTags produces the same per-tag layout; the
combined endpoints.ts imports every cross-tag schema from the single
'./model' barrel.
Mirrors the style of the #3596 regression tests added in 76de240.
* fix(schemas): reject splitByTags + indexFiles:false
When schemas are split by tag and there is no root barrel, operation
files (pets/pets.ts) and the consolidated <schemas>/index.faker.ts file
both emit schema imports that don't resolve. Operation files import
'../model/pet' when the file lives at '../model/pets/pet.ts'; the faker
factory file imports types from '.' when no root index.ts exists.
The root cause is that generateImportsForBuilder (used by every mode
writer) and writeFakerSchemaMocks both assume a flat schemas directory
or a root barrel. Neither consults the schema-to-tag map computed by
writeSchemasTagsSplit.
Reject the combination at config normalization time until the import
resolvers are taught to honor splitByTags directly. Tracked as a
follow-up.
- packages/orval/src/utils/options.ts: throw when
schemas.splitByTags + !indexFiles.
- packages/orval/src/utils/options.test.ts: rejection test plus two
positive cases (splitByTags + indexFiles:true accepted; indexFiles:false
alone accepted).
- docs/content/docs/reference/configuration/output.mdx: note the
indexFiles requirement under splitByTags.
* fix(schemas): support splitByTags with indexFiles:false
When schemas are split by tag and there is no root barrel, operation
files (pets/pets.ts) and the consolidated <schemas>/index.faker.ts file
both previously emitted schema imports that didn't resolve. Operation
files imported '../model/pet' when the file lived at
'../model/pets/pet.ts'; the faker factory file imported types from '.'
when no root index.ts existed.
The root cause was that generateImportsForBuilder (used by every mode
writer) and writeFakerSchemaMocks both assumed a flat schemas directory
or a root barrel. Neither consulted the schema-to-tag map computed by
writeSchemasTagsSplit.
Compute the schema-to-tag map once in writeSpecs and thread it through:
- WriteModeProps: new optional schemaTagMap field
- generateImportsForBuilder: optional schemaTagMap param; the
indexFiles:false branch routes each import into its tag subdir
(or keeps shared schemas at the schemas root)
- single-mode / split-mode / tags-mode / split-tags-mode: forward
schemaTagMap at all 13 generateImportsForBuilder call sites
- writeFakerSchemaMocks: same per-schema routing for the consolidated
index.faker.ts file under indexFiles:false
Removes the validation guard that previously rejected the combination,
since the underlying import resolution now handles it correctly.
- packages/core/src/types.ts: add schemaTagMap? to WriteModeProps
- packages/core/src/writers/generate-imports-for-builder.ts: optional
schemaTagMap param + tag-aware routing in indexFiles:false branch
- packages/core/src/writers/{single,split,tags,split-tags}-mode.ts:
destructure schemaTagMap from WriteModeProps, forward to call sites
- packages/orval/src/write-specs.ts: hoist schemaTagMap computation to
writeSpecs scope; thread to writeFakerSchemaMocks and writeMode;
per-schema routing in writeFakerSchemaMocks under indexFiles:false
- packages/orval/src/utils/options.ts: remove the guard
- packages/orval/src/utils/options.test.ts: remove guard tests
- packages/core/src/writers/generate-imports-for-builder.test.ts: add
splitByTags: false to two #3618 schemasImportPath test cases that
now require it (those test cases predate splitByTags becoming
required in NormalizedSchemaOptions)
- docs/content/docs/reference/configuration/output.mdx: drop the
indexFiles:true requirement note under splitByTags
- tests/configs/axios.config.ts: new splitByTagsFakerSchemasNoIndex
config exercising the previously-broken combination
- tests/__snapshots__/axios/split-by-tags-faker-schemas-no-index/:
snapshots for the new config
- tests/api-generation.spec.ts: 3 regression tests pinning per-tag
import paths for operation files, faker factory file, and tag
isolation
* fix(schemas): route splitByTags imports by TS identifier, not schemaName
generateImportsForBuilder's splitByTags tag lookup was using
`baseName` (which prefers `schemaImport.schemaName` over `.name`)
to key into the schema-to-tag map. The map is built by
`buildSchemaTagMap`, which keys exclusively on `schema.name` — the
pascal-cased TS identifier produced by `getRefInfo`. When an import's
`schemaName` differs from its TS `name` (e.g. `PetSchema` vs
`Pet`), the lookup silently missed and the import was placed at the
schemas root instead of its tag subdirectory.
Use `schemaImport.name` directly for the tag lookup. The filename
computation still uses `baseName` (preferring `schemaName`), which
is unchanged from the existing flat-layout behavior — `conventionName`
is idempotent on already-pascal-cased input in the common case.
- packages/core/src/writers/generate-imports-for-builder.ts: lookup by
`schemaImport.name`; clarify the comment.
- packages/core/src/writers/generate-imports-for-builder.test.ts: two
new tests pinning tag routing — one with `name`/`schemaName`
differing to exercise the lookup, one with a missing entry to
exercise the root fallback.
* fix(schemas): address review findings on cleanup hook and dirSchemas skip
Two issues found in review:
1. `schemas-tags-split.test.ts`: the outer-scope `dir` was shadowed by
per-test `const dir` declarations, so the `afterEach` cleanup hook
never removed temp directories. Replaced `const dir = await tmpDir()`
with `dir = await tmpDir()` so the outer variable is populated.
2. `write-zod-specs.ts`: the dirSchemas loop in `writeZodSchemasFromVerbs`
iterated `uniqueVerbsSchemas` directly, but the writing loop above
skips pure-$ref wrappers via `continue` when `useReusableSchemas`
is on. Skipped entries still landed in dirSchemas, so the tag barrel
re-exported files that were never written. Replicated the skip
condition in the dirSchemas loop with a comment explaining why.
* test(schemas): prune redundant splitByTags config and fold symmetry test
`petstoreTagsSplitSchemas` generated 27 snapshot files but no test in
`api-generation.spec.ts` actually inspected any of them — the cross-
tag split logic was already covered by `tagsSplitSharedModels` with
explicit assertions. Removed the config block and its snapshots.
Also folded the standalone 'stores files do not import peer tag
subdirectories' test into the operation-imports test as additional
assertions — same coverage, one less test.
Updated existing snapshots for the v8.18.0 version bump brought in by
the master rebase.
* test(schemas): complete unresolved .resolves assertions in splitByTags test
Three `await expect(readFile(...)).resolves;` chains had no trailing
matcher, so the promise was awaited but no assertion ran. The file-
exists intent only surfaced via promise rejection. Added
`.toBeDefined()` to make the assertion explicit.
The broader readFile-vs-pathExists inconsistency is tracked in #3623.1 parent 60b84f2 commit 162a96c
108 files changed
Lines changed: 3904 additions & 77 deletions
File tree
- docs/content/docs/reference/configuration
- packages
- core/src
- utils
- writers
- hono/src
- orval/src
- utils
- tests
- __snapshots__/axios
- petstore-tags-split-zod-reusable
- model
- pets
- stores
- pets
- stores
- petstore-tags-split-zod-schemas
- health
- model
- pets
- pets
- split-by-tags-faker-schemas-no-index
- model
- pets
- stores
- pets
- stores
- split-mode-split-schemas
- model
- pets
- stores
- configs
- specifications
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
| |||
173 | 174 | | |
174 | 175 | | |
175 | 176 | | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
176 | 228 | | |
177 | 229 | | |
178 | 230 | | |
| |||
352 | 404 | | |
353 | 405 | | |
354 | 406 | | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
355 | 429 | | |
356 | 430 | | |
357 | 431 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
314 | 322 | | |
315 | 323 | | |
316 | 324 | | |
317 | 325 | | |
318 | 326 | | |
319 | 327 | | |
| 328 | + | |
320 | 329 | | |
321 | 330 | | |
322 | 331 | | |
| |||
1783 | 1792 | | |
1784 | 1793 | | |
1785 | 1794 | | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
1786 | 1803 | | |
1787 | 1804 | | |
1788 | 1805 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
18 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
19 | 24 | | |
20 | 25 | | |
21 | 26 | | |
| |||
Lines changed: 54 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| |||
184 | 184 | | |
185 | 185 | | |
186 | 186 | | |
187 | | - | |
| 187 | + | |
188 | 188 | | |
189 | 189 | | |
190 | 190 | | |
| |||
208 | 208 | | |
209 | 209 | | |
210 | 210 | | |
| 211 | + | |
211 | 212 | | |
212 | 213 | | |
213 | 214 | | |
| |||
230 | 231 | | |
231 | 232 | | |
232 | 233 | | |
| 234 | + | |
233 | 235 | | |
234 | 236 | | |
235 | 237 | | |
| |||
262 | 264 | | |
263 | 265 | | |
264 | 266 | | |
| 267 | + | |
265 | 268 | | |
266 | 269 | | |
267 | 270 | | |
| |||
284 | 287 | | |
285 | 288 | | |
286 | 289 | | |
| 290 | + | |
287 | 291 | | |
288 | 292 | | |
289 | 293 | | |
| |||
306 | 310 | | |
307 | 311 | | |
308 | 312 | | |
| 313 | + | |
309 | 314 | | |
310 | 315 | | |
311 | 316 | | |
| |||
330 | 335 | | |
331 | 336 | | |
332 | 337 | | |
| 338 | + | |
333 | 339 | | |
334 | 340 | | |
335 | 341 | | |
| |||
368 | 374 | | |
369 | 375 | | |
370 | 376 | | |
| 377 | + | |
371 | 378 | | |
372 | 379 | | |
373 | 380 | | |
| |||
426 | 433 | | |
427 | 434 | | |
428 | 435 | | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
429 | 480 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
23 | 28 | | |
24 | 29 | | |
25 | 30 | | |
| |||
93 | 98 | | |
94 | 99 | | |
95 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
96 | 112 | | |
97 | 113 | | |
98 | | - | |
| 114 | + | |
99 | 115 | | |
100 | 116 | | |
101 | 117 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
| 4 | + | |
3 | 5 | | |
4 | 6 | | |
5 | 7 | | |
| |||
0 commit comments