orval 8.0.0-rc.2 → 8.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,8 @@
1
1
  import path from "node:path";
2
- import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMode, PropertySortOrder, RefComponentSuffix, asyncReduce, createLogger, createSuccessMessage, dynamicImport, generateComponentDefinition, generateDependencyImports, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getFileInfo, getFullRoute, getMockFileExtensionByTypeName, getRoute, ibmOpenapiValidator, isBoolean, isFunction, isObject, isReference, isRootKey, isSchema, isString, isUndefined, isUrl, jsDoc, log, logError, mergeDeep, openApiConverter, pascal, removeFilesAndEmptyFolders, resolveRef, upath, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
3
- import SwaggerParser from "@apidevtools/swagger-parser";
4
- import chalk from "chalk";
5
- import fs from "fs-extra";
6
- import yaml from "js-yaml";
2
+ import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMode, PropertySortOrder, RefComponentSuffix, asyncReduce, conventionName, createLogger, createSuccessMessage, dynamicImport, generateComponentDefinition, generateDependencyImports, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getFileInfo, getFullRoute, getMockFileExtensionByTypeName, getRoute, isBoolean, isFunction, isObject, isReference, isString, isUndefined, isUrl, jsDoc, log, logError, pascal, removeFilesAndEmptyFolders, resolveRef, upath, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
3
+ import { bundle } from "@scalar/json-magic/bundle";
4
+ import { fetchUrls, parseJson, parseYaml, readFiles } from "@scalar/json-magic/bundle/plugins/node";
5
+ import { upgrade, validate } from "@scalar/openapi-parser";
7
6
  import * as mock from "@orval/mock";
8
7
  import { DEFAULT_MOCK_OPTIONS, generateMockImports } from "@orval/mock";
9
8
  import angular from "@orval/angular";
@@ -13,13 +12,14 @@ import hono from "@orval/hono";
13
12
  import mcp from "@orval/mcp";
14
13
  import query from "@orval/query";
15
14
  import swr from "@orval/swr";
16
- import zod from "@orval/zod";
15
+ import zod, { dereference, generateZodValidationSchemaDefinition, isZodVersionV4, parseZodValidationSchemaDefinition } from "@orval/zod";
16
+ import chalk from "chalk";
17
17
  import { ExecaError, execa } from "execa";
18
+ import fs from "fs-extra";
18
19
  import { unique } from "remeda";
19
20
  import { parseArgsStringToArgv } from "string-argv";
20
- import https from "node:https";
21
- import enquirer from "enquirer";
22
21
  import { findUp } from "find-up";
22
+ import yaml from "js-yaml";
23
23
  import { parse } from "tsconfck";
24
24
  import fs$1 from "node:fs";
25
25
  import { createJiti } from "jiti";
@@ -27,7 +27,7 @@ import { createJiti } from "jiti";
27
27
  //#region package.json
28
28
  var name = "orval";
29
29
  var description = "A swagger client generator for typescript";
30
- var version = "8.0.0-rc.2";
30
+ var version = "8.0.0-rc.4";
31
31
 
32
32
  //#endregion
33
33
  //#region src/client.ts
@@ -59,9 +59,9 @@ const getGeneratorClient = (outputClient, output) => {
59
59
  if (!generator) throw new Error(`Oups... 🍻. Client not found: ${outputClient}`);
60
60
  return generator;
61
61
  };
62
- const generateClientImports = ({ client = DEFAULT_CLIENT, implementation, imports, specsName, hasSchemaDir, isAllowSyntheticDefaultImports, hasGlobalMutator, hasTagsMutator, hasParamsSerializerOptions, packageJson, output }) => {
62
+ const generateClientImports = ({ client = DEFAULT_CLIENT, implementation, imports, projectName, hasSchemaDir, isAllowSyntheticDefaultImports, hasGlobalMutator, hasTagsMutator, hasParamsSerializerOptions, packageJson, output }) => {
63
63
  const { dependencies } = getGeneratorClient(client, output);
64
- return generateDependencyImports(implementation, dependencies ? [...dependencies(hasGlobalMutator, hasParamsSerializerOptions, packageJson, output.httpClient, hasTagsMutator, output.override), ...imports] : imports, specsName, hasSchemaDir, isAllowSyntheticDefaultImports);
64
+ return generateDependencyImports(implementation, dependencies ? [...dependencies(hasGlobalMutator, hasParamsSerializerOptions, packageJson, output.httpClient, hasTagsMutator, output.override), ...imports] : imports, projectName, hasSchemaDir, isAllowSyntheticDefaultImports);
65
65
  };
66
66
  const generateClientHeader = ({ outputClient = DEFAULT_CLIENT, isRequestOptions, isGlobalMutator, isMutator, provideIn, hasAwaitedType, titles, output, verbOptions, tag, clientImplementation }) => {
67
67
  const { header } = getGeneratorClient(outputClient, output);
@@ -172,18 +172,13 @@ const generateExtraFiles = (outputClient = DEFAULT_CLIENT, verbsOptions, output,
172
172
 
173
173
  //#endregion
174
174
  //#region src/api.ts
175
- const getApiBuilder = async ({ input, output, context }) => {
176
- const api = await asyncReduce(Object.entries(context.specs[context.specKey].paths ?? {}), async (acc, [pathRoute, verbs]) => {
175
+ async function getApiBuilder({ input, output, context }) {
176
+ const api = await asyncReduce(Object.entries(context.spec.paths ?? {}), async (acc, [pathRoute, verbs]) => {
177
177
  const route = getRoute(pathRoute);
178
178
  let resolvedVerbs = verbs;
179
- let resolvedContext = context;
180
179
  if (isReference(verbs)) {
181
180
  const { schema, imports } = resolveRef(verbs, context);
182
181
  resolvedVerbs = schema;
183
- resolvedContext = {
184
- ...context,
185
- ...imports.length > 0 ? { specKey: imports[0].specKey } : {}
186
- };
187
182
  }
188
183
  let verbsOptions = await generateVerbsOptions({
189
184
  verbs: resolvedVerbs,
@@ -191,7 +186,7 @@ const getApiBuilder = async ({ input, output, context }) => {
191
186
  output,
192
187
  route,
193
188
  pathRoute,
194
- context: resolvedContext
189
+ context
195
190
  });
196
191
  if (output.override.useDeprecatedOperations === false) verbsOptions = verbsOptions.filter((verb) => {
197
192
  return !verb.deprecated;
@@ -203,13 +198,13 @@ const getApiBuilder = async ({ input, output, context }) => {
203
198
  acc$1.push(...body.schemas, ...response.schemas);
204
199
  return acc$1;
205
200
  }, []);
206
- const fullRoute = getFullRoute(route, verbs.servers ?? context.specs[context.specKey].servers, output.baseUrl);
201
+ const fullRoute = getFullRoute(route, verbs.servers ?? context.spec.servers, output.baseUrl);
207
202
  if (!output.target) throw new Error("Output does not have a target");
208
203
  const pathOperations = await generateOperations(output.client, verbsOptions, {
209
204
  route: fullRoute,
210
205
  pathRoute,
211
206
  override: output.override,
212
- context: resolvedContext,
207
+ context,
213
208
  mock: output.mock,
214
209
  output: output.target
215
210
  }, output);
@@ -237,155 +232,146 @@ const getApiBuilder = async ({ input, output, context }) => {
237
232
  importsMock: generateMockImports,
238
233
  extraFiles
239
234
  };
240
- };
235
+ }
241
236
 
242
237
  //#endregion
243
238
  //#region src/import-open-api.ts
244
- const importOpenApi = async ({ data, input, output, target, workspace }) => {
245
- const specs = await generateInputSpecs({
246
- specs: data,
247
- input,
248
- workspace
249
- });
239
+ async function importOpenApi({ spec, input, output, target, workspace, projectName }) {
240
+ const transformedOpenApi = await applyTransformer(spec, input.override.transformer, workspace);
250
241
  const schemas = getApiSchemas({
251
242
  input,
252
243
  output,
253
244
  target,
254
245
  workspace,
255
- specs
246
+ spec: transformedOpenApi
256
247
  });
257
248
  const api = await getApiBuilder({
258
249
  input,
259
250
  output,
260
251
  context: {
261
- specKey: target,
252
+ projectName,
262
253
  target,
263
254
  workspace,
264
- specs,
255
+ spec: transformedOpenApi,
265
256
  output
266
257
  }
267
258
  });
268
259
  return {
269
260
  ...api,
270
- schemas: {
271
- ...schemas,
272
- [target]: [...schemas[target] ?? [], ...api.schemas]
273
- },
261
+ schemas: [...schemas, ...api.schemas],
274
262
  target,
275
- info: specs[target].info
263
+ info: transformedOpenApi.info,
264
+ spec: transformedOpenApi
276
265
  };
277
- };
278
- const generateInputSpecs = async ({ specs, input, workspace }) => {
279
- const transformerFn = input.override?.transformer ? await dynamicImport(input.override.transformer, workspace) : void 0;
280
- return asyncReduce(Object.entries(specs), async (acc, [specKey, value]) => {
281
- const schema = await openApiConverter(value, input.converterOptions, specKey);
282
- const transfomedSchema = transformerFn ? transformerFn(schema) : schema;
283
- if (input.validation) await ibmOpenapiValidator(transfomedSchema, input.validation);
284
- acc[specKey] = transfomedSchema;
285
- return acc;
286
- }, {});
287
- };
288
- const getApiSchemas = ({ input, output, target, workspace, specs }) => {
289
- return Object.entries(specs).reduce((acc, [specKey, spec]) => {
290
- const context = {
291
- specKey,
292
- target,
293
- workspace,
294
- specs,
295
- output
296
- };
297
- const schemaDefinition = generateSchemasDefinition(spec.openapi ? spec.components?.schemas : getAllSchemas(spec, specKey), context, output.override.components.schemas.suffix, input.filters);
298
- const responseDefinition = generateComponentDefinition(spec.components?.responses, context, output.override.components.responses.suffix);
299
- const bodyDefinition = generateComponentDefinition(spec.components?.requestBodies, context, output.override.components.requestBodies.suffix);
300
- const parameters = generateParameterDefinition(spec.components?.parameters, context, output.override.components.parameters.suffix);
301
- const schemas = [
302
- ...schemaDefinition,
303
- ...responseDefinition,
304
- ...bodyDefinition,
305
- ...parameters
306
- ];
307
- if (schemas.length === 0) return acc;
308
- acc[specKey] = schemas;
309
- return acc;
310
- }, {});
311
- };
312
- const getAllSchemas = (spec, specKey) => {
313
- const keysToOmit = new Set([
314
- "openapi",
315
- "info",
316
- "servers",
317
- "paths",
318
- "components",
319
- "security",
320
- "tags",
321
- "externalDocs"
322
- ]);
323
- const cleanedSpec = Object.fromEntries(Object.entries(spec).filter(([key]) => !keysToOmit.has(key)));
324
- if (specKey && isSchema(cleanedSpec)) {
325
- const name$1 = upath.getSchemaFileName(specKey);
326
- const additionalKeysToOmit = new Set([
327
- "type",
328
- "properties",
329
- "allOf",
330
- "oneOf",
331
- "anyOf",
332
- "items"
333
- ]);
334
- return {
335
- [name$1]: cleanedSpec,
336
- ...getAllSchemas(Object.fromEntries(Object.entries(cleanedSpec).filter(([key]) => !additionalKeysToOmit.has(key))))
337
- };
338
- }
339
- return {
340
- ...Object.entries(cleanedSpec).reduce((acc, [key, value]) => {
341
- if (!isObject(value)) return acc;
342
- if (!isSchema(value) && !isReference(value)) return {
343
- ...acc,
344
- ...getAllSchemas(value)
345
- };
346
- acc[key] = value;
347
- return acc;
348
- }, {}),
349
- ...spec?.components?.schemas
266
+ }
267
+ async function applyTransformer(openApi, transformer, workspace) {
268
+ const transformerFn = transformer ? await dynamicImport(transformer, workspace) : void 0;
269
+ if (!transformerFn) return openApi;
270
+ const transformedOpenApi = transformerFn(openApi);
271
+ const { valid, errors } = await validate(transformedOpenApi);
272
+ if (!valid) throw new Error(`Validation failed`, { cause: errors });
273
+ return transformedOpenApi;
274
+ }
275
+ function getApiSchemas({ input, output, target, workspace, spec }) {
276
+ const context = {
277
+ target,
278
+ workspace,
279
+ spec,
280
+ output
350
281
  };
351
- };
282
+ const schemaDefinition = generateSchemasDefinition(spec.components?.schemas, context, output.override.components.schemas.suffix, input.filters);
283
+ const responseDefinition = generateComponentDefinition(spec.components?.responses, context, output.override.components.responses.suffix);
284
+ const bodyDefinition = generateComponentDefinition(spec.components?.requestBodies, context, output.override.components.requestBodies.suffix);
285
+ const parameters = generateParameterDefinition(spec.components?.parameters, context, output.override.components.parameters.suffix);
286
+ return [
287
+ ...schemaDefinition,
288
+ ...responseDefinition,
289
+ ...bodyDefinition,
290
+ ...parameters
291
+ ];
292
+ }
352
293
 
353
294
  //#endregion
354
295
  //#region src/import-specs.ts
355
- const resolveSpecs = async (path$1, { validate, ...options }, _isUrl, isOnlySchema) => {
356
- try {
357
- if (validate) try {
358
- await SwaggerParser.validate(path$1, options);
359
- } catch (error) {
360
- if (error instanceof Error && error.name === "ParserError") throw error;
361
- if (!isOnlySchema) log(`⚠️ ${chalk.yellow(error)}`);
362
- }
363
- const data = (await SwaggerParser.resolve(path$1, options)).values();
364
- if (_isUrl) return data;
365
- return Object.fromEntries(Object.entries(data).sort().map(([key, value]) => [isUrl(key) ? key : upath.resolve(key), value]));
366
- } catch {
367
- const file = await fs.readFile(path$1, "utf8");
368
- return { [path$1]: yaml.load(file) };
369
- }
370
- };
371
- const importSpecs = async (workspace, options) => {
296
+ async function resolveSpec(input, parserOptions) {
297
+ const dereferencedData = dereferenceExternalRef(await bundle(input, {
298
+ plugins: [
299
+ readFiles(),
300
+ fetchUrls({ headers: parserOptions?.headers }),
301
+ parseJson(),
302
+ parseYaml()
303
+ ],
304
+ treeShake: true
305
+ }));
306
+ const { valid, errors } = await validate(dereferencedData);
307
+ if (!valid) throw new Error("Validation failed", { cause: errors });
308
+ const { specification } = upgrade(dereferencedData);
309
+ return specification;
310
+ }
311
+ async function importSpecs(workspace, options, projectName) {
372
312
  const { input, output } = options;
373
- if (!isString(input.target)) return importOpenApi({
374
- data: { [workspace]: input.target },
375
- input,
376
- output,
377
- target: workspace,
378
- workspace
379
- });
380
- const isPathUrl = isUrl(input.target);
381
313
  return importOpenApi({
382
- data: await resolveSpecs(input.target, input.parserOptions, isPathUrl, !output.target),
314
+ spec: await resolveSpec(input.target, input.parserOptions),
383
315
  input,
384
316
  output,
385
317
  target: input.target,
386
- workspace
318
+ workspace,
319
+ projectName
387
320
  });
388
- };
321
+ }
322
+ /**
323
+ * The plugins from `@scalar/json-magic` does not dereference $ref.
324
+ * Instead if fetches them and puts them under x-ext, and changes the $ref to point to #x-ext/<name>.
325
+ * This function dereferences those x-ext $ref's.
326
+ */
327
+ function dereferenceExternalRef(data) {
328
+ const extensions = data["x-ext"] ?? {};
329
+ const UNWANTED_KEYS = new Set(["$schema", "$id"]);
330
+ function scrub(obj) {
331
+ if (obj === null || obj === void 0) return obj;
332
+ if (Array.isArray(obj)) return obj.map((x) => scrub(x));
333
+ if (typeof obj === "object") {
334
+ const rec = obj;
335
+ const out = {};
336
+ for (const [k, v] of Object.entries(rec)) {
337
+ if (UNWANTED_KEYS.has(k)) continue;
338
+ out[k] = scrub(v);
339
+ }
340
+ return out;
341
+ }
342
+ return obj;
343
+ }
344
+ function replaceRefs(obj) {
345
+ if (obj === null || obj === void 0) return obj;
346
+ if (typeof obj === "object") {
347
+ if (Array.isArray(obj)) return obj.map((element) => replaceRefs(element));
348
+ const record = obj;
349
+ if ("$ref" in record && typeof record.$ref === "string") {
350
+ const refValue = record.$ref;
351
+ if (refValue.startsWith("#/x-ext/")) {
352
+ const parts = refValue.replace("#/x-ext/", "").split("/");
353
+ const extKey = parts.shift();
354
+ if (extKey) {
355
+ let refObj = extensions[extKey];
356
+ for (const p of parts) if (refObj && typeof refObj === "object" && p in refObj) refObj = refObj[p];
357
+ else {
358
+ refObj = void 0;
359
+ break;
360
+ }
361
+ if (refObj) return replaceRefs(scrub(refObj));
362
+ }
363
+ }
364
+ }
365
+ const result$1 = {};
366
+ for (const [key, value] of Object.entries(record)) result$1[key] = replaceRefs(value);
367
+ return result$1;
368
+ }
369
+ return obj;
370
+ }
371
+ const result = {};
372
+ for (const [key, value] of Object.entries(data)) if (key !== "x-ext") result[key] = replaceRefs(value);
373
+ return result;
374
+ }
389
375
 
390
376
  //#endregion
391
377
  //#region src/utils/execute-hook.ts
@@ -409,123 +395,6 @@ async function executeObjectCommand(command, args) {
409
395
  else if (isFunction(command.command)) await command.command();
410
396
  }
411
397
 
412
- //#endregion
413
- //#region src/utils/request.ts
414
- const request = (urlOptions, data) => {
415
- return new Promise((resolve, reject) => {
416
- const req = https.request(urlOptions, (res) => {
417
- let body = "";
418
- res.on("data", (chunk) => body += chunk.toString());
419
- res.on("error", reject);
420
- res.on("end", () => {
421
- const response = {
422
- status: res.statusCode,
423
- headers: res.headers,
424
- body: JSON.parse(body)
425
- };
426
- if (res.statusCode && res.statusCode >= 200 && res.statusCode <= 299) resolve(response);
427
- else reject(response);
428
- });
429
- });
430
- req.on("error", reject);
431
- if (data) req.write(data, "binary");
432
- req.end();
433
- });
434
- };
435
-
436
- //#endregion
437
- //#region src/utils/github.ts
438
- const getGithubSpecReq = ({ accessToken, repo, owner, branch, path: path$1 }) => {
439
- const payload = JSON.stringify({ query: `query {
440
- repository(name: "${repo}", owner: "${owner}") {
441
- object(expression: "${branch}:${path$1}") {
442
- ... on Blob {
443
- text
444
- }
445
- }
446
- }
447
- }` });
448
- return [{
449
- method: "POST",
450
- hostname: "api.github.com",
451
- path: "/graphql",
452
- headers: {
453
- "content-type": "application/json",
454
- "user-agent": "orval-importer",
455
- authorization: `bearer ${accessToken}`,
456
- "Content-Length": payload.length
457
- }
458
- }, payload];
459
- };
460
- let githubToken = null;
461
- const getGithubAcessToken = async (githubTokenPath) => {
462
- if (githubToken) return githubToken;
463
- if (await fs.pathExists(githubTokenPath)) return fs.readFile(githubTokenPath, "utf8");
464
- else {
465
- const answers = await enquirer.prompt([{
466
- type: "input",
467
- name: "githubToken",
468
- message: "Please provide a GitHub token with `repo` rules checked (https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)"
469
- }, {
470
- type: "confirm",
471
- name: "saveToken",
472
- message: "Would you like to store your token for the next time? (stored in your node_modules)"
473
- }]);
474
- githubToken = answers.githubToken;
475
- if (answers.saveToken) await fs.outputFile(githubTokenPath, answers.githubToken);
476
- return answers.githubToken;
477
- }
478
- };
479
- const getGithubOpenApi = async (url) => {
480
- const githubTokenPath = upath.join(import.meta.dirname, ".githubToken");
481
- const accessToken = await getGithubAcessToken(githubTokenPath);
482
- const [info] = url.split("github.com/").slice(-1);
483
- const [owner, repo, , branch, ...paths] = info.split("/");
484
- const path$1 = paths.join("/");
485
- try {
486
- const { body } = await request(...getGithubSpecReq({
487
- accessToken,
488
- repo,
489
- owner,
490
- branch,
491
- path: path$1
492
- }));
493
- if (body.errors?.length) {
494
- if (body.errors?.some((error) => error?.type === "NOT_FOUND")) {
495
- if ((await enquirer.prompt([{
496
- type: "confirm",
497
- name: "removeToken",
498
- message: "Your token doesn't have the correct permissions, should we remove it?"
499
- }])).removeToken) await fs.unlink(githubTokenPath);
500
- }
501
- }
502
- return body.data?.repository?.object.text;
503
- } catch (error) {
504
- if (!error.body) throw new Error(`Oups... 🍻. ${error}`);
505
- if (error.body.message === "Bad credentials") {
506
- if ((await enquirer.prompt([{
507
- type: "confirm",
508
- name: "removeToken",
509
- message: "Your token doesn't have the correct permissions, should we remove it?"
510
- }])).removeToken) await fs.unlink(githubTokenPath);
511
- }
512
- throw new Error(error.body.message || `Oups... 🍻. ${error}`);
513
- }
514
- };
515
- const githubResolver = {
516
- order: 199,
517
- canRead(file) {
518
- return file.url.includes("github.com");
519
- },
520
- read(file) {
521
- return getGithubOpenApi(file.url);
522
- }
523
- };
524
-
525
- //#endregion
526
- //#region src/utils/http-resolver.ts
527
- const httpResolver = { safeUrlResolver: false };
528
-
529
398
  //#endregion
530
399
  //#region src/utils/package-json.ts
531
400
  const loadPackageJson = async (packageJson, workspace = process.cwd()) => {
@@ -621,7 +490,7 @@ const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
621
490
  function defineConfig(options) {
622
491
  return options;
623
492
  }
624
- const createFormData = (workspace, formData) => {
493
+ function createFormData(workspace, formData) {
625
494
  const defaultArrayHandling = FormDataArrayHandling.SERIALIZE;
626
495
  if (formData === void 0) return {
627
496
  disabled: false,
@@ -646,8 +515,17 @@ const createFormData = (workspace, formData) => {
646
515
  mutator: normalizeMutator(workspace, formData),
647
516
  arrayHandling: defaultArrayHandling
648
517
  };
649
- };
650
- const normalizeOptions = async (optionsExport, workspace = process.cwd(), globalOptions = {}) => {
518
+ }
519
+ function normalizeSchemasOption(schemas, workspace) {
520
+ if (!schemas) return;
521
+ if (isString(schemas)) return normalizePath(schemas, workspace);
522
+ const types = Array.isArray(schemas.type) ? schemas.type : [schemas.type];
523
+ return {
524
+ path: normalizePath(schemas.path, workspace),
525
+ type: types
526
+ };
527
+ }
528
+ async function normalizeOptions(optionsExport, workspace = process.cwd(), globalOptions = {}) {
651
529
  const options = await (isFunction(optionsExport) ? optionsExport() : optionsExport);
652
530
  if (!options.input) throw new Error(chalk.red(`Config require an input`));
653
531
  if (!options.output) throw new Error(chalk.red(`Config require an output`));
@@ -680,15 +558,13 @@ const normalizeOptions = async (optionsExport, workspace = process.cwd(), global
680
558
  const normalizedOptions = {
681
559
  input: {
682
560
  target: globalOptions.input ? normalizePathOrUrl(globalOptions.input, process.cwd()) : normalizePathOrUrl(inputOptions.target, workspace),
683
- validation: inputOptions.validation || false,
684
561
  override: { transformer: normalizePath(inputOptions.override?.transformer, workspace) },
685
- converterOptions: inputOptions.converterOptions ?? {},
686
- parserOptions: mergeDeep(parserDefaultOptions, inputOptions.parserOptions ?? {}),
687
- filters: inputOptions.filters
562
+ filters: inputOptions.filters,
563
+ parserOptions: inputOptions.parserOptions
688
564
  },
689
565
  output: {
690
566
  target: globalOptions.output ? normalizePath(globalOptions.output, process.cwd()) : normalizePath(outputOptions.target, outputWorkspace),
691
- schemas: normalizePath(outputOptions.schemas, outputWorkspace),
567
+ schemas: normalizeSchemasOption(outputOptions.schemas, outputWorkspace),
692
568
  namingConvention: outputOptions.namingConvention || NamingConvention.CAMEL_CASE,
693
569
  fileExtension: outputOptions.fileExtension || defaultFileExtension,
694
570
  workspace: outputOptions.workspace ? outputWorkspace : void 0,
@@ -785,12 +661,15 @@ const normalizeOptions = async (optionsExport, workspace = process.cwd(), global
785
661
  fetch: {
786
662
  includeHttpResponseReturnType: outputOptions.override?.fetch?.includeHttpResponseReturnType ?? true,
787
663
  forceSuccessResponse: outputOptions.override?.fetch?.forceSuccessResponse ?? false,
664
+ useZodSchemaResponse: outputOptions.override?.fetch?.useZodSchemaResponse ?? false,
665
+ runtimeValidation: outputOptions.override?.fetch?.runtimeValidation ?? false,
788
666
  ...outputOptions.override?.fetch
789
667
  },
790
668
  useDates: outputOptions.override?.useDates || false,
791
669
  useDeprecatedOperations: outputOptions.override?.useDeprecatedOperations ?? true,
792
670
  enumGenerationType: outputOptions.override?.enumGenerationType ?? "const",
793
- suppressReadonlyModifier: outputOptions.override?.suppressReadonlyModifier || false
671
+ suppressReadonlyModifier: outputOptions.override?.suppressReadonlyModifier || false,
672
+ aliasCombinedTypes: outputOptions.override?.aliasCombinedTypes ?? false
794
673
  },
795
674
  allParamsOptional: outputOptions.allParamsOptional ?? false,
796
675
  urlEncodeParameters: outputOptions.urlEncodeParameters ?? false,
@@ -802,15 +681,8 @@ const normalizeOptions = async (optionsExport, workspace = process.cwd(), global
802
681
  if (!normalizedOptions.input.target) throw new Error(chalk.red(`Config require an input target`));
803
682
  if (!normalizedOptions.output.target && !normalizedOptions.output.schemas) throw new Error(chalk.red(`Config require an output target or schemas`));
804
683
  return normalizedOptions;
805
- };
806
- const parserDefaultOptions = {
807
- validate: true,
808
- resolve: {
809
- github: githubResolver,
810
- http: httpResolver
811
- }
812
- };
813
- const normalizeMutator = (workspace, mutator) => {
684
+ }
685
+ function normalizeMutator(workspace, mutator) {
814
686
  if (isObject(mutator)) {
815
687
  if (!mutator.path) throw new Error(chalk.red(`Mutator need a path`));
816
688
  return {
@@ -824,16 +696,16 @@ const normalizeMutator = (workspace, mutator) => {
824
696
  default: true
825
697
  };
826
698
  return mutator;
827
- };
828
- const normalizePathOrUrl = (path$1, workspace) => {
699
+ }
700
+ function normalizePathOrUrl(path$1, workspace) {
829
701
  if (isString(path$1) && !isUrl(path$1)) return normalizePath(path$1, workspace);
830
702
  return path$1;
831
- };
832
- const normalizePath = (path$1, workspace) => {
703
+ }
704
+ function normalizePath(path$1, workspace) {
833
705
  if (!isString(path$1)) return path$1;
834
706
  return upath.resolve(workspace, path$1);
835
- };
836
- const normalizeOperationsAndTags = (operationsOrTags, workspace, global) => {
707
+ }
708
+ function normalizeOperationsAndTags(operationsOrTags, workspace, global) {
837
709
  return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, query: query$1, zod: zod$1, ...rest }]) => {
838
710
  return [key, {
839
711
  ...rest,
@@ -878,16 +750,16 @@ const normalizeOperationsAndTags = (operationsOrTags, workspace, global) => {
878
750
  ...paramsSerializer ? { paramsSerializer: normalizeMutator(workspace, paramsSerializer) } : {}
879
751
  }];
880
752
  }));
881
- };
882
- const normalizeOutputMode = (mode) => {
753
+ }
754
+ function normalizeOutputMode(mode) {
883
755
  if (!mode) return OutputMode.SINGLE;
884
756
  if (!Object.values(OutputMode).includes(mode)) {
885
757
  createLogger().warn(chalk.yellow(`Unknown the provided mode => ${mode}`));
886
758
  return OutputMode.SINGLE;
887
759
  }
888
760
  return mode;
889
- };
890
- const normalizeHooks = (hooks) => {
761
+ }
762
+ function normalizeHooks(hooks) {
891
763
  return Object.keys(hooks).reduce((acc, key) => {
892
764
  if (isString(hooks[key])) return {
893
765
  ...acc,
@@ -907,19 +779,19 @@ const normalizeHooks = (hooks) => {
907
779
  };
908
780
  return acc;
909
781
  }, {});
910
- };
911
- const normalizeHonoOptions = (hono$1 = {}, workspace) => {
782
+ }
783
+ function normalizeHonoOptions(hono$1 = {}, workspace) {
912
784
  return {
913
785
  ...hono$1.handlers ? { handlers: upath.resolve(workspace, hono$1.handlers) } : {},
914
786
  compositeRoute: hono$1.compositeRoute ?? "",
915
787
  validator: hono$1.validator ?? true,
916
788
  validatorOutputPath: hono$1.validatorOutputPath ? upath.resolve(workspace, hono$1.validatorOutputPath) : ""
917
789
  };
918
- };
919
- const normalizeJSDocOptions = (jsdoc = {}) => {
790
+ }
791
+ function normalizeJSDocOptions(jsdoc = {}) {
920
792
  return { ...jsdoc };
921
- };
922
- const normalizeQueryOptions = (queryOptions = {}, outputWorkspace, globalOptions = {}) => {
793
+ }
794
+ function normalizeQueryOptions(queryOptions = {}, outputWorkspace, globalOptions = {}) {
923
795
  if (queryOptions.options) console.warn("[WARN] Using query options is deprecated and will be removed in a future major release. Please use queryOptions or mutationOptions instead.");
924
796
  return {
925
797
  ...isUndefined(queryOptions.usePrefetch) ? {} : { usePrefetch: queryOptions.usePrefetch },
@@ -953,14 +825,16 @@ const normalizeQueryOptions = (queryOptions = {}, outputWorkspace, globalOptions
953
825
  ...isUndefined(globalOptions.version) ? {} : { version: globalOptions.version },
954
826
  ...isUndefined(queryOptions.version) ? {} : { version: queryOptions.version }
955
827
  };
956
- };
957
- const getDefaultFilesHeader = ({ title, description: description$1, version: version$1 } = {}) => [
958
- `Generated by ${name} v${version} 🍺`,
959
- `Do not edit manually.`,
960
- ...title ? [title] : [],
961
- ...description$1 ? [description$1] : [],
962
- ...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
963
- ];
828
+ }
829
+ function getDefaultFilesHeader({ title, description: description$1, version: version$1 } = {}) {
830
+ return [
831
+ `Generated by ${name} v${version} 🍺`,
832
+ `Do not edit manually.`,
833
+ ...title ? [title] : [],
834
+ ...description$1 ? [description$1] : [],
835
+ ...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
836
+ ];
837
+ }
964
838
 
965
839
  //#endregion
966
840
  //#region src/utils/watcher.ts
@@ -997,60 +871,169 @@ async function startWatcher(watchOptions, watchFn, defaultTarget = ".") {
997
871
  });
998
872
  }
999
873
 
874
+ //#endregion
875
+ //#region src/write-zod-specs.ts
876
+ function generateZodSchemaFileContent(header, schemaName, zodContent) {
877
+ return `${header}import { z as zod } from 'zod';
878
+
879
+ export const ${schemaName} = ${zodContent}
880
+
881
+ export type ${schemaName} = zod.infer<typeof ${schemaName}>;
882
+ `;
883
+ }
884
+ async function writeZodSchemaIndex(schemasPath, fileExtension, header, schemaNames, namingConvention, shouldMergeExisting = false) {
885
+ const importFileExtension = fileExtension.replace(/\.ts$/, "");
886
+ const indexPath = upath.join(schemasPath, `index${fileExtension}`);
887
+ let existingExports = "";
888
+ if (shouldMergeExisting && await fs.pathExists(indexPath)) {
889
+ const existingContent = await fs.readFile(indexPath, "utf-8");
890
+ const headerMatch = existingContent.match(/^(\/\*\*[\s\S]*?\*\/\n)?/);
891
+ const headerPart = headerMatch ? headerMatch[0] : "";
892
+ existingExports = existingContent.substring(headerPart.length).trim();
893
+ }
894
+ const newExports = schemaNames.map((schemaName) => {
895
+ return `export * from './${conventionName(schemaName, namingConvention)}${importFileExtension}';`;
896
+ }).sort().join("\n");
897
+ const allExports = existingExports ? `${existingExports}\n${newExports}` : newExports;
898
+ const uniqueExports = [...new Set(allExports.split("\n"))].filter((line) => line.trim()).sort().join("\n");
899
+ await fs.outputFile(indexPath, `${header}\n${uniqueExports}\n`);
900
+ }
901
+ async function writeZodSchemas(builder, schemasPath, fileExtension, header, output) {
902
+ const schemasWithOpenApiDef = builder.schemas.filter((s) => s.schema);
903
+ await Promise.all(schemasWithOpenApiDef.map(async (generatorSchema) => {
904
+ const { name: name$1, schema: schemaObject } = generatorSchema;
905
+ if (!schemaObject) return;
906
+ const fileName = conventionName(name$1, output.namingConvention);
907
+ const filePath = upath.join(schemasPath, `${fileName}${fileExtension}`);
908
+ const context = {
909
+ spec: builder.spec,
910
+ target: builder.target,
911
+ workspace: "",
912
+ output
913
+ };
914
+ const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
915
+ const strict = typeof output.override?.zod?.strict === "object" ? output.override.zod.strict.body ?? false : output.override?.zod?.strict ?? false;
916
+ const coerce = typeof output.override?.zod?.coerce === "object" ? output.override.zod.coerce.body ?? false : output.override?.zod?.coerce ?? false;
917
+ const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name$1, strict, isZodV4, { required: true }), context, coerce, strict, isZodV4);
918
+ const fileContent = generateZodSchemaFileContent(header, name$1, parsedZodDefinition.consts ? `${parsedZodDefinition.consts}\n${parsedZodDefinition.zod}` : parsedZodDefinition.zod);
919
+ await fs.outputFile(filePath, fileContent);
920
+ }));
921
+ if (output.indexFiles) await writeZodSchemaIndex(schemasPath, fileExtension, header, schemasWithOpenApiDef.map((schema) => schema.name), output.namingConvention, false);
922
+ }
923
+ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension, header, output, context) {
924
+ const verbOptionsArray = Object.values(verbOptions);
925
+ if (verbOptionsArray.length === 0) return;
926
+ const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
927
+ const strict = typeof output.override?.zod?.strict === "object" ? output.override.zod.strict.body ?? false : output.override?.zod?.strict ?? false;
928
+ const coerce = typeof output.override?.zod?.coerce === "object" ? output.override.zod.coerce.body ?? false : output.override?.zod?.coerce ?? false;
929
+ const generateVerbsSchemas = verbOptionsArray.flatMap((verbOption) => {
930
+ const operation = verbOption.originalOperation;
931
+ const bodySchema = operation.requestBody && "content" in operation.requestBody ? operation.requestBody.content["application/json"]?.schema : void 0;
932
+ const bodySchemas = bodySchema ? [{
933
+ name: `${pascal(verbOption.operationName)}Body`,
934
+ schema: dereference(bodySchema, context)
935
+ }] : [];
936
+ const queryParams = operation.parameters?.filter((p) => "in" in p && p.in === "query");
937
+ const queryParamsSchemas = queryParams && queryParams.length > 0 ? [{
938
+ name: `${pascal(verbOption.operationName)}Params`,
939
+ schema: {
940
+ type: "object",
941
+ properties: Object.fromEntries(queryParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, dereference(p.schema, context)])),
942
+ required: queryParams.filter((p) => p.required).map((p) => p.name)
943
+ }
944
+ }] : [];
945
+ const headerParams = operation.parameters?.filter((p) => "in" in p && p.in === "header");
946
+ const headerParamsSchemas = headerParams && headerParams.length > 0 ? [{
947
+ name: `${pascal(verbOption.operationName)}Headers`,
948
+ schema: {
949
+ type: "object",
950
+ properties: Object.fromEntries(headerParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, dereference(p.schema, context)])),
951
+ required: headerParams.filter((p) => p.required).map((p) => p.name)
952
+ }
953
+ }] : [];
954
+ return [
955
+ ...bodySchemas,
956
+ ...queryParamsSchemas,
957
+ ...headerParamsSchemas
958
+ ];
959
+ });
960
+ await Promise.all(generateVerbsSchemas.map(async ({ name: name$1, schema }) => {
961
+ const fileName = conventionName(name$1, output.namingConvention);
962
+ const filePath = upath.join(schemasPath, `${fileName}${fileExtension}`);
963
+ const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(schema, context, name$1, strict, isZodV4, { required: true }), context, coerce, strict, isZodV4);
964
+ const fileContent = generateZodSchemaFileContent(header, name$1, parsedZodDefinition.consts ? `${parsedZodDefinition.consts}\n${parsedZodDefinition.zod}` : parsedZodDefinition.zod);
965
+ await fs.outputFile(filePath, fileContent);
966
+ }));
967
+ if (output.indexFiles && generateVerbsSchemas.length > 0) await writeZodSchemaIndex(schemasPath, fileExtension, header, generateVerbsSchemas.map((s) => s.name), output.namingConvention, true);
968
+ }
969
+
1000
970
  //#endregion
1001
971
  //#region src/write-specs.ts
1002
- const getHeader = (option, info) => {
972
+ function getHeader(option, info) {
1003
973
  if (!option) return "";
1004
974
  const header = option(info);
1005
975
  return Array.isArray(header) ? jsDoc({ description: header }) : header;
1006
- };
1007
- const writeSpecs = async (builder, workspace, options, projectName) => {
976
+ }
977
+ async function writeSpecs(builder, workspace, options, projectName) {
1008
978
  const { info = {
1009
979
  title: "",
1010
980
  version: 0
1011
981
  }, schemas, target } = builder;
1012
982
  const { output } = options;
1013
983
  const projectTitle = projectName ?? info.title;
1014
- const specsName = Object.keys(schemas).reduce((acc, specKey) => {
1015
- acc[specKey] = upath.getSpecName(specKey, target).slice(1).split("/").join("-");
1016
- return acc;
1017
- }, {});
1018
984
  const header = getHeader(output.override.header, info);
1019
- if (output.schemas) {
1020
- const rootSchemaPath = output.schemas;
1021
- const fileExtension = [
1022
- "tags",
1023
- "tags-split",
1024
- "split"
1025
- ].includes(output.mode) ? ".ts" : output.fileExtension ?? ".ts";
1026
- await Promise.all(Object.entries(schemas).map(([specKey, schemas$1]) => {
1027
- return writeSchemas({
1028
- schemaPath: isRootKey(specKey, target) ? rootSchemaPath : upath.join(rootSchemaPath, specsName[specKey]),
1029
- schemas: schemas$1,
985
+ if (output.schemas) if (isString(output.schemas)) {
986
+ const fileExtension = output.fileExtension || ".ts";
987
+ const schemaPath = output.schemas;
988
+ await writeSchemas({
989
+ schemaPath,
990
+ schemas,
991
+ target,
992
+ namingConvention: output.namingConvention,
993
+ fileExtension,
994
+ header,
995
+ indexFiles: output.indexFiles
996
+ });
997
+ } else {
998
+ const types = Array.isArray(output.schemas.type) ? output.schemas.type : [output.schemas.type];
999
+ for (const schemaType of types) if (schemaType === "typescript") {
1000
+ const fileExtension = output.fileExtension || ".ts";
1001
+ await writeSchemas({
1002
+ schemaPath: output.schemas.path,
1003
+ schemas,
1030
1004
  target,
1031
1005
  namingConvention: output.namingConvention,
1032
1006
  fileExtension,
1033
- specsName,
1034
- specKey,
1035
- isRootKey: isRootKey(specKey, target),
1036
1007
  header,
1037
1008
  indexFiles: output.indexFiles
1038
1009
  });
1039
- }));
1010
+ } else if (schemaType === "zod") {
1011
+ const fileExtension = ".zod.ts";
1012
+ await writeZodSchemas(builder, output.schemas.path, fileExtension, header, output);
1013
+ if (builder.verbOptions) await writeZodSchemasFromVerbs(builder.verbOptions, output.schemas.path, fileExtension, header, output, {
1014
+ spec: builder.spec,
1015
+ target: builder.target,
1016
+ workspace,
1017
+ output
1018
+ });
1019
+ }
1040
1020
  }
1041
1021
  let implementationPaths = [];
1042
1022
  if (output.target) implementationPaths = await getWriteMode(output.mode)({
1043
1023
  builder,
1044
1024
  workspace,
1045
1025
  output,
1046
- specsName,
1026
+ projectName,
1047
1027
  header,
1048
1028
  needSchema: !output.schemas && output.client !== "zod"
1049
1029
  });
1050
1030
  if (output.workspace) {
1051
1031
  const workspacePath = output.workspace;
1052
1032
  const imports = implementationPaths.filter((path$1) => !output.mock || !path$1.endsWith(`.${getMockFileExtensionByTypeName(output.mock)}.ts`)).map((path$1) => upath.relativeSafe(workspacePath, getFileInfo(path$1).pathWithoutExtension));
1053
- if (output.schemas) imports.push(upath.relativeSafe(workspacePath, getFileInfo(output.schemas).dirname));
1033
+ if (output.schemas) {
1034
+ const schemasPath = typeof output.schemas === "string" ? output.schemas : output.schemas.path;
1035
+ imports.push(upath.relativeSafe(workspacePath, getFileInfo(schemasPath).dirname));
1036
+ }
1054
1037
  if (output.indexFiles) {
1055
1038
  const indexFile = upath.join(workspacePath, "/index.ts");
1056
1039
  if (await fs.pathExists(indexFile)) {
@@ -1065,7 +1048,7 @@ const writeSpecs = async (builder, workspace, options, projectName) => {
1065
1048
  await Promise.all(builder.extraFiles.map(async (file) => fs.outputFile(file.path, file.content)));
1066
1049
  implementationPaths = [...implementationPaths, ...builder.extraFiles.map((file) => file.path)];
1067
1050
  }
1068
- const paths = [...output.schemas ? [getFileInfo(output.schemas).dirname] : [], ...implementationPaths];
1051
+ const paths = [...output.schemas ? [getFileInfo(typeof output.schemas === "string" ? output.schemas : output.schemas.path).dirname] : [], ...implementationPaths];
1069
1052
  if (options.hooks.afterAllFilesWrite) await executeHook("afterAllFilesWrite", options.hooks.afterAllFilesWrite, paths);
1070
1053
  if (output.prettier) try {
1071
1054
  await execa("prettier", ["--write", ...paths]);
@@ -1110,8 +1093,8 @@ const writeSpecs = async (builder, workspace, options, projectName) => {
1110
1093
  log(chalk.yellow(message));
1111
1094
  }
1112
1095
  createSuccessMessage(projectTitle);
1113
- };
1114
- const getWriteMode = (mode) => {
1096
+ }
1097
+ function getWriteMode(mode) {
1115
1098
  switch (mode) {
1116
1099
  case OutputMode.SPLIT: return writeSplitMode;
1117
1100
  case OutputMode.TAGS: return writeTagsMode;
@@ -1119,7 +1102,7 @@ const getWriteMode = (mode) => {
1119
1102
  case OutputMode.SINGLE:
1120
1103
  default: return writeSingleMode;
1121
1104
  }
1122
- };
1105
+ }
1123
1106
 
1124
1107
  //#endregion
1125
1108
  //#region src/generate-spec.ts
@@ -1149,7 +1132,7 @@ async function generateSpec(workspace, options, projectName) {
1149
1132
  ], getFileInfo(options.output.schemas).dirname);
1150
1133
  log(`${projectName} Cleaning output folder`);
1151
1134
  }
1152
- await writeSpecs(await importSpecs(workspace, options), workspace, options, projectName);
1135
+ await writeSpecs(await importSpecs(workspace, options, projectName), workspace, options, projectName);
1153
1136
  }
1154
1137
 
1155
1138
  //#endregion
@@ -1205,4 +1188,4 @@ async function loadConfigFile(configFilePath) {
1205
1188
 
1206
1189
  //#endregion
1207
1190
  export { defineConfig as a, name as c, startWatcher as i, version as l, loadConfigFile as n, normalizeOptions as o, generateSpec as r, description as s, findConfigFile as t };
1208
- //# sourceMappingURL=config-BH1mpZT1.mjs.map
1191
+ //# sourceMappingURL=config-DQWtHEeF.mjs.map