fork of hey-api/openapi-ts because I need some additional things

Merge pull request #433 from hey-api/feat/schemas-exports

feat: add form type option for schemas

authored by

Lubos and committed by
GitHub
a15612da 33adebc7

+1684 -23
+5
.changeset/quiet-seals-pay.md
··· 1 + --- 2 + "@hey-api/openapi-ts": minor 3 + --- 4 + 5 + feat: add form type option for schemas
+13 -1
docs/openapi-ts/configuration.md
··· 174 174 } 175 175 ``` 176 176 177 - If you're using OpenAPI v3.1, your schemas are JSON Schema compliant and can be used with any other tools supporting JSON Schema. However, if you don't need schemas at all, you can disable them with 177 + If you're using OpenAPI v3.1, your schemas are JSON Schema compliant and can be used with other tools supporting JSON Schema. However, if you only want to validate form input, you don't want to include string descriptions inside your bundle. Instead, use `form` type. 178 + 179 + ```js{5} 180 + export default { 181 + input: 'path/to/openapi.json', 182 + output: 'src/client', 183 + schemas: { 184 + type: 'form' 185 + }, 186 + } 187 + ``` 188 + 189 + If you don't need schemas at all, you can disable them with 178 190 179 191 ```js{4} 180 192 export default {
+17 -1
packages/openapi-ts/src/index.ts
··· 88 88 } 89 89 }; 90 90 91 + const getSchemas = (userConfig: UserConfig): Config['schemas'] => { 92 + let schemas: Config['schemas'] = { 93 + export: true, 94 + type: 'json', 95 + }; 96 + if (typeof userConfig.schemas === 'boolean') { 97 + schemas.export = userConfig.schemas; 98 + } else { 99 + schemas = { 100 + ...schemas, 101 + ...userConfig.schemas, 102 + }; 103 + } 104 + return schemas; 105 + }; 106 + 91 107 const getServices = (userConfig: UserConfig): Config['services'] => { 92 108 let services: Config['services'] = { 93 109 export: true, ··· 152 168 name, 153 169 operationId = true, 154 170 request, 155 - schemas = true, 156 171 serviceResponse = 'body', 157 172 useDateType = false, 158 173 useOptions = true, ··· 182 197 183 198 const client = userConfig.client || inferClient(dependencies); 184 199 const output = path.resolve(process.cwd(), userConfig.output); 200 + const schemas = getSchemas(userConfig); 185 201 const services = getServices(userConfig); 186 202 const types = getTypes(userConfig); 187 203
+6 -2
packages/openapi-ts/src/openApi/common/parser/__tests__/operation.spec.ts
··· 19 19 lint: false, 20 20 operationId: true, 21 21 output: '', 22 - schemas: false, 22 + schemas: { 23 + export: false, 24 + }, 23 25 serviceResponse: 'body', 24 26 services: { 25 27 export: false, ··· 42 44 lint: false, 43 45 operationId: false, 44 46 output: '', 45 - schemas: false, 47 + schemas: { 48 + export: false, 49 + }, 46 50 serviceResponse: 'body', 47 51 services: { 48 52 export: false,
+1 -1
packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts
··· 16 16 lint: false, 17 17 operationId: false, 18 18 output: '', 19 - schemas: true, 19 + schemas: {}, 20 20 serviceResponse: 'body', 21 21 services: {}, 22 22 types: {},
+1 -1
packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts
··· 16 16 lint: false, 17 17 operationId: true, 18 18 output: '', 19 - schemas: true, 19 + schemas: {}, 20 20 serviceResponse: 'body', 21 21 services: {}, 22 22 types: {},
+19 -3
packages/openapi-ts/src/types/config.ts
··· 60 60 */ 61 61 request?: string; 62 62 /** 63 - * Export JSON schemas? 63 + * Generate JSON schemas? 64 64 * @default true 65 65 */ 66 - schemas?: boolean; 66 + schemas?: 67 + | boolean 68 + | { 69 + /** 70 + * Generate JSON schemas? 71 + * @default true 72 + */ 73 + export?: boolean; 74 + /** 75 + * Choose schema type to generate. Select 'form' if you don't want 76 + * descriptions to reduce bundle size and you plan to use schemas 77 + * for form validation 78 + * @default 'json' 79 + */ 80 + type?: 'form' | 'json'; 81 + }; 67 82 /** 68 83 * Define shape of returned value from service calls 69 84 * @default 'body' ··· 129 144 130 145 export type Config = Omit< 131 146 Required<UserConfig>, 132 - 'base' | 'name' | 'request' | 'services' | 'types' 147 + 'base' | 'name' | 'request' | 'schemas' | 'services' | 'types' 133 148 > & 134 149 Pick<UserConfig, 'base' | 'name' | 'request'> & { 150 + schemas: Extract<Required<UserConfig>['schemas'], object>; 135 151 services: Extract<Required<UserConfig>['services'], object>; 136 152 types: Extract<Required<UserConfig>['types'], object>; 137 153 };
+2 -2
packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts
··· 20 20 lint: false, 21 21 operationId: true, 22 22 output: '', 23 - schemas: true, 23 + schemas: {}, 24 24 serviceResponse: 'body', 25 25 services: {}, 26 26 types: {}, ··· 50 50 lint: false, 51 51 operationId: true, 52 52 output: '', 53 - schemas: true, 53 + schemas: {}, 54 54 serviceResponse: 'body', 55 55 services: {}, 56 56 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/class.spec.ts
··· 23 23 name: 'AppClient', 24 24 operationId: true, 25 25 output: '', 26 - schemas: true, 26 + schemas: {}, 27 27 serviceResponse: 'body', 28 28 services: {}, 29 29 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/client.spec.ts
··· 22 22 lint: false, 23 23 operationId: true, 24 24 output: './dist', 25 - schemas: true, 25 + schemas: {}, 26 26 serviceResponse: 'body', 27 27 services: {}, 28 28 types: {},
+3 -3
packages/openapi-ts/src/utils/write/__tests__/core.spec.ts
··· 37 37 name: 'AppClient', 38 38 operationId: true, 39 39 output: '', 40 - schemas: true, 40 + schemas: {}, 41 41 serviceResponse: 'body', 42 42 services: {}, 43 43 types: {}, ··· 94 94 name: 'AppClient', 95 95 operationId: true, 96 96 output: '', 97 - schemas: true, 97 + schemas: {}, 98 98 serviceResponse: 'body', 99 99 services: {}, 100 100 types: {}, ··· 134 134 name: 'AppClient', 135 135 operationId: true, 136 136 output: '', 137 - schemas: true, 137 + schemas: {}, 138 138 serviceResponse: 'body', 139 139 services: {}, 140 140 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/index.spec.ts
··· 22 22 lint: false, 23 23 operationId: true, 24 24 output: '', 25 - schemas: true, 25 + schemas: {}, 26 26 serviceResponse: 'body', 27 27 services: {}, 28 28 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/models.spec.ts
··· 23 23 name: 'AppClient', 24 24 operationId: true, 25 25 output: '', 26 - schemas: true, 26 + schemas: {}, 27 27 serviceResponse: 'body', 28 28 services: {}, 29 29 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts
··· 24 24 name: 'AppClient', 25 25 operationId: true, 26 26 output: '', 27 - schemas: true, 27 + schemas: {}, 28 28 serviceResponse: 'body', 29 29 services: {}, 30 30 types: {},
+1 -1
packages/openapi-ts/src/utils/write/__tests__/services.spec.ts
··· 22 22 lint: false, 23 23 operationId: true, 24 24 output: '', 25 - schemas: true, 25 + schemas: {}, 26 26 serviceResponse: 'body', 27 27 services: {}, 28 28 types: {},
+1 -1
packages/openapi-ts/src/utils/write/client.ts
··· 56 56 name: 'enums.ts', 57 57 }); 58 58 } 59 - if (config.schemas) { 59 + if (config.schemas.export) { 60 60 files.schemas = new TypeScriptFile({ 61 61 dir: config.output, 62 62 name: 'schemas.ts',
+38 -1
packages/openapi-ts/src/utils/write/schemas.ts
··· 1 1 import { compiler, TypeScriptFile } from '../../compiler'; 2 2 import type { OpenApi } from '../../openApi'; 3 3 import { ensureValidTypeScriptJavaScriptIdentifier } from '../../openApi/common/parser/sanitize'; 4 + import { getConfig } from '../config'; 5 + 6 + const schemaToFormSchema = (schema: unknown): object => { 7 + if (Array.isArray(schema)) { 8 + return schema.map((item) => schemaToFormSchema(item)); 9 + } 10 + 11 + if (typeof schema !== 'object' || schema === null) { 12 + return schema as object; 13 + } 14 + 15 + const result = { ...schema }; 16 + Object.entries(result).forEach(([key, value]) => { 17 + if ( 18 + [ 19 + 'description', 20 + 'x-enum-descriptions', 21 + 'x-enum-varnames', 22 + 'x-enumNames', 23 + ].includes(key) 24 + ) { 25 + // @ts-ignore 26 + delete result[key]; 27 + return; 28 + } 29 + 30 + if (value && typeof value === 'object') { 31 + // @ts-ignore 32 + result[key] = schemaToFormSchema(value); 33 + } 34 + }); 35 + return result; 36 + }; 4 37 5 38 export const processSchemas = async ({ 6 39 file, ··· 13 46 return; 14 47 } 15 48 16 - const addSchema = (name: string, obj: any) => { 49 + const config = getConfig(); 50 + 51 + const addSchema = (name: string, schema: object) => { 17 52 const validName = `$${ensureValidTypeScriptJavaScriptIdentifier(name)}`; 53 + const obj = 54 + config.schemas.type === 'form' ? schemaToFormSchema(schema) : schema; 18 55 const expression = compiler.types.object({ obj }); 19 56 const statement = compiler.export.asConst(validName, expression); 20 57 file.add(statement);
+3
packages/openapi-ts/test/__snapshots__/test/generated/v3_schemas_form/index.ts.snap
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export * from './schemas.gen';
+1552
packages/openapi-ts/test/__snapshots__/test/generated/v3_schemas_form/schemas.gen.ts.snap
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export const $camelCaseCommentWithBreaks = { 4 + type: 'integer', 5 + } as const; 6 + 7 + export const $CommentWithBreaks = { 8 + type: 'integer', 9 + } as const; 10 + 11 + export const $CommentWithBackticks = { 12 + type: 'integer', 13 + } as const; 14 + 15 + export const $CommentWithBackticksAndQuotes = { 16 + type: 'integer', 17 + } as const; 18 + 19 + export const $CommentWithSlashes = { 20 + type: 'integer', 21 + } as const; 22 + 23 + export const $CommentWithExpressionPlaceholders = { 24 + type: 'integer', 25 + } as const; 26 + 27 + export const $CommentWithQuotes = { 28 + type: 'integer', 29 + } as const; 30 + 31 + export const $CommentWithReservedCharacters = { 32 + type: 'integer', 33 + } as const; 34 + 35 + export const $SimpleInteger = { 36 + type: 'integer', 37 + } as const; 38 + 39 + export const $SimpleBoolean = { 40 + type: 'boolean', 41 + } as const; 42 + 43 + export const $SimpleString = { 44 + type: 'string', 45 + } as const; 46 + 47 + export const $NonAsciiStringæøåÆØÅöôêÊ字符串 = { 48 + type: 'string', 49 + } as const; 50 + 51 + export const $SimpleFile = { 52 + type: 'file', 53 + } as const; 54 + 55 + export const $SimpleReference = { 56 + $ref: '#/components/schemas/ModelWithString', 57 + } as const; 58 + 59 + export const $SimpleStringWithPattern = { 60 + type: 'string', 61 + nullable: true, 62 + maxLength: 64, 63 + pattern: '^[a-zA-Z0-9_]*$', 64 + } as const; 65 + 66 + export const $EnumWithStrings = { 67 + enum: [ 68 + 'Success', 69 + 'Warning', 70 + 'Error', 71 + "'Single Quote'", 72 + '"Double Quotes"', 73 + 'Non-ascii: øæåôöØÆÅÔÖ字符串', 74 + ], 75 + } as const; 76 + 77 + export const $EnumWithReplacedCharacters = { 78 + enum: ["'Single Quote'", '"Double Quotes"', 'øæåôöØÆÅÔÖ字符串', 3.1, ''], 79 + type: 'string', 80 + } as const; 81 + 82 + export const $EnumWithNumbers = { 83 + enum: [ 84 + 1, 2, 3, 1.1, 1.2, 1.3, 100, 200, 300, -100, -200, -300, -1.1, -1.2, -1.3, 85 + ], 86 + default: 200, 87 + } as const; 88 + 89 + export const $EnumFromDescription = { 90 + type: 'number', 91 + } as const; 92 + 93 + export const $EnumWithExtensions = { 94 + enum: [200, 400, 500], 95 + } as const; 96 + 97 + export const $EnumWithXEnumNames = { 98 + enum: [0, 1, 2], 99 + } as const; 100 + 101 + export const $ArrayWithNumbers = { 102 + type: 'array', 103 + items: { 104 + type: 'integer', 105 + }, 106 + } as const; 107 + 108 + export const $ArrayWithBooleans = { 109 + type: 'array', 110 + items: { 111 + type: 'boolean', 112 + }, 113 + } as const; 114 + 115 + export const $ArrayWithStrings = { 116 + type: 'array', 117 + items: { 118 + type: 'string', 119 + }, 120 + default: ['test'], 121 + } as const; 122 + 123 + export const $ArrayWithReferences = { 124 + type: 'array', 125 + items: { 126 + $ref: '#/components/schemas/ModelWithString', 127 + }, 128 + } as const; 129 + 130 + export const $ArrayWithArray = { 131 + type: 'array', 132 + items: { 133 + type: 'array', 134 + items: { 135 + $ref: '#/components/schemas/ModelWithString', 136 + }, 137 + }, 138 + } as const; 139 + 140 + export const $ArrayWithProperties = { 141 + type: 'array', 142 + items: { 143 + type: 'object', 144 + properties: { 145 + foo: { 146 + $ref: '#/components/schemas/camelCaseCommentWithBreaks', 147 + }, 148 + bar: { 149 + type: 'string', 150 + }, 151 + }, 152 + }, 153 + } as const; 154 + 155 + export const $ArrayWithAnyOfProperties = { 156 + type: 'array', 157 + items: { 158 + anyOf: [ 159 + { 160 + type: 'object', 161 + properties: { 162 + foo: { 163 + type: 'string', 164 + default: 'test', 165 + }, 166 + }, 167 + }, 168 + { 169 + type: 'object', 170 + properties: { 171 + bar: { 172 + type: 'string', 173 + }, 174 + }, 175 + }, 176 + ], 177 + }, 178 + } as const; 179 + 180 + export const $AnyOfAnyAndNull = { 181 + type: 'object', 182 + properties: { 183 + data: { 184 + anyOf: [ 185 + {}, 186 + { 187 + type: 'null', 188 + }, 189 + ], 190 + }, 191 + }, 192 + } as const; 193 + 194 + export const $AnyOfArrays = { 195 + type: 'object', 196 + properties: { 197 + results: { 198 + items: { 199 + anyOf: [ 200 + { 201 + type: 'object', 202 + properties: { 203 + foo: { 204 + type: 'string', 205 + }, 206 + }, 207 + }, 208 + { 209 + type: 'object', 210 + properties: { 211 + bar: { 212 + type: 'string', 213 + }, 214 + }, 215 + }, 216 + ], 217 + }, 218 + type: 'array', 219 + }, 220 + }, 221 + } as const; 222 + 223 + export const $DictionaryWithString = { 224 + type: 'object', 225 + additionalProperties: { 226 + type: 'string', 227 + }, 228 + } as const; 229 + 230 + export const $DictionaryWithPropertiesAndAdditionalProperties = { 231 + type: 'object', 232 + properties: { 233 + foo: { 234 + type: 'string', 235 + }, 236 + }, 237 + additionalProperties: { 238 + type: 'string', 239 + }, 240 + } as const; 241 + 242 + export const $DictionaryWithReference = { 243 + type: 'object', 244 + additionalProperties: { 245 + $ref: '#/components/schemas/ModelWithString', 246 + }, 247 + } as const; 248 + 249 + export const $DictionaryWithArray = { 250 + type: 'object', 251 + additionalProperties: { 252 + type: 'array', 253 + items: { 254 + $ref: '#/components/schemas/ModelWithString', 255 + }, 256 + }, 257 + } as const; 258 + 259 + export const $DictionaryWithDictionary = { 260 + type: 'object', 261 + additionalProperties: { 262 + type: 'object', 263 + additionalProperties: { 264 + type: 'string', 265 + }, 266 + }, 267 + } as const; 268 + 269 + export const $DictionaryWithProperties = { 270 + type: 'object', 271 + additionalProperties: { 272 + type: 'object', 273 + properties: { 274 + foo: { 275 + type: 'string', 276 + }, 277 + bar: { 278 + type: 'string', 279 + }, 280 + }, 281 + }, 282 + } as const; 283 + 284 + export const $ModelWithInteger = { 285 + type: 'object', 286 + properties: { 287 + prop: { 288 + type: 'integer', 289 + }, 290 + }, 291 + } as const; 292 + 293 + export const $ModelWithBoolean = { 294 + type: 'object', 295 + properties: { 296 + prop: { 297 + type: 'boolean', 298 + }, 299 + }, 300 + } as const; 301 + 302 + export const $ModelWithString = { 303 + type: 'object', 304 + properties: { 305 + prop: { 306 + type: 'string', 307 + }, 308 + }, 309 + } as const; 310 + 311 + export const $Model_From_Zendesk = { 312 + type: 'string', 313 + } as const; 314 + 315 + export const $ModelWithNullableString = { 316 + type: 'object', 317 + required: ['nullableRequiredProp1', 'nullableRequiredProp2'], 318 + properties: { 319 + nullableProp1: { 320 + type: 'string', 321 + nullable: true, 322 + }, 323 + nullableRequiredProp1: { 324 + type: 'string', 325 + nullable: true, 326 + }, 327 + nullableProp2: { 328 + type: ['string', 'null'], 329 + }, 330 + nullableRequiredProp2: { 331 + type: ['string', 'null'], 332 + }, 333 + 'foo_bar-enum': { 334 + enum: ['Success', 'Warning', 'Error', 'ØÆÅ字符串'], 335 + }, 336 + }, 337 + } as const; 338 + 339 + export const $ModelWithEnum = { 340 + type: 'object', 341 + properties: { 342 + 'foo_bar-enum': { 343 + enum: ['Success', 'Warning', 'Error', 'ØÆÅ字符串'], 344 + }, 345 + statusCode: { 346 + enum: [ 347 + '100', 348 + '200 FOO', 349 + '300 FOO_BAR', 350 + '400 foo-bar', 351 + '500 foo.bar', 352 + '600 foo&bar', 353 + ], 354 + }, 355 + bool: { 356 + type: 'boolean', 357 + enum: [true], 358 + }, 359 + }, 360 + } as const; 361 + 362 + export const $ModelWithEnumWithHyphen = { 363 + type: 'object', 364 + properties: { 365 + 'foo-bar-baz-qux': { 366 + type: 'string', 367 + enum: ['3.0'], 368 + title: 'Foo-Bar-Baz-Qux', 369 + default: '3.0', 370 + }, 371 + }, 372 + } as const; 373 + 374 + export const $ModelWithEnumFromDescription = { 375 + type: 'object', 376 + properties: { 377 + test: { 378 + type: 'integer', 379 + }, 380 + }, 381 + } as const; 382 + 383 + export const $ModelWithNestedEnums = { 384 + type: 'object', 385 + properties: { 386 + dictionaryWithEnum: { 387 + type: 'object', 388 + additionalProperties: { 389 + enum: ['Success', 'Warning', 'Error'], 390 + }, 391 + }, 392 + dictionaryWithEnumFromDescription: { 393 + type: 'object', 394 + additionalProperties: { 395 + type: 'integer', 396 + }, 397 + }, 398 + arrayWithEnum: { 399 + type: 'array', 400 + items: { 401 + enum: ['Success', 'Warning', 'Error'], 402 + }, 403 + }, 404 + arrayWithDescription: { 405 + type: 'array', 406 + items: { 407 + type: 'integer', 408 + }, 409 + }, 410 + 'foo_bar-enum': { 411 + enum: ['Success', 'Warning', 'Error', 'ØÆÅ字符串'], 412 + }, 413 + }, 414 + } as const; 415 + 416 + export const $ModelWithReference = { 417 + type: 'object', 418 + properties: { 419 + prop: { 420 + $ref: '#/components/schemas/ModelWithProperties', 421 + }, 422 + }, 423 + } as const; 424 + 425 + export const $ModelWithArrayReadOnlyAndWriteOnly = { 426 + type: 'object', 427 + properties: { 428 + prop: { 429 + type: 'array', 430 + items: { 431 + $ref: '#/components/schemas/ModelWithReadOnlyAndWriteOnly', 432 + }, 433 + }, 434 + propWithFile: { 435 + type: 'array', 436 + items: { 437 + type: 'file', 438 + }, 439 + }, 440 + propWithNumber: { 441 + type: 'array', 442 + items: { 443 + type: 'number', 444 + }, 445 + }, 446 + }, 447 + } as const; 448 + 449 + export const $ModelWithArray = { 450 + type: 'object', 451 + properties: { 452 + prop: { 453 + type: 'array', 454 + items: { 455 + $ref: '#/components/schemas/ModelWithString', 456 + }, 457 + }, 458 + propWithFile: { 459 + type: 'array', 460 + items: { 461 + type: 'file', 462 + }, 463 + }, 464 + propWithNumber: { 465 + type: 'array', 466 + items: { 467 + type: 'number', 468 + }, 469 + }, 470 + }, 471 + } as const; 472 + 473 + export const $ModelWithDictionary = { 474 + type: 'object', 475 + properties: { 476 + prop: { 477 + type: 'object', 478 + additionalProperties: { 479 + type: 'string', 480 + }, 481 + }, 482 + }, 483 + } as const; 484 + 485 + export const $DeprecatedModel = { 486 + deprecated: true, 487 + type: 'object', 488 + properties: { 489 + prop: { 490 + deprecated: true, 491 + type: 'string', 492 + }, 493 + }, 494 + } as const; 495 + 496 + export const $ModelWithCircularReference = { 497 + type: 'object', 498 + properties: { 499 + prop: { 500 + $ref: '#/components/schemas/ModelWithCircularReference', 501 + }, 502 + }, 503 + } as const; 504 + 505 + export const $CompositionWithOneOf = { 506 + type: 'object', 507 + properties: { 508 + propA: { 509 + type: 'object', 510 + oneOf: [ 511 + { 512 + $ref: '#/components/schemas/ModelWithString', 513 + }, 514 + { 515 + $ref: '#/components/schemas/ModelWithEnum', 516 + }, 517 + { 518 + $ref: '#/components/schemas/ModelWithArray', 519 + }, 520 + { 521 + $ref: '#/components/schemas/ModelWithDictionary', 522 + }, 523 + ], 524 + }, 525 + }, 526 + } as const; 527 + 528 + export const $CompositionWithOneOfAnonymous = { 529 + type: 'object', 530 + properties: { 531 + propA: { 532 + type: 'object', 533 + oneOf: [ 534 + { 535 + type: 'object', 536 + properties: { 537 + propA: { 538 + type: 'string', 539 + }, 540 + }, 541 + }, 542 + { 543 + type: 'string', 544 + }, 545 + { 546 + type: 'integer', 547 + }, 548 + ], 549 + }, 550 + }, 551 + } as const; 552 + 553 + export const $ModelCircle = { 554 + type: 'object', 555 + required: ['kind'], 556 + properties: { 557 + kind: { 558 + type: 'string', 559 + }, 560 + radius: { 561 + type: 'number', 562 + }, 563 + }, 564 + } as const; 565 + 566 + export const $ModelSquare = { 567 + type: 'object', 568 + required: ['kind'], 569 + properties: { 570 + kind: { 571 + type: 'string', 572 + }, 573 + sideLength: { 574 + type: 'number', 575 + }, 576 + }, 577 + } as const; 578 + 579 + export const $CompositionWithOneOfDiscriminator = { 580 + type: 'object', 581 + oneOf: [ 582 + { 583 + $ref: '#/components/schemas/ModelCircle', 584 + }, 585 + { 586 + $ref: '#/components/schemas/ModelSquare', 587 + }, 588 + ], 589 + discriminator: { 590 + propertyName: 'kind', 591 + mapping: { 592 + circle: '#/components/schemas/ModelCircle', 593 + square: '#/components/schemas/ModelSquare', 594 + }, 595 + }, 596 + } as const; 597 + 598 + export const $CompositionWithAnyOf = { 599 + type: 'object', 600 + properties: { 601 + propA: { 602 + type: 'object', 603 + anyOf: [ 604 + { 605 + $ref: '#/components/schemas/ModelWithString', 606 + }, 607 + { 608 + $ref: '#/components/schemas/ModelWithEnum', 609 + }, 610 + { 611 + $ref: '#/components/schemas/ModelWithArray', 612 + }, 613 + { 614 + $ref: '#/components/schemas/ModelWithDictionary', 615 + }, 616 + ], 617 + }, 618 + }, 619 + } as const; 620 + 621 + export const $CompositionWithAnyOfAnonymous = { 622 + type: 'object', 623 + properties: { 624 + propA: { 625 + type: 'object', 626 + anyOf: [ 627 + { 628 + type: 'object', 629 + properties: { 630 + propA: { 631 + type: 'string', 632 + }, 633 + }, 634 + }, 635 + { 636 + type: 'string', 637 + }, 638 + { 639 + type: 'integer', 640 + }, 641 + ], 642 + }, 643 + }, 644 + } as const; 645 + 646 + export const $CompositionWithNestedAnyAndTypeNull = { 647 + type: 'object', 648 + properties: { 649 + propA: { 650 + type: 'object', 651 + anyOf: [ 652 + { 653 + items: { 654 + anyOf: [ 655 + { 656 + $ref: '#/components/schemas/ModelWithDictionary', 657 + }, 658 + { 659 + type: 'null', 660 + }, 661 + ], 662 + }, 663 + type: 'array', 664 + }, 665 + { 666 + items: { 667 + anyOf: [ 668 + { 669 + $ref: '#/components/schemas/ModelWithArray', 670 + }, 671 + { 672 + type: 'null', 673 + }, 674 + ], 675 + }, 676 + type: 'array', 677 + }, 678 + ], 679 + }, 680 + }, 681 + } as const; 682 + 683 + export const $Enum1 = { 684 + enum: ['Bird', 'Dog'], 685 + type: 'string', 686 + } as const; 687 + 688 + export const $ConstValue = { 689 + type: 'string', 690 + const: 'ConstValue', 691 + } as const; 692 + 693 + export const $CompositionWithNestedAnyOfAndNull = { 694 + type: 'object', 695 + properties: { 696 + propA: { 697 + anyOf: [ 698 + { 699 + items: { 700 + anyOf: [ 701 + { 702 + $ref: '#/components/schemas/Enum1', 703 + }, 704 + { 705 + $ref: '#/components/schemas/ConstValue', 706 + }, 707 + ], 708 + }, 709 + type: 'array', 710 + }, 711 + { 712 + type: 'null', 713 + }, 714 + ], 715 + title: 'Scopes', 716 + }, 717 + }, 718 + } as const; 719 + 720 + export const $CompositionWithOneOfAndNullable = { 721 + type: 'object', 722 + properties: { 723 + propA: { 724 + nullable: true, 725 + type: 'object', 726 + oneOf: [ 727 + { 728 + type: 'object', 729 + properties: { 730 + boolean: { 731 + type: 'boolean', 732 + }, 733 + }, 734 + }, 735 + { 736 + $ref: '#/components/schemas/ModelWithEnum', 737 + }, 738 + { 739 + $ref: '#/components/schemas/ModelWithArray', 740 + }, 741 + { 742 + $ref: '#/components/schemas/ModelWithDictionary', 743 + }, 744 + ], 745 + }, 746 + }, 747 + } as const; 748 + 749 + export const $CompositionWithOneOfAndSimpleDictionary = { 750 + type: 'object', 751 + properties: { 752 + propA: { 753 + oneOf: [ 754 + { 755 + type: 'boolean', 756 + }, 757 + { 758 + type: 'object', 759 + additionalProperties: { 760 + type: 'number', 761 + }, 762 + }, 763 + ], 764 + }, 765 + }, 766 + } as const; 767 + 768 + export const $CompositionWithOneOfAndSimpleArrayDictionary = { 769 + type: 'object', 770 + properties: { 771 + propA: { 772 + oneOf: [ 773 + { 774 + type: 'boolean', 775 + }, 776 + { 777 + type: 'object', 778 + additionalProperties: { 779 + type: 'array', 780 + items: { 781 + type: 'boolean', 782 + }, 783 + }, 784 + }, 785 + ], 786 + }, 787 + }, 788 + } as const; 789 + 790 + export const $CompositionWithOneOfAndComplexArrayDictionary = { 791 + type: 'object', 792 + properties: { 793 + propA: { 794 + oneOf: [ 795 + { 796 + type: 'boolean', 797 + }, 798 + { 799 + type: 'object', 800 + additionalProperties: { 801 + type: 'array', 802 + items: { 803 + oneOf: [ 804 + { 805 + type: 'number', 806 + }, 807 + { 808 + type: 'string', 809 + }, 810 + ], 811 + }, 812 + }, 813 + }, 814 + ], 815 + }, 816 + }, 817 + } as const; 818 + 819 + export const $CompositionWithAllOfAndNullable = { 820 + type: 'object', 821 + properties: { 822 + propA: { 823 + nullable: true, 824 + type: 'object', 825 + allOf: [ 826 + { 827 + type: 'object', 828 + properties: { 829 + boolean: { 830 + type: 'boolean', 831 + }, 832 + }, 833 + }, 834 + { 835 + $ref: '#/components/schemas/ModelWithEnum', 836 + }, 837 + { 838 + $ref: '#/components/schemas/ModelWithArray', 839 + }, 840 + { 841 + $ref: '#/components/schemas/ModelWithDictionary', 842 + }, 843 + ], 844 + }, 845 + }, 846 + } as const; 847 + 848 + export const $CompositionWithAnyOfAndNullable = { 849 + type: 'object', 850 + properties: { 851 + propA: { 852 + nullable: true, 853 + type: 'object', 854 + anyOf: [ 855 + { 856 + type: 'object', 857 + properties: { 858 + boolean: { 859 + type: 'boolean', 860 + }, 861 + }, 862 + }, 863 + { 864 + $ref: '#/components/schemas/ModelWithEnum', 865 + }, 866 + { 867 + $ref: '#/components/schemas/ModelWithArray', 868 + }, 869 + { 870 + $ref: '#/components/schemas/ModelWithDictionary', 871 + }, 872 + ], 873 + }, 874 + }, 875 + } as const; 876 + 877 + export const $CompositionBaseModel = { 878 + type: 'object', 879 + properties: { 880 + firstName: { 881 + type: 'string', 882 + }, 883 + lastname: { 884 + type: 'string', 885 + }, 886 + }, 887 + } as const; 888 + 889 + export const $CompositionExtendedModel = { 890 + type: 'object', 891 + allOf: [ 892 + { 893 + $ref: '#/components/schemas/CompositionBaseModel', 894 + }, 895 + ], 896 + properties: { 897 + age: { 898 + type: 'number', 899 + }, 900 + }, 901 + required: ['firstName', 'lastname', 'age'], 902 + } as const; 903 + 904 + export const $ModelWithProperties = { 905 + type: 'object', 906 + required: ['required', 'requiredAndReadOnly', 'requiredAndNullable'], 907 + properties: { 908 + required: { 909 + type: 'string', 910 + }, 911 + requiredAndReadOnly: { 912 + type: 'string', 913 + readOnly: true, 914 + }, 915 + requiredAndNullable: { 916 + type: 'string', 917 + nullable: true, 918 + }, 919 + string: { 920 + type: 'string', 921 + }, 922 + number: { 923 + type: 'number', 924 + }, 925 + boolean: { 926 + type: 'boolean', 927 + }, 928 + reference: { 929 + $ref: '#/components/schemas/ModelWithString', 930 + }, 931 + 'property with space': { 932 + type: 'string', 933 + }, 934 + default: { 935 + type: 'string', 936 + }, 937 + try: { 938 + type: 'string', 939 + }, 940 + '@namespace.string': { 941 + type: 'string', 942 + readOnly: true, 943 + }, 944 + '@namespace.integer': { 945 + type: 'integer', 946 + readOnly: true, 947 + }, 948 + }, 949 + } as const; 950 + 951 + export const $ModelWithNestedProperties = { 952 + type: 'object', 953 + required: ['first'], 954 + properties: { 955 + first: { 956 + type: 'object', 957 + required: ['second'], 958 + readOnly: true, 959 + nullable: true, 960 + properties: { 961 + second: { 962 + type: 'object', 963 + required: ['third'], 964 + readOnly: true, 965 + nullable: true, 966 + properties: { 967 + third: { 968 + type: 'string', 969 + required: true, 970 + readOnly: true, 971 + nullable: true, 972 + }, 973 + }, 974 + }, 975 + }, 976 + }, 977 + }, 978 + } as const; 979 + 980 + export const $ModelWithDuplicateProperties = { 981 + type: 'object', 982 + properties: { 983 + prop: { 984 + $ref: '#/components/schemas/ModelWithString', 985 + }, 986 + }, 987 + } as const; 988 + 989 + export const $ModelWithOrderedProperties = { 990 + type: 'object', 991 + properties: { 992 + zebra: { 993 + type: 'string', 994 + }, 995 + apple: { 996 + type: 'string', 997 + }, 998 + hawaii: { 999 + type: 'string', 1000 + }, 1001 + }, 1002 + } as const; 1003 + 1004 + export const $ModelWithDuplicateImports = { 1005 + type: 'object', 1006 + properties: { 1007 + propA: { 1008 + $ref: '#/components/schemas/ModelWithString', 1009 + }, 1010 + propB: { 1011 + $ref: '#/components/schemas/ModelWithString', 1012 + }, 1013 + propC: { 1014 + $ref: '#/components/schemas/ModelWithString', 1015 + }, 1016 + }, 1017 + } as const; 1018 + 1019 + export const $ModelThatExtends = { 1020 + type: 'object', 1021 + allOf: [ 1022 + { 1023 + $ref: '#/components/schemas/ModelWithString', 1024 + }, 1025 + { 1026 + type: 'object', 1027 + properties: { 1028 + propExtendsA: { 1029 + type: 'string', 1030 + }, 1031 + propExtendsB: { 1032 + $ref: '#/components/schemas/ModelWithString', 1033 + }, 1034 + }, 1035 + }, 1036 + ], 1037 + } as const; 1038 + 1039 + export const $ModelThatExtendsExtends = { 1040 + type: 'object', 1041 + allOf: [ 1042 + { 1043 + $ref: '#/components/schemas/ModelWithString', 1044 + }, 1045 + { 1046 + $ref: '#/components/schemas/ModelThatExtends', 1047 + }, 1048 + { 1049 + type: 'object', 1050 + properties: { 1051 + propExtendsC: { 1052 + type: 'string', 1053 + }, 1054 + propExtendsD: { 1055 + $ref: '#/components/schemas/ModelWithString', 1056 + }, 1057 + }, 1058 + }, 1059 + ], 1060 + } as const; 1061 + 1062 + export const $ModelWithPattern = { 1063 + type: 'object', 1064 + required: ['key', 'name'], 1065 + properties: { 1066 + key: { 1067 + maxLength: 64, 1068 + pattern: '^[a-zA-Z0-9_]*$', 1069 + type: 'string', 1070 + }, 1071 + name: { 1072 + maxLength: 255, 1073 + type: 'string', 1074 + }, 1075 + enabled: { 1076 + type: 'boolean', 1077 + readOnly: true, 1078 + }, 1079 + modified: { 1080 + type: 'string', 1081 + format: 'date-time', 1082 + readOnly: true, 1083 + }, 1084 + id: { 1085 + type: 'string', 1086 + pattern: '^d{2}-d{3}-d{4}$', 1087 + }, 1088 + text: { 1089 + type: 'string', 1090 + pattern: '^w+$', 1091 + }, 1092 + patternWithSingleQuotes: { 1093 + type: 'string', 1094 + pattern: "^[a-zA-Z0-9']*$", 1095 + }, 1096 + patternWithNewline: { 1097 + type: 'string', 1098 + pattern: `aaa 1099 + bbb`, 1100 + }, 1101 + patternWithBacktick: { 1102 + type: 'string', 1103 + pattern: 'aaa`bbb', 1104 + }, 1105 + }, 1106 + } as const; 1107 + 1108 + export const $File = { 1109 + required: ['mime'], 1110 + type: 'object', 1111 + properties: { 1112 + id: { 1113 + title: 'Id', 1114 + type: 'string', 1115 + readOnly: true, 1116 + minLength: 1, 1117 + }, 1118 + updated_at: { 1119 + title: 'Updated at', 1120 + type: 'string', 1121 + format: 'date-time', 1122 + readOnly: true, 1123 + }, 1124 + created_at: { 1125 + title: 'Created at', 1126 + type: 'string', 1127 + format: 'date-time', 1128 + readOnly: true, 1129 + }, 1130 + mime: { 1131 + title: 'Mime', 1132 + type: 'string', 1133 + maxLength: 24, 1134 + minLength: 1, 1135 + }, 1136 + file: { 1137 + title: 'File', 1138 + type: 'string', 1139 + readOnly: true, 1140 + format: 'uri', 1141 + }, 1142 + }, 1143 + } as const; 1144 + 1145 + export const $default = { 1146 + type: 'object', 1147 + properties: { 1148 + name: { 1149 + type: 'string', 1150 + }, 1151 + }, 1152 + } as const; 1153 + 1154 + export const $Pageable = { 1155 + type: 'object', 1156 + properties: { 1157 + page: { 1158 + minimum: 0, 1159 + type: 'integer', 1160 + format: 'int32', 1161 + default: 0, 1162 + }, 1163 + size: { 1164 + minimum: 1, 1165 + type: 'integer', 1166 + format: 'int32', 1167 + }, 1168 + sort: { 1169 + type: 'array', 1170 + items: { 1171 + type: 'string', 1172 + }, 1173 + }, 1174 + }, 1175 + } as const; 1176 + 1177 + export const $FreeFormObjectWithoutAdditionalProperties = { 1178 + type: 'object', 1179 + } as const; 1180 + 1181 + export const $FreeFormObjectWithAdditionalPropertiesEqTrue = { 1182 + type: 'object', 1183 + additionalProperties: true, 1184 + } as const; 1185 + 1186 + export const $FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { 1187 + type: 'object', 1188 + additionalProperties: {}, 1189 + } as const; 1190 + 1191 + export const $ModelWithConst = { 1192 + type: 'object', 1193 + properties: { 1194 + String: { 1195 + const: 'String', 1196 + }, 1197 + number: { 1198 + const: 0, 1199 + }, 1200 + null: { 1201 + const: null, 1202 + }, 1203 + withType: { 1204 + type: 'string', 1205 + const: 'Some string', 1206 + }, 1207 + }, 1208 + } as const; 1209 + 1210 + export const $ModelWithAdditionalPropertiesEqTrue = { 1211 + type: 'object', 1212 + properties: { 1213 + prop: { 1214 + type: 'string', 1215 + }, 1216 + }, 1217 + additionalProperties: true, 1218 + } as const; 1219 + 1220 + export const $NestedAnyOfArraysNullable = { 1221 + properties: { 1222 + nullableArray: { 1223 + anyOf: [ 1224 + { 1225 + items: { 1226 + anyOf: [ 1227 + { 1228 + type: 'string', 1229 + }, 1230 + { 1231 + type: 'boolean', 1232 + }, 1233 + ], 1234 + }, 1235 + type: 'array', 1236 + }, 1237 + { 1238 + type: 'null', 1239 + }, 1240 + ], 1241 + }, 1242 + }, 1243 + type: 'object', 1244 + } as const; 1245 + 1246 + export const $CompositionWithOneOfAndProperties = { 1247 + type: 'object', 1248 + oneOf: [ 1249 + { 1250 + type: 'object', 1251 + required: ['foo'], 1252 + properties: { 1253 + foo: { 1254 + $ref: '#/components/parameters/SimpleParameter', 1255 + }, 1256 + }, 1257 + additionalProperties: false, 1258 + }, 1259 + { 1260 + type: 'object', 1261 + required: ['bar'], 1262 + properties: { 1263 + bar: { 1264 + $ref: '#/components/schemas/NonAsciiString%C3%A6%C3%B8%C3%A5%C3%86%C3%98%C3%85%C3%B6%C3%B4%C3%AA%C3%8A%E5%AD%97%E7%AC%A6%E4%B8%B2', 1265 + }, 1266 + }, 1267 + additionalProperties: false, 1268 + }, 1269 + ], 1270 + required: ['baz', 'qux'], 1271 + properties: { 1272 + baz: { 1273 + type: 'integer', 1274 + format: 'uint16', 1275 + minimum: 0, 1276 + nullable: true, 1277 + }, 1278 + qux: { 1279 + type: 'integer', 1280 + format: 'uint8', 1281 + minimum: 0, 1282 + }, 1283 + }, 1284 + } as const; 1285 + 1286 + export const $NullableObject = { 1287 + type: 'object', 1288 + nullable: true, 1289 + properties: { 1290 + foo: { 1291 + type: 'string', 1292 + }, 1293 + }, 1294 + default: null, 1295 + } as const; 1296 + 1297 + export const $CharactersInDescription = { 1298 + type: 'string', 1299 + } as const; 1300 + 1301 + export const $ModelWithNullableObject = { 1302 + type: 'object', 1303 + properties: { 1304 + data: { 1305 + $ref: '#/components/schemas/NullableObject', 1306 + }, 1307 + }, 1308 + } as const; 1309 + 1310 + export const $ModelWithOneOfEnum = { 1311 + oneOf: [ 1312 + { 1313 + type: 'object', 1314 + required: ['foo'], 1315 + properties: { 1316 + foo: { 1317 + type: 'string', 1318 + enum: ['Bar'], 1319 + }, 1320 + }, 1321 + }, 1322 + { 1323 + type: 'object', 1324 + required: ['foo'], 1325 + properties: { 1326 + foo: { 1327 + type: 'string', 1328 + enum: ['Baz'], 1329 + }, 1330 + }, 1331 + }, 1332 + { 1333 + type: 'object', 1334 + required: ['foo'], 1335 + properties: { 1336 + foo: { 1337 + type: 'string', 1338 + enum: ['Qux'], 1339 + }, 1340 + }, 1341 + }, 1342 + { 1343 + type: 'object', 1344 + required: ['content', 'foo'], 1345 + properties: { 1346 + content: { 1347 + type: 'string', 1348 + format: 'date-time', 1349 + }, 1350 + foo: { 1351 + type: 'string', 1352 + enum: ['Quux'], 1353 + }, 1354 + }, 1355 + }, 1356 + { 1357 + type: 'object', 1358 + required: ['content', 'foo'], 1359 + properties: { 1360 + content: { 1361 + type: 'array', 1362 + items: [ 1363 + { 1364 + type: 'string', 1365 + format: 'date-time', 1366 + }, 1367 + { 1368 + type: 'string', 1369 + }, 1370 + ], 1371 + maxItems: 2, 1372 + minItems: 2, 1373 + }, 1374 + foo: { 1375 + type: 'string', 1376 + enum: ['Corge'], 1377 + }, 1378 + }, 1379 + }, 1380 + ], 1381 + } as const; 1382 + 1383 + export const $ModelWithNestedArrayEnumsDataFoo = { 1384 + enum: ['foo', 'bar'], 1385 + type: 'string', 1386 + } as const; 1387 + 1388 + export const $ModelWithNestedArrayEnumsDataBar = { 1389 + enum: ['baz', 'qux'], 1390 + type: 'string', 1391 + } as const; 1392 + 1393 + export const $ModelWithNestedArrayEnumsData = { 1394 + type: 'object', 1395 + properties: { 1396 + foo: { 1397 + type: 'array', 1398 + items: { 1399 + $ref: '#/components/schemas/ModelWithNestedArrayEnumsDataFoo', 1400 + }, 1401 + }, 1402 + bar: { 1403 + type: 'array', 1404 + items: { 1405 + $ref: '#/components/schemas/ModelWithNestedArrayEnumsDataBar', 1406 + }, 1407 + }, 1408 + }, 1409 + } as const; 1410 + 1411 + export const $ModelWithNestedArrayEnums = { 1412 + type: 'object', 1413 + properties: { 1414 + array_strings: { 1415 + type: 'array', 1416 + items: { 1417 + type: 'string', 1418 + }, 1419 + }, 1420 + data: { 1421 + allOf: [ 1422 + { 1423 + $ref: '#/components/schemas/ModelWithNestedArrayEnumsData', 1424 + }, 1425 + ], 1426 + }, 1427 + }, 1428 + } as const; 1429 + 1430 + export const $ModelWithNestedCompositionEnums = { 1431 + type: 'object', 1432 + properties: { 1433 + foo: { 1434 + allOf: [ 1435 + { 1436 + $ref: '#/components/schemas/ModelWithNestedArrayEnumsDataFoo', 1437 + }, 1438 + ], 1439 + }, 1440 + }, 1441 + } as const; 1442 + 1443 + export const $ModelWithReadOnlyAndWriteOnly = { 1444 + type: 'object', 1445 + required: ['foo', 'bar', 'baz'], 1446 + properties: { 1447 + foo: { 1448 + type: 'string', 1449 + }, 1450 + bar: { 1451 + readOnly: true, 1452 + type: 'string', 1453 + }, 1454 + baz: { 1455 + type: 'string', 1456 + writeOnly: true, 1457 + }, 1458 + }, 1459 + } as const; 1460 + 1461 + export const $ModelWithConstantSizeArray = { 1462 + type: 'array', 1463 + items: { 1464 + type: 'number', 1465 + }, 1466 + minItems: 2, 1467 + maxItems: 2, 1468 + } as const; 1469 + 1470 + export const $ModelWithAnyOfConstantSizeArray = { 1471 + type: 'array', 1472 + items: { 1473 + oneOf: [ 1474 + { 1475 + type: 'number', 1476 + }, 1477 + { 1478 + type: 'string', 1479 + }, 1480 + ], 1481 + }, 1482 + minItems: 3, 1483 + maxItems: 3, 1484 + } as const; 1485 + 1486 + export const $ModelWithAnyOfConstantSizeArrayNullable = { 1487 + type: 'array', 1488 + items: { 1489 + oneOf: [ 1490 + { 1491 + type: 'number', 1492 + nullable: true, 1493 + }, 1494 + { 1495 + type: 'string', 1496 + }, 1497 + ], 1498 + }, 1499 + minItems: 3, 1500 + maxItems: 3, 1501 + } as const; 1502 + 1503 + export const $ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = { 1504 + type: 'array', 1505 + items: { 1506 + oneOf: [ 1507 + { 1508 + type: 'number', 1509 + }, 1510 + { 1511 + type: 'string', 1512 + }, 1513 + ], 1514 + }, 1515 + minItems: 2, 1516 + maxItems: 2, 1517 + } as const; 1518 + 1519 + export const $ModelWithAnyOfConstantSizeArrayAndIntersect = { 1520 + type: 'array', 1521 + items: { 1522 + allOf: [ 1523 + { 1524 + type: 'number', 1525 + }, 1526 + { 1527 + type: 'string', 1528 + }, 1529 + ], 1530 + }, 1531 + minItems: 2, 1532 + maxItems: 2, 1533 + } as const; 1534 + 1535 + export const $ModelWithNumericEnumUnion = { 1536 + type: 'object', 1537 + properties: { 1538 + value: { 1539 + type: 'number', 1540 + enum: [1, 3, 6, 12], 1541 + }, 1542 + }, 1543 + } as const; 1544 + 1545 + export const $ModelWithBackticksInDescription = { 1546 + type: 'object', 1547 + properties: { 1548 + template: { 1549 + type: 'string', 1550 + }, 1551 + }, 1552 + } as const;
+17 -1
packages/openapi-ts/test/index.spec.ts
··· 213 213 ...config, 214 214 enums: false, 215 215 exportCore: false, 216 - schemas: true, 216 + schemas: { 217 + type: 'json', 218 + }, 217 219 services: false, 218 220 types: false, 219 221 } as UserConfig, 220 222 description: 'generate JSON Schemas', 221 223 name: 'v3_schemas_json', 224 + }, 225 + { 226 + config: { 227 + ...config, 228 + enums: false, 229 + exportCore: false, 230 + schemas: { 231 + type: 'form', 232 + }, 233 + services: false, 234 + types: false, 235 + } as UserConfig, 236 + description: 'generate form validation schemas', 237 + name: 'v3_schemas_form', 222 238 }, 223 239 ])('$description', async ({ name, config }) => { 224 240 const output = toOutputPath(name);