orval 8.0.0-rc.1 → 8.0.0-rc.3

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,12 +1,8 @@
1
- import { ErrorWithTag, 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";
2
- import fs from "node:fs";
3
1
  import path from "node:path";
4
- import process$1 from "node:process";
5
- import { createJiti } from "jiti";
6
- import SwaggerParser from "@apidevtools/swagger-parser";
7
- import chalk from "chalk";
8
- import fs$1 from "fs-extra";
9
- import yaml from "js-yaml";
2
+ import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMode, PropertySortOrder, RefComponentSuffix, asyncReduce, 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";
10
6
  import * as mock from "@orval/mock";
11
7
  import { DEFAULT_MOCK_OPTIONS, generateMockImports } from "@orval/mock";
12
8
  import angular from "@orval/angular";
@@ -17,14 +13,23 @@ import mcp from "@orval/mcp";
17
13
  import query from "@orval/query";
18
14
  import swr from "@orval/swr";
19
15
  import zod from "@orval/zod";
20
- import https from "node:https";
21
- import enquirer from "enquirer";
22
- import { findUp } from "find-up";
23
- import { parse } from "tsconfck";
16
+ import chalk from "chalk";
24
17
  import { ExecaError, execa } from "execa";
18
+ import fs from "fs-extra";
25
19
  import { unique } from "remeda";
26
20
  import { parseArgsStringToArgv } from "string-argv";
21
+ import { findUp } from "find-up";
22
+ import yaml from "js-yaml";
23
+ import { parse } from "tsconfck";
24
+ import fs$1 from "node:fs";
25
+ import { createJiti } from "jiti";
26
+
27
+ //#region package.json
28
+ var name = "orval";
29
+ var description = "A swagger client generator for typescript";
30
+ var version = "8.0.0-rc.3";
27
31
 
32
+ //#endregion
28
33
  //#region src/client.ts
29
34
  const DEFAULT_CLIENT = OutputClient.AXIOS;
30
35
  const getGeneratorClient = (outputClient, output) => {
@@ -54,9 +59,9 @@ const getGeneratorClient = (outputClient, output) => {
54
59
  if (!generator) throw new Error(`Oups... 🍻. Client not found: ${outputClient}`);
55
60
  return generator;
56
61
  };
57
- 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 }) => {
58
63
  const { dependencies } = getGeneratorClient(client, output);
59
- 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);
60
65
  };
61
66
  const generateClientHeader = ({ outputClient = DEFAULT_CLIENT, isRequestOptions, isGlobalMutator, isMutator, provideIn, hasAwaitedType, titles, output, verbOptions, tag, clientImplementation }) => {
62
67
  const { header } = getGeneratorClient(outputClient, output);
@@ -167,18 +172,13 @@ const generateExtraFiles = (outputClient = DEFAULT_CLIENT, verbsOptions, output,
167
172
 
168
173
  //#endregion
169
174
  //#region src/api.ts
170
- const getApiBuilder = async ({ input, output, context }) => {
171
- 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]) => {
172
177
  const route = getRoute(pathRoute);
173
178
  let resolvedVerbs = verbs;
174
- let resolvedContext = context;
175
179
  if (isReference(verbs)) {
176
180
  const { schema, imports } = resolveRef(verbs, context);
177
181
  resolvedVerbs = schema;
178
- resolvedContext = {
179
- ...context,
180
- ...imports.length > 0 ? { specKey: imports[0].specKey } : {}
181
- };
182
182
  }
183
183
  let verbsOptions = await generateVerbsOptions({
184
184
  verbs: resolvedVerbs,
@@ -186,7 +186,7 @@ const getApiBuilder = async ({ input, output, context }) => {
186
186
  output,
187
187
  route,
188
188
  pathRoute,
189
- context: resolvedContext
189
+ context
190
190
  });
191
191
  if (output.override.useDeprecatedOperations === false) verbsOptions = verbsOptions.filter((verb) => {
192
192
  return !verb.deprecated;
@@ -198,13 +198,13 @@ const getApiBuilder = async ({ input, output, context }) => {
198
198
  acc$1.push(...body.schemas, ...response.schemas);
199
199
  return acc$1;
200
200
  }, []);
201
- 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);
202
202
  if (!output.target) throw new Error("Output does not have a target");
203
203
  const pathOperations = await generateOperations(output.client, verbsOptions, {
204
204
  route: fullRoute,
205
205
  pathRoute,
206
206
  override: output.override,
207
- context: resolvedContext,
207
+ context,
208
208
  mock: output.mock,
209
209
  output: output.target
210
210
  }, output);
@@ -232,278 +232,167 @@ const getApiBuilder = async ({ input, output, context }) => {
232
232
  importsMock: generateMockImports,
233
233
  extraFiles
234
234
  };
235
- };
235
+ }
236
236
 
237
237
  //#endregion
238
238
  //#region src/import-open-api.ts
239
- const importOpenApi = async ({ data, input, output, target, workspace }) => {
240
- const specs = await generateInputSpecs({
241
- specs: data,
242
- input,
243
- workspace
244
- });
239
+ async function importOpenApi({ spec, input, output, target, workspace, projectName }) {
240
+ const transformedOpenApi = await applyTransformer(spec, input.override.transformer, workspace);
245
241
  const schemas = getApiSchemas({
246
242
  input,
247
243
  output,
248
244
  target,
249
245
  workspace,
250
- specs
246
+ spec: transformedOpenApi
251
247
  });
252
248
  const api = await getApiBuilder({
253
249
  input,
254
250
  output,
255
251
  context: {
256
- specKey: target,
252
+ projectName,
257
253
  target,
258
254
  workspace,
259
- specs,
255
+ spec: transformedOpenApi,
260
256
  output
261
257
  }
262
258
  });
263
259
  return {
264
260
  ...api,
265
- schemas: {
266
- ...schemas,
267
- [target]: [...schemas[target] ?? [], ...api.schemas]
268
- },
261
+ schemas: [...schemas, ...api.schemas],
269
262
  target,
270
- info: specs[target].info
263
+ info: transformedOpenApi.info
271
264
  };
272
- };
273
- const generateInputSpecs = async ({ specs, input, workspace }) => {
274
- const transformerFn = input.override?.transformer ? await dynamicImport(input.override.transformer, workspace) : void 0;
275
- return asyncReduce(Object.entries(specs), async (acc, [specKey, value]) => {
276
- const schema = await openApiConverter(value, input.converterOptions, specKey);
277
- const transfomedSchema = transformerFn ? transformerFn(schema) : schema;
278
- if (input.validation) await ibmOpenapiValidator(transfomedSchema, input.validation);
279
- acc[specKey] = transfomedSchema;
280
- return acc;
281
- }, {});
282
- };
283
- const getApiSchemas = ({ input, output, target, workspace, specs }) => {
284
- return Object.entries(specs).reduce((acc, [specKey, spec]) => {
285
- const context = {
286
- specKey,
287
- target,
288
- workspace,
289
- specs,
290
- output
291
- };
292
- const schemaDefinition = generateSchemasDefinition(spec.openapi ? spec.components?.schemas : getAllSchemas(spec, specKey), context, output.override.components.schemas.suffix, input.filters);
293
- const responseDefinition = generateComponentDefinition(spec.components?.responses, context, output.override.components.responses.suffix);
294
- const bodyDefinition = generateComponentDefinition(spec.components?.requestBodies, context, output.override.components.requestBodies.suffix);
295
- const parameters = generateParameterDefinition(spec.components?.parameters, context, output.override.components.parameters.suffix);
296
- const schemas = [
297
- ...schemaDefinition,
298
- ...responseDefinition,
299
- ...bodyDefinition,
300
- ...parameters
301
- ];
302
- if (schemas.length === 0) return acc;
303
- acc[specKey] = schemas;
304
- return acc;
305
- }, {});
306
- };
307
- const getAllSchemas = (spec, specKey) => {
308
- const keysToOmit = new Set([
309
- "openapi",
310
- "info",
311
- "servers",
312
- "paths",
313
- "components",
314
- "security",
315
- "tags",
316
- "externalDocs"
317
- ]);
318
- const cleanedSpec = Object.fromEntries(Object.entries(spec).filter(([key]) => !keysToOmit.has(key)));
319
- if (specKey && isSchema(cleanedSpec)) {
320
- const name$1 = upath.getSchemaFileName(specKey);
321
- const additionalKeysToOmit = new Set([
322
- "type",
323
- "properties",
324
- "allOf",
325
- "oneOf",
326
- "anyOf",
327
- "items"
328
- ]);
329
- return {
330
- [name$1]: cleanedSpec,
331
- ...getAllSchemas(Object.fromEntries(Object.entries(cleanedSpec).filter(([key]) => !additionalKeysToOmit.has(key))))
332
- };
333
- }
334
- return {
335
- ...Object.entries(cleanedSpec).reduce((acc, [key, value]) => {
336
- if (!isObject(value)) return acc;
337
- if (!isSchema(value) && !isReference(value)) return {
338
- ...acc,
339
- ...getAllSchemas(value)
340
- };
341
- acc[key] = value;
342
- return acc;
343
- }, {}),
344
- ...spec?.components?.schemas
265
+ }
266
+ async function applyTransformer(openApi, transformer, workspace) {
267
+ const transformerFn = transformer ? await dynamicImport(transformer, workspace) : void 0;
268
+ if (!transformerFn) return openApi;
269
+ const transformedOpenApi = transformerFn(openApi);
270
+ const { valid, errors } = await validate(transformedOpenApi);
271
+ if (!valid) throw new Error(`Validation failed`, { cause: errors });
272
+ return transformedOpenApi;
273
+ }
274
+ function getApiSchemas({ input, output, target, workspace, spec }) {
275
+ const context = {
276
+ target,
277
+ workspace,
278
+ spec,
279
+ output
345
280
  };
346
- };
281
+ const schemaDefinition = generateSchemasDefinition(spec.components?.schemas, context, output.override.components.schemas.suffix, input.filters);
282
+ const responseDefinition = generateComponentDefinition(spec.components?.responses, context, output.override.components.responses.suffix);
283
+ const bodyDefinition = generateComponentDefinition(spec.components?.requestBodies, context, output.override.components.requestBodies.suffix);
284
+ const parameters = generateParameterDefinition(spec.components?.parameters, context, output.override.components.parameters.suffix);
285
+ return [
286
+ ...schemaDefinition,
287
+ ...responseDefinition,
288
+ ...bodyDefinition,
289
+ ...parameters
290
+ ];
291
+ }
347
292
 
348
293
  //#endregion
349
294
  //#region src/import-specs.ts
350
- const resolveSpecs = async (path$1, { validate,...options }, _isUrl, isOnlySchema) => {
351
- try {
352
- if (validate) try {
353
- await SwaggerParser.validate(path$1, options);
354
- } catch (error) {
355
- if (error instanceof Error && error.name === "ParserError") throw error;
356
- if (!isOnlySchema) log(`⚠️ ${chalk.yellow(error)}`);
357
- }
358
- const data = (await SwaggerParser.resolve(path$1, options)).values();
359
- if (_isUrl) return data;
360
- return Object.fromEntries(Object.entries(data).sort().map(([key, value]) => [isUrl(key) ? key : upath.resolve(key), value]));
361
- } catch {
362
- const file = await fs$1.readFile(path$1, "utf8");
363
- return { [path$1]: yaml.load(file) };
364
- }
365
- };
366
- const importSpecs = async (workspace, options) => {
295
+ async function resolveSpec(input) {
296
+ const dereferencedData = dereferenceExternalRef(await bundle(input, {
297
+ plugins: [
298
+ readFiles(),
299
+ fetchUrls(),
300
+ parseJson(),
301
+ parseYaml()
302
+ ],
303
+ treeShake: true
304
+ }));
305
+ const { valid, errors } = await validate(dereferencedData);
306
+ if (!valid) throw new Error("Validation failed", { cause: errors });
307
+ const { specification } = upgrade(dereferencedData);
308
+ return specification;
309
+ }
310
+ async function importSpecs(workspace, options, projectName) {
367
311
  const { input, output } = options;
368
- if (!isString(input.target)) return importOpenApi({
369
- data: { [workspace]: input.target },
370
- input,
371
- output,
372
- target: workspace,
373
- workspace
374
- });
375
- const isPathUrl = isUrl(input.target);
376
312
  return importOpenApi({
377
- data: await resolveSpecs(input.target, input.parserOptions, isPathUrl, !output.target),
313
+ spec: await resolveSpec(input.target),
378
314
  input,
379
315
  output,
380
316
  target: input.target,
381
- workspace
382
- });
383
- };
384
-
385
- //#endregion
386
- //#region package.json
387
- var name = "orval";
388
- var description = "A swagger client generator for typescript";
389
- var version = "8.0.0-rc.1";
390
-
391
- //#endregion
392
- //#region src/utils/request.ts
393
- const request = (urlOptions, data) => {
394
- return new Promise((resolve, reject) => {
395
- const req = https.request(urlOptions, (res) => {
396
- let body = "";
397
- res.on("data", (chunk) => body += chunk.toString());
398
- res.on("error", reject);
399
- res.on("end", () => {
400
- const response = {
401
- status: res.statusCode,
402
- headers: res.headers,
403
- body: JSON.parse(body)
404
- };
405
- if (res.statusCode && res.statusCode >= 200 && res.statusCode <= 299) resolve(response);
406
- else reject(response);
407
- });
408
- });
409
- req.on("error", reject);
410
- if (data) req.write(data, "binary");
411
- req.end();
317
+ workspace,
318
+ projectName
412
319
  });
413
- };
414
-
415
- //#endregion
416
- //#region src/utils/github.ts
417
- const getGithubSpecReq = ({ accessToken, repo, owner, branch, path: path$1 }) => {
418
- const payload = JSON.stringify({ query: `query {
419
- repository(name: "${repo}", owner: "${owner}") {
420
- object(expression: "${branch}:${path$1}") {
421
- ... on Blob {
422
- text
423
- }
424
- }
425
- }
426
- }` });
427
- return [{
428
- method: "POST",
429
- hostname: "api.github.com",
430
- path: "/graphql",
431
- headers: {
432
- "content-type": "application/json",
433
- "user-agent": "orval-importer",
434
- authorization: `bearer ${accessToken}`,
435
- "Content-Length": payload.length
320
+ }
321
+ /**
322
+ * The plugins from `@scalar/json-magic` does not dereference $ref.
323
+ * Instead if fetches them and puts them under x-ext, and changes the $ref to point to #x-ext/<name>.
324
+ * This function dereferences those x-ext $ref's.
325
+ */
326
+ function dereferenceExternalRef(data) {
327
+ const extensions = data["x-ext"] ?? {};
328
+ const UNWANTED_KEYS = new Set(["$schema", "$id"]);
329
+ function scrub(obj) {
330
+ if (obj === null || obj === void 0) return obj;
331
+ if (Array.isArray(obj)) return obj.map((x) => scrub(x));
332
+ if (typeof obj === "object") {
333
+ const rec = obj;
334
+ const out = {};
335
+ for (const [k, v] of Object.entries(rec)) {
336
+ if (UNWANTED_KEYS.has(k)) continue;
337
+ out[k] = scrub(v);
338
+ }
339
+ return out;
436
340
  }
437
- }, payload];
438
- };
439
- let githubToken = null;
440
- const getGithubAcessToken = async (githubTokenPath) => {
441
- if (githubToken) return githubToken;
442
- if (await fs$1.pathExists(githubTokenPath)) return fs$1.readFile(githubTokenPath, "utf8");
443
- else {
444
- const answers = await enquirer.prompt([{
445
- type: "input",
446
- name: "githubToken",
447
- message: "Please provide a GitHub token with `repo` rules checked (https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)"
448
- }, {
449
- type: "confirm",
450
- name: "saveToken",
451
- message: "Would you like to store your token for the next time? (stored in your node_modules)"
452
- }]);
453
- githubToken = answers.githubToken;
454
- if (answers.saveToken) await fs$1.outputFile(githubTokenPath, answers.githubToken);
455
- return answers.githubToken;
341
+ return obj;
456
342
  }
457
- };
458
- const getGithubOpenApi = async (url) => {
459
- const githubTokenPath = upath.join(import.meta.dirname, ".githubToken");
460
- const accessToken = await getGithubAcessToken(githubTokenPath);
461
- const [info] = url.split("github.com/").slice(-1);
462
- const [owner, repo, , branch, ...paths] = info.split("/");
463
- const path$1 = paths.join("/");
464
- try {
465
- const { body } = await request(...getGithubSpecReq({
466
- accessToken,
467
- repo,
468
- owner,
469
- branch,
470
- path: path$1
471
- }));
472
- if (body.errors?.length) {
473
- if (body.errors?.some((error) => error?.type === "NOT_FOUND")) {
474
- if ((await enquirer.prompt([{
475
- type: "confirm",
476
- name: "removeToken",
477
- message: "Your token doesn't have the correct permissions, should we remove it?"
478
- }])).removeToken) await fs$1.unlink(githubTokenPath);
343
+ function replaceRefs(obj) {
344
+ if (obj === null || obj === void 0) return obj;
345
+ if (typeof obj === "object") {
346
+ if (Array.isArray(obj)) return obj.map((element) => replaceRefs(element));
347
+ const record = obj;
348
+ if ("$ref" in record && typeof record.$ref === "string") {
349
+ const refValue = record.$ref;
350
+ if (refValue.startsWith("#/x-ext/")) {
351
+ const parts = refValue.replace("#/x-ext/", "").split("/");
352
+ const extKey = parts.shift();
353
+ if (extKey) {
354
+ let refObj = extensions[extKey];
355
+ for (const p of parts) if (refObj && typeof refObj === "object" && p in refObj) refObj = refObj[p];
356
+ else {
357
+ refObj = void 0;
358
+ break;
359
+ }
360
+ if (refObj) return replaceRefs(scrub(refObj));
361
+ }
362
+ }
479
363
  }
364
+ const result$1 = {};
365
+ for (const [key, value] of Object.entries(record)) result$1[key] = replaceRefs(value);
366
+ return result$1;
480
367
  }
481
- return body.data?.repository?.object.text;
482
- } catch (error) {
483
- if (!error.body) throw new Error(`Oups... 🍻. ${error}`);
484
- if (error.body.message === "Bad credentials") {
485
- if ((await enquirer.prompt([{
486
- type: "confirm",
487
- name: "removeToken",
488
- message: "Your token doesn't have the correct permissions, should we remove it?"
489
- }])).removeToken) await fs$1.unlink(githubTokenPath);
490
- }
491
- throw new Error(error.body.message || `Oups... 🍻. ${error}`);
368
+ return obj;
492
369
  }
493
- };
494
- const githubResolver = {
495
- order: 199,
496
- canRead(file) {
497
- return file.url.includes("github.com");
498
- },
499
- read(file) {
500
- return getGithubOpenApi(file.url);
501
- }
502
- };
370
+ const result = {};
371
+ for (const [key, value] of Object.entries(data)) if (key !== "x-ext") result[key] = replaceRefs(value);
372
+ return result;
373
+ }
503
374
 
504
375
  //#endregion
505
- //#region src/utils/http-resolver.ts
506
- const httpResolver = { safeUrlResolver: false };
376
+ //#region src/utils/execute-hook.ts
377
+ const executeHook = async (name$1, commands = [], args = []) => {
378
+ log(chalk.white(`Running ${name$1} hook...`));
379
+ for (const command of commands) try {
380
+ if (isString(command)) await executeCommand(command, args);
381
+ else if (isFunction(command)) await command(args);
382
+ else if (isObject(command)) await executeObjectCommand(command, args);
383
+ } catch (error) {
384
+ logError(error, `Failed to run ${name$1} hook`);
385
+ }
386
+ };
387
+ async function executeCommand(command, args) {
388
+ const [cmd, ..._args] = [...parseArgsStringToArgv(command), ...args];
389
+ await execa(cmd, _args);
390
+ }
391
+ async function executeObjectCommand(command, args) {
392
+ if (command.injectGeneratedDirsAndFiles === false) args = [];
393
+ if (isString(command.command)) await executeCommand(command.command, args);
394
+ else if (isFunction(command.command)) await command.command();
395
+ }
507
396
 
508
397
  //#endregion
509
398
  //#region src/utils/package-json.ts
@@ -518,7 +407,7 @@ const loadPackageJson = async (packageJson, workspace = process.cwd()) => {
518
407
  return;
519
408
  }
520
409
  const normalizedPath = normalizePath(packageJson, workspace);
521
- if (fs$1.existsSync(normalizedPath)) {
410
+ if (fs.existsSync(normalizedPath)) {
522
411
  const pkg = await dynamicImport(normalizedPath);
523
412
  if (isPackageJson(pkg)) return await maybeReplaceCatalog(pkg, workspace);
524
413
  else throw new Error(`Invalid package.json file: ${normalizedPath}`);
@@ -536,7 +425,7 @@ const maybeReplaceCatalog = async (pkg, workspace) => {
536
425
  log(`⚠️ ${chalk.yellow("package.json contains pnpm catalog: in dependencies, but no pnpm-workspace.yaml was found.")}`);
537
426
  return pkg;
538
427
  }
539
- const file = await fs$1.readFile(filePath, "utf8");
428
+ const file = await fs.readFile(filePath, "utf8");
540
429
  const pnpmWorkspaceFile = yaml.load(file);
541
430
  performSubstitution(pkg.dependencies, pnpmWorkspaceFile);
542
431
  performSubstitution(pkg.devDependencies, pnpmWorkspaceFile);
@@ -582,7 +471,7 @@ const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
582
471
  }
583
472
  if (isString(tsconfig)) {
584
473
  const normalizedPath = normalizePath(tsconfig, workspace);
585
- if (fs$1.existsSync(normalizedPath)) {
474
+ if (fs.existsSync(normalizedPath)) {
586
475
  const config = await parse(normalizedPath);
587
476
  return config.referenced?.find(({ tsconfigFile }) => tsconfigFile === normalizedPath)?.tsconfig || config.tsconfig;
588
477
  }
@@ -600,7 +489,7 @@ const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
600
489
  function defineConfig(options) {
601
490
  return options;
602
491
  }
603
- const createFormData = (workspace, formData) => {
492
+ function createFormData(workspace, formData) {
604
493
  const defaultArrayHandling = FormDataArrayHandling.SERIALIZE;
605
494
  if (formData === void 0) return {
606
495
  disabled: false,
@@ -625,8 +514,8 @@ const createFormData = (workspace, formData) => {
625
514
  mutator: normalizeMutator(workspace, formData),
626
515
  arrayHandling: defaultArrayHandling
627
516
  };
628
- };
629
- const normalizeOptions = async (optionsExport, workspace = process.cwd(), globalOptions = {}) => {
517
+ }
518
+ async function normalizeOptions(optionsExport, workspace = process.cwd(), globalOptions = {}) {
630
519
  const options = await (isFunction(optionsExport) ? optionsExport() : optionsExport);
631
520
  if (!options.input) throw new Error(chalk.red(`Config require an input`));
632
521
  if (!options.output) throw new Error(chalk.red(`Config require an output`));
@@ -659,10 +548,7 @@ const normalizeOptions = async (optionsExport, workspace = process.cwd(), global
659
548
  const normalizedOptions = {
660
549
  input: {
661
550
  target: globalOptions.input ? normalizePathOrUrl(globalOptions.input, process.cwd()) : normalizePathOrUrl(inputOptions.target, workspace),
662
- validation: inputOptions.validation || false,
663
551
  override: { transformer: normalizePath(inputOptions.override?.transformer, workspace) },
664
- converterOptions: inputOptions.converterOptions ?? {},
665
- parserOptions: mergeDeep(parserDefaultOptions, inputOptions.parserOptions ?? {}),
666
552
  filters: inputOptions.filters
667
553
  },
668
554
  output: {
@@ -781,15 +667,8 @@ const normalizeOptions = async (optionsExport, workspace = process.cwd(), global
781
667
  if (!normalizedOptions.input.target) throw new Error(chalk.red(`Config require an input target`));
782
668
  if (!normalizedOptions.output.target && !normalizedOptions.output.schemas) throw new Error(chalk.red(`Config require an output target or schemas`));
783
669
  return normalizedOptions;
784
- };
785
- const parserDefaultOptions = {
786
- validate: true,
787
- resolve: {
788
- github: githubResolver,
789
- http: httpResolver
790
- }
791
- };
792
- const normalizeMutator = (workspace, mutator) => {
670
+ }
671
+ function normalizeMutator(workspace, mutator) {
793
672
  if (isObject(mutator)) {
794
673
  if (!mutator.path) throw new Error(chalk.red(`Mutator need a path`));
795
674
  return {
@@ -803,17 +682,17 @@ const normalizeMutator = (workspace, mutator) => {
803
682
  default: true
804
683
  };
805
684
  return mutator;
806
- };
807
- const normalizePathOrUrl = (path$1, workspace) => {
685
+ }
686
+ function normalizePathOrUrl(path$1, workspace) {
808
687
  if (isString(path$1) && !isUrl(path$1)) return normalizePath(path$1, workspace);
809
688
  return path$1;
810
- };
811
- const normalizePath = (path$1, workspace) => {
689
+ }
690
+ function normalizePath(path$1, workspace) {
812
691
  if (!isString(path$1)) return path$1;
813
692
  return upath.resolve(workspace, path$1);
814
- };
815
- const normalizeOperationsAndTags = (operationsOrTags, workspace, global) => {
816
- return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, query: query$1, zod: zod$1,...rest }]) => {
693
+ }
694
+ function normalizeOperationsAndTags(operationsOrTags, workspace, global) {
695
+ return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, query: query$1, zod: zod$1, ...rest }]) => {
817
696
  return [key, {
818
697
  ...rest,
819
698
  ...query$1 ? { query: normalizeQueryOptions(query$1, workspace, global.query) } : {},
@@ -857,16 +736,16 @@ const normalizeOperationsAndTags = (operationsOrTags, workspace, global) => {
857
736
  ...paramsSerializer ? { paramsSerializer: normalizeMutator(workspace, paramsSerializer) } : {}
858
737
  }];
859
738
  }));
860
- };
861
- const normalizeOutputMode = (mode) => {
739
+ }
740
+ function normalizeOutputMode(mode) {
862
741
  if (!mode) return OutputMode.SINGLE;
863
742
  if (!Object.values(OutputMode).includes(mode)) {
864
743
  createLogger().warn(chalk.yellow(`Unknown the provided mode => ${mode}`));
865
744
  return OutputMode.SINGLE;
866
745
  }
867
746
  return mode;
868
- };
869
- const normalizeHooks = (hooks) => {
747
+ }
748
+ function normalizeHooks(hooks) {
870
749
  return Object.keys(hooks).reduce((acc, key) => {
871
750
  if (isString(hooks[key])) return {
872
751
  ...acc,
@@ -886,19 +765,19 @@ const normalizeHooks = (hooks) => {
886
765
  };
887
766
  return acc;
888
767
  }, {});
889
- };
890
- const normalizeHonoOptions = (hono$1 = {}, workspace) => {
768
+ }
769
+ function normalizeHonoOptions(hono$1 = {}, workspace) {
891
770
  return {
892
771
  ...hono$1.handlers ? { handlers: upath.resolve(workspace, hono$1.handlers) } : {},
893
772
  compositeRoute: hono$1.compositeRoute ?? "",
894
773
  validator: hono$1.validator ?? true,
895
774
  validatorOutputPath: hono$1.validatorOutputPath ? upath.resolve(workspace, hono$1.validatorOutputPath) : ""
896
775
  };
897
- };
898
- const normalizeJSDocOptions = (jsdoc = {}) => {
776
+ }
777
+ function normalizeJSDocOptions(jsdoc = {}) {
899
778
  return { ...jsdoc };
900
- };
901
- const normalizeQueryOptions = (queryOptions = {}, outputWorkspace, globalOptions = {}) => {
779
+ }
780
+ function normalizeQueryOptions(queryOptions = {}, outputWorkspace, globalOptions = {}) {
902
781
  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.");
903
782
  return {
904
783
  ...isUndefined(queryOptions.usePrefetch) ? {} : { usePrefetch: queryOptions.usePrefetch },
@@ -932,76 +811,66 @@ const normalizeQueryOptions = (queryOptions = {}, outputWorkspace, globalOptions
932
811
  ...isUndefined(globalOptions.version) ? {} : { version: globalOptions.version },
933
812
  ...isUndefined(queryOptions.version) ? {} : { version: queryOptions.version }
934
813
  };
935
- };
936
- const getDefaultFilesHeader = ({ title, description: description$1, version: version$1 } = {}) => [
937
- `Generated by ${name} v${version} 🍺`,
938
- `Do not edit manually.`,
939
- ...title ? [title] : [],
940
- ...description$1 ? [description$1] : [],
941
- ...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
942
- ];
814
+ }
815
+ function getDefaultFilesHeader({ title, description: description$1, version: version$1 } = {}) {
816
+ return [
817
+ `Generated by ${name} v${version} 🍺`,
818
+ `Do not edit manually.`,
819
+ ...title ? [title] : [],
820
+ ...description$1 ? [description$1] : [],
821
+ ...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
822
+ ];
823
+ }
943
824
 
944
825
  //#endregion
945
826
  //#region src/utils/watcher.ts
946
- const startWatcher = async (watchOptions, watchFn, defaultTarget = ".") => {
827
+ /**
828
+ * Start a file watcher and invoke an async callback on file changes.
829
+ *
830
+ * If `watchOptions` is falsy the watcher is not started. Supported shapes:
831
+ * - boolean: when true the `defaultTarget` is watched
832
+ * - string: a single path to watch
833
+ * - string[]: an array of paths to watch
834
+ *
835
+ * @param watchOptions - false to disable watching, or a path/paths to watch
836
+ * @param watchFn - async callback executed on change events
837
+ * @param defaultTarget - path(s) to watch when `watchOptions` is `true` (default: '.')
838
+ * @returns Resolves once the watcher has been started (or immediately if disabled)
839
+ *
840
+ * @example
841
+ * await startWatcher(true, async () => { await buildProject(); }, 'src');
842
+ */
843
+ async function startWatcher(watchOptions, watchFn, defaultTarget = ".") {
947
844
  if (!watchOptions) return;
948
845
  const { watch } = await import("chokidar");
949
846
  const ignored = ["**/{.git,node_modules}/**"];
950
- const watchPaths = typeof watchOptions === "boolean" ? defaultTarget : Array.isArray(watchOptions) ? watchOptions.filter((path$1) => typeof path$1 === "string") : watchOptions;
847
+ const watchPaths = typeof watchOptions === "boolean" ? defaultTarget : watchOptions;
951
848
  log(`Watching for changes in ${Array.isArray(watchPaths) ? watchPaths.map((v) => "\"" + v + "\"").join(" | ") : "\"" + watchPaths + "\""}`);
952
849
  watch(watchPaths, {
953
850
  ignorePermissionErrors: true,
954
851
  ignored
955
- }).on("all", async (type, file) => {
852
+ }).on("all", (type, file) => {
956
853
  log(`Change detected: ${type} ${file}`);
957
- try {
958
- await watchFn();
959
- } catch (error) {
854
+ watchFn().catch((error) => {
960
855
  logError(error);
961
- }
856
+ });
962
857
  });
963
- };
964
-
965
- //#endregion
966
- //#region src/utils/execute-hook.ts
967
- const executeHook = async (name$1, commands = [], args = []) => {
968
- log(chalk.white(`Running ${name$1} hook...`));
969
- for (const command of commands) try {
970
- if (isString(command)) await executeCommand(command, args);
971
- else if (isFunction(command)) await command(args);
972
- else if (isObject(command)) await executeObjectCommand(command, args);
973
- } catch (error) {
974
- logError(error, `Failed to run ${name$1} hook`);
975
- }
976
- };
977
- async function executeCommand(command, args) {
978
- const [cmd, ..._args] = [...parseArgsStringToArgv(command), ...args];
979
- await execa(cmd, _args);
980
- }
981
- async function executeObjectCommand(command, args) {
982
- if (command.injectGeneratedDirsAndFiles === false) args = [];
983
- if (isString(command.command)) await executeCommand(command.command, args);
984
- else if (isFunction(command.command)) await command.command();
985
858
  }
986
859
 
987
860
  //#endregion
988
861
  //#region src/write-specs.ts
989
- const getHeader = (option, info) => {
862
+ function getHeader(option, info) {
990
863
  if (!option) return "";
991
864
  const header = option(info);
992
865
  return Array.isArray(header) ? jsDoc({ description: header }) : header;
993
- };
994
- const writeSpecs = async (builder, workspace, options, projectName) => {
866
+ }
867
+ async function writeSpecs(builder, workspace, options, projectName) {
995
868
  const { info = {
996
869
  title: "",
997
870
  version: 0
998
871
  }, schemas, target } = builder;
999
872
  const { output } = options;
1000
873
  const projectTitle = projectName ?? info.title;
1001
- const specsName = Object.keys(schemas).reduce((acc, specKey) => {
1002
- acc[specKey] = upath.getSpecName(specKey, target).slice(1).split("/").join("-");
1003
- return acc;
1004
- }, {});
1005
874
  const header = getHeader(output.override.header, info);
1006
875
  if (output.schemas) {
1007
876
  const rootSchemaPath = output.schemas;
@@ -1009,28 +878,23 @@ const writeSpecs = async (builder, workspace, options, projectName) => {
1009
878
  "tags",
1010
879
  "tags-split",
1011
880
  "split"
1012
- ].includes(output.mode) ? ".ts" : output.fileExtension ?? ".ts";
1013
- await Promise.all(Object.entries(schemas).map(([specKey, schemas$1]) => {
1014
- return writeSchemas({
1015
- schemaPath: isRootKey(specKey, target) ? rootSchemaPath : upath.join(rootSchemaPath, specsName[specKey]),
1016
- schemas: schemas$1,
1017
- target,
1018
- namingConvention: output.namingConvention,
1019
- fileExtension,
1020
- specsName,
1021
- specKey,
1022
- isRootKey: isRootKey(specKey, target),
1023
- header,
1024
- indexFiles: output.indexFiles
1025
- });
1026
- }));
881
+ ].includes(output.mode) ? ".ts" : output.fileExtension;
882
+ await writeSchemas({
883
+ schemaPath: rootSchemaPath,
884
+ schemas,
885
+ target,
886
+ namingConvention: output.namingConvention,
887
+ fileExtension,
888
+ header,
889
+ indexFiles: output.indexFiles
890
+ });
1027
891
  }
1028
892
  let implementationPaths = [];
1029
893
  if (output.target) implementationPaths = await getWriteMode(output.mode)({
1030
894
  builder,
1031
895
  workspace,
1032
896
  output,
1033
- specsName,
897
+ projectName,
1034
898
  header,
1035
899
  needSchema: !output.schemas && output.client !== "zod"
1036
900
  });
@@ -1040,16 +904,16 @@ const writeSpecs = async (builder, workspace, options, projectName) => {
1040
904
  if (output.schemas) imports.push(upath.relativeSafe(workspacePath, getFileInfo(output.schemas).dirname));
1041
905
  if (output.indexFiles) {
1042
906
  const indexFile = upath.join(workspacePath, "/index.ts");
1043
- if (await fs$1.pathExists(indexFile)) {
1044
- const data = await fs$1.readFile(indexFile, "utf8");
907
+ if (await fs.pathExists(indexFile)) {
908
+ const data = await fs.readFile(indexFile, "utf8");
1045
909
  const importsNotDeclared = imports.filter((imp) => !data.includes(imp));
1046
- await fs$1.appendFile(indexFile, unique(importsNotDeclared).map((imp) => `export * from '${imp}';\n`).join(""));
1047
- } else await fs$1.outputFile(indexFile, unique(imports).map((imp) => `export * from '${imp}';`).join("\n") + "\n");
910
+ await fs.appendFile(indexFile, unique(importsNotDeclared).map((imp) => `export * from '${imp}';\n`).join(""));
911
+ } else await fs.outputFile(indexFile, unique(imports).map((imp) => `export * from '${imp}';`).join("\n") + "\n");
1048
912
  implementationPaths = [indexFile, ...implementationPaths];
1049
913
  }
1050
914
  }
1051
915
  if (builder.extraFiles.length > 0) {
1052
- await Promise.all(builder.extraFiles.map(async (file) => fs$1.outputFile(file.path, file.content)));
916
+ await Promise.all(builder.extraFiles.map(async (file) => fs.outputFile(file.path, file.content)));
1053
917
  implementationPaths = [...implementationPaths, ...builder.extraFiles.map((file) => file.path)];
1054
918
  }
1055
919
  const paths = [...output.schemas ? [getFileInfo(output.schemas).dirname] : [], ...implementationPaths];
@@ -1097,8 +961,8 @@ const writeSpecs = async (builder, workspace, options, projectName) => {
1097
961
  log(chalk.yellow(message));
1098
962
  }
1099
963
  createSuccessMessage(projectTitle);
1100
- };
1101
- const getWriteMode = (mode) => {
964
+ }
965
+ function getWriteMode(mode) {
1102
966
  switch (mode) {
1103
967
  case OutputMode.SPLIT: return writeSplitMode;
1104
968
  case OutputMode.TAGS: return writeTagsMode;
@@ -1106,11 +970,22 @@ const getWriteMode = (mode) => {
1106
970
  case OutputMode.SINGLE:
1107
971
  default: return writeSingleMode;
1108
972
  }
1109
- };
973
+ }
1110
974
 
1111
975
  //#endregion
1112
- //#region src/generate.ts
1113
- const generateSpec = async (workspace, options, projectName) => {
976
+ //#region src/generate-spec.ts
977
+ /**
978
+ * Generate client/spec files for a single Orval project.
979
+ *
980
+ * @param workspace - Absolute or relative workspace path used to resolve imports.
981
+ * @param options - Normalized generation options for this project.
982
+ * @param projectName - Optional project name used in logging output.
983
+ * @returns A promise that resolves once generation (and optional cleaning) completes.
984
+ *
985
+ * @example
986
+ * await generateSpec(process.cwd(), normalizedOptions, 'my-project');
987
+ */
988
+ async function generateSpec(workspace, options, projectName) {
1114
989
  if (options.output.clean) {
1115
990
  const extraPatterns = Array.isArray(options.output.clean) ? options.output.clean : [];
1116
991
  if (options.output.target) await removeFilesAndEmptyFolders([
@@ -1123,44 +998,35 @@ const generateSpec = async (workspace, options, projectName) => {
1123
998
  "!**/*.d.ts",
1124
999
  ...extraPatterns
1125
1000
  ], getFileInfo(options.output.schemas).dirname);
1126
- log(`${projectName ? `${projectName}: ` : ""}Cleaning output folder`);
1127
- }
1128
- await writeSpecs(await importSpecs(workspace, options), workspace, options, projectName);
1129
- };
1130
- const generateSpecs = async (config, workspace, projectName) => {
1131
- if (projectName) {
1132
- const options = config[projectName];
1133
- if (options) try {
1134
- await generateSpec(workspace, options, projectName);
1135
- } catch (error) {
1136
- throw new ErrorWithTag(error instanceof Error ? error.message : "unknown error", projectName, { cause: error });
1137
- }
1138
- else throw new Error("Project not found");
1139
- return;
1140
- }
1141
- let hasErrors;
1142
- for (const [projectName$1, options] of Object.entries(config)) {
1143
- if (!options) {
1144
- hasErrors = true;
1145
- logError("No options found", projectName$1);
1146
- continue;
1147
- }
1148
- try {
1149
- await generateSpec(workspace, options, projectName$1);
1150
- } catch (error) {
1151
- hasErrors = true;
1152
- logError(error, projectName$1);
1153
- }
1001
+ log(`${projectName} Cleaning output folder`);
1154
1002
  }
1155
- if (hasErrors) throw new Error("One or more project failed, see above for details");
1156
- };
1003
+ await writeSpecs(await importSpecs(workspace, options, projectName), workspace, options, projectName);
1004
+ }
1005
+
1006
+ //#endregion
1007
+ //#region src/utils/config.ts
1008
+ /**
1009
+ * Resolve the Orval config file path.
1010
+ *
1011
+ * @param configFilePath - Optional path to the config file (absolute or relative).
1012
+ * @returns The absolute path to the resolved config file.
1013
+ * @throws If a provided path does not exist or if no config file is found.
1014
+ *
1015
+ * @example
1016
+ * // explicit path
1017
+ * const p = findConfigFile('./orval.config.ts');
1018
+ *
1019
+ * @example
1020
+ * // automatic discovery (searches process.cwd())
1021
+ * const p = findConfigFile();
1022
+ */
1157
1023
  function findConfigFile(configFilePath) {
1158
1024
  if (configFilePath) {
1159
- const absolutePath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process$1.cwd(), configFilePath);
1160
- if (!fs.existsSync(absolutePath)) throw new Error(`Config file ${configFilePath} does not exist`);
1025
+ const absolutePath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process.cwd(), configFilePath);
1026
+ if (!fs$1.existsSync(absolutePath)) throw new Error(`Config file ${configFilePath} does not exist`);
1161
1027
  return absolutePath;
1162
1028
  }
1163
- const root = process$1.cwd();
1029
+ const root = process.cwd();
1164
1030
  for (const ext of [
1165
1031
  ".ts",
1166
1032
  ".js",
@@ -1168,34 +1034,26 @@ function findConfigFile(configFilePath) {
1168
1034
  ".mts"
1169
1035
  ]) {
1170
1036
  const fullPath = path.resolve(root, `orval.config${ext}`);
1171
- if (fs.existsSync(fullPath)) return fullPath;
1037
+ if (fs$1.existsSync(fullPath)) return fullPath;
1172
1038
  }
1173
1039
  throw new Error(`No config file found in ${root}`);
1174
1040
  }
1041
+ /**
1042
+ * Load an Orval config file
1043
+ * @param configFilePath - Path to the config file (absolute or relative).
1044
+ * @returns The resolved Orval `Config` object.
1045
+ * @throws If the module does not provide a default export or the default export resolves to `undefined`.
1046
+ *
1047
+ * @example
1048
+ * // load a config object
1049
+ * const cfg = await loadConfigFile('./orval.config.ts');
1050
+ */
1175
1051
  async function loadConfigFile(configFilePath) {
1176
- const module = await createJiti(import.meta.url, { interopDefault: true }).import(configFilePath, { default: true });
1177
- if (module === void 0) throw new Error(`${configFilePath} doesn't have a default export`);
1178
- return await Promise.resolve(module);
1052
+ const configExternal = await createJiti(process.cwd(), { interopDefault: true }).import(configFilePath, { default: true });
1053
+ if (configExternal === void 0) throw new Error(`${configFilePath} doesn't have a default export`);
1054
+ return await (isFunction(configExternal) ? configExternal() : configExternal);
1179
1055
  }
1180
- const generateConfig = async (configFile, options) => {
1181
- const configFilePath = findConfigFile(configFile);
1182
- let configExternal;
1183
- try {
1184
- configExternal = await loadConfigFile(configFilePath);
1185
- } catch (error) {
1186
- const errorMsg = error instanceof Error ? error.message : "unknown error";
1187
- throw new Error(`failed to load from ${configFilePath} => ${errorMsg}`);
1188
- }
1189
- const workspace = path.dirname(configFilePath);
1190
- const config = await (isFunction(configExternal) ? configExternal() : configExternal);
1191
- const normalizedConfig = await asyncReduce(Object.entries(config), async (acc, [key, value]) => {
1192
- acc[key] = await normalizeOptions(value, workspace, options);
1193
- return acc;
1194
- }, {});
1195
- const fileToWatch = Object.entries(normalizedConfig).filter(([project]) => options?.projectName === void 0 || project === options.projectName).map(([, options$1]) => options$1?.input.target).filter((target) => isString(target));
1196
- await (options?.watch && fileToWatch.length > 0 ? startWatcher(options.watch, () => generateSpecs(normalizedConfig, workspace, options.projectName), fileToWatch) : generateSpecs(normalizedConfig, workspace, options?.projectName));
1197
- };
1198
1056
 
1199
1057
  //#endregion
1200
- export { normalizeOptions as a, version as c, defineConfig as i, generateSpec as n, description as o, startWatcher as r, name as s, generateConfig as t };
1201
- //# sourceMappingURL=generate-6oT76j_W.js.map
1058
+ 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 };
1059
+ //# sourceMappingURL=config-CJTbXPiX.mjs.map