orval
Version:
A swagger client generator for typescript
1,170 lines (1,159 loc) • 70.2 kB
JavaScript
import path from "node:path";
import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMode, PropertySortOrder, RefComponentSuffix, asyncReduce, conventionName, createLogger, createSuccessMessage, dynamicImport, fixCrossDirectoryImports, fixRegularSchemaImports, generateComponentDefinition, generateDependencyImports, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getFileInfo, getFullRoute, getMockFileExtensionByTypeName, getRoute, isBoolean, isFunction, isNullish, isObject, isReference, isString, isUrl, jsDoc, log, logError, logVerbose, pascal, removeFilesAndEmptyFolders, resolveInstalledVersions, resolveRef, splitSchemasByType, upath, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
import { bundle } from "@scalar/json-magic/bundle";
import { fetchUrls, parseJson, parseYaml, readFiles } from "@scalar/json-magic/bundle/plugins/node";
import { upgrade, validate } from "@scalar/openapi-parser";
import { isNullish as isNullish$1, unique } from "remeda";
import * as mock from "@orval/mock";
import { DEFAULT_MOCK_OPTIONS, generateMockImports } from "@orval/mock";
import angular from "@orval/angular";
import axios from "@orval/axios";
import fetchClient from "@orval/fetch";
import hono from "@orval/hono";
import mcp from "@orval/mcp";
import query from "@orval/query";
import solidStart from "@orval/solid-start";
import swr from "@orval/swr";
import zod, { dereference, generateZodValidationSchemaDefinition, isZodVersionV4, parseZodValidationSchemaDefinition } from "@orval/zod";
import { styleText } from "node:util";
import { ExecaError, execa } from "execa";
import fs from "fs-extra";
import fs$1, { access } from "node:fs/promises";
import { parseArgsStringToArgv } from "string-argv";
import { findUp, findUpMultiple } from "find-up";
import yaml from "js-yaml";
import { parse } from "tsconfck";
import fs$2 from "node:fs";
import { createJiti } from "jiti";
//#region package.json
var name = "orval";
var description = "A swagger client generator for typescript";
var version = "8.6.2";
//#endregion
//#region src/client.ts
const DEFAULT_CLIENT = OutputClient.AXIOS;
const getGeneratorClient = (outputClient, output) => {
const angularBuilder = angular();
const GENERATOR_CLIENT = {
axios: axios({ type: "axios" })(),
"axios-functions": axios({ type: "axios-functions" })(),
angular: angularBuilder(output.override.angular),
"angular-query": query({
output,
type: "angular-query"
})(),
"react-query": query({
output,
type: "react-query"
})(),
"solid-start": solidStart()(),
"solid-query": query({
output,
type: "solid-query"
})(),
"svelte-query": query({
output,
type: "svelte-query"
})(),
"vue-query": query({
output,
type: "vue-query"
})(),
swr: swr()(),
zod: zod()(),
hono: hono()(),
fetch: fetchClient()(),
mcp: mcp()()
};
const generator = isFunction(outputClient) ? outputClient(GENERATOR_CLIENT) : GENERATOR_CLIENT[outputClient];
if (!generator) throw new Error(`Unknown output client provided to getGeneratorClient: ${String(outputClient)}`);
return generator;
};
const generateClientImports = ({ client, implementation, imports, projectName, hasSchemaDir, isAllowSyntheticDefaultImports, hasGlobalMutator, hasTagsMutator, hasParamsSerializerOptions, packageJson, output }) => {
const { dependencies } = getGeneratorClient(client, output);
return generateDependencyImports(implementation, dependencies ? [...dependencies(hasGlobalMutator, hasParamsSerializerOptions, packageJson, output.httpClient, hasTagsMutator, output.override), ...imports] : imports, projectName, hasSchemaDir, isAllowSyntheticDefaultImports);
};
const generateClientHeader = ({ outputClient = DEFAULT_CLIENT, isRequestOptions, isGlobalMutator, isMutator, provideIn, hasAwaitedType, titles, output, verbOptions, tag, clientImplementation }) => {
const { header } = getGeneratorClient(outputClient, output);
return {
implementation: header ? header({
title: titles.implementation,
isRequestOptions,
isGlobalMutator,
isMutator,
provideIn,
hasAwaitedType,
output,
verbOptions,
tag,
clientImplementation
}) : "",
implementationMock: `export const ${titles.implementationMock} = () => [\n`
};
};
const generateClientFooter = ({ outputClient, operationNames, hasMutator, hasAwaitedType, titles, output }) => {
const { footer } = getGeneratorClient(outputClient, output);
if (!footer) return {
implementation: "",
implementationMock: `\n]\n`
};
let implementation;
try {
if (isFunction(outputClient)) {
implementation = footer(operationNames);
console.warn("[WARN] Passing an array of strings for operations names to the footer function is deprecated and will be removed in a future major release. Please pass them in an object instead: { operationNames: string[] }.");
} else implementation = footer({
operationNames,
title: titles.implementation,
hasMutator,
hasAwaitedType
});
} catch {
implementation = footer({
operationNames,
title: titles.implementation,
hasMutator,
hasAwaitedType
});
}
return {
implementation,
implementationMock: `]\n`
};
};
const generateClientTitle = ({ outputClient = DEFAULT_CLIENT, title, customTitleFunc, output }) => {
const { title: generatorTitle } = getGeneratorClient(outputClient, output);
if (!generatorTitle) return {
implementation: "",
implementationMock: `get${pascal(title)}Mock`
};
if (customTitleFunc) {
const customTitle = customTitleFunc(title);
return {
implementation: generatorTitle(customTitle),
implementationMock: `get${pascal(customTitle)}Mock`
};
}
return {
implementation: generatorTitle(title),
implementationMock: `get${pascal(title)}Mock`
};
};
const generateMock = (verbOption, options) => {
if (!options.mock) return {
implementation: {
function: "",
handler: "",
handlerName: ""
},
imports: []
};
if (isFunction(options.mock)) return options.mock(verbOption, options);
return mock.generateMock(verbOption, options);
};
const generateOperations = (outputClient = DEFAULT_CLIENT, verbsOptions, options, output) => {
return asyncReduce(verbsOptions, async (acc, verbOption) => {
const { client: generatorClient } = getGeneratorClient(outputClient, output);
const client = await generatorClient(verbOption, options, outputClient, output);
if (!client.implementation) return acc;
const generatedMock = generateMock(verbOption, options);
const hasImplementation = client.implementation.trim().length > 0;
const preferredOperationKey = verbOption.operationName;
const baseOperationKey = verbOption.operationId ? `${verbOption.operationId}::${verbOption.operationName}` : verbOption.operationName;
let operationKey = Object.hasOwn(acc, preferredOperationKey) ? baseOperationKey : preferredOperationKey;
let collisionIndex = 1;
while (Object.hasOwn(acc, operationKey)) {
collisionIndex += 1;
operationKey = `${baseOperationKey}::${collisionIndex}`;
}
acc[operationKey] = {
implementation: hasImplementation ? verbOption.doc + client.implementation : client.implementation,
imports: client.imports,
implementationMock: generatedMock.implementation,
importsMock: generatedMock.imports,
tags: verbOption.tags,
mutator: verbOption.mutator,
clientMutators: client.mutators,
formData: verbOption.formData,
formUrlEncoded: verbOption.formUrlEncoded,
paramsSerializer: verbOption.paramsSerializer,
operationName: verbOption.operationName,
fetchReviver: verbOption.fetchReviver
};
return acc;
}, {});
};
const generateExtraFiles = (outputClient = DEFAULT_CLIENT, verbsOptions, output, context) => {
const { extraFiles: generateExtraFiles } = getGeneratorClient(outputClient, output);
if (!generateExtraFiles) return Promise.resolve([]);
return generateExtraFiles(verbsOptions, output, context);
};
//#endregion
//#region src/api.ts
async function getApiBuilder({ input, output, context }) {
const api = await asyncReduce(Object.entries(context.spec.paths ?? {}), async (acc, [pathRoute, verbs]) => {
if (!verbs) return acc;
const route = getRoute(pathRoute);
let resolvedVerbs = verbs;
if (isReference(verbs)) {
const { schema } = resolveRef(verbs, context);
resolvedVerbs = schema;
}
let verbsOptions = await generateVerbsOptions({
verbs: resolvedVerbs,
input,
output,
route,
pathRoute,
context
});
if (output.override.useDeprecatedOperations === false) verbsOptions = verbsOptions.filter((verb) => {
return !verb.deprecated;
});
const schemas = [];
for (const { queryParams, headers, body, response, props } of verbsOptions) {
schemas.push(...props.flatMap((param) => param.type === GetterPropType.NAMED_PATH_PARAMS ? param.schema : []));
if (queryParams) schemas.push(queryParams.schema, ...queryParams.deps);
if (headers) schemas.push(headers.schema, ...headers.deps);
schemas.push(...body.schemas, ...response.schemas);
}
const fullRoute = getFullRoute(route, resolvedVerbs.servers ?? context.spec.servers, output.baseUrl);
if (!output.target) throw new Error("Output does not have a target");
const pathOperations = await generateOperations(output.client, verbsOptions, {
route: fullRoute,
pathRoute,
override: output.override,
context,
mock: output.mock,
output: output.target
}, output);
for (const verbOption of verbsOptions) acc.verbOptions[verbOption.operationId] = verbOption;
acc.schemas.push(...schemas);
acc.operations = {
...acc.operations,
...pathOperations
};
return acc;
}, {
operations: {},
verbOptions: {},
schemas: []
});
const extraFiles = await generateExtraFiles(output.client, api.verbOptions, output, context);
return {
operations: api.operations,
schemas: api.schemas,
verbOptions: api.verbOptions,
title: generateClientTitle,
header: generateClientHeader,
footer: generateClientFooter,
imports: generateClientImports,
importsMock: generateMockImports,
extraFiles
};
}
//#endregion
//#region src/import-open-api.ts
async function importOpenApi({ spec, input, output, target, workspace, projectName }) {
const transformedOpenApi = await applyTransformer(spec, input.override.transformer, workspace);
const schemas = getApiSchemas({
input,
output,
target,
workspace,
spec: transformedOpenApi
});
const api = await getApiBuilder({
input,
output,
context: {
projectName,
target,
workspace,
spec: transformedOpenApi,
output
}
});
return {
...api,
schemas: [...schemas, ...api.schemas],
target,
info: transformedOpenApi.info,
spec: transformedOpenApi
};
}
async function applyTransformer(openApi, transformer, workspace) {
const transformerFn = transformer ? await dynamicImport(transformer, workspace) : void 0;
if (!transformerFn) return openApi;
const transformedOpenApi = transformerFn(openApi);
const { valid, errors } = await validate(transformedOpenApi);
if (!valid) throw new Error(`Validation failed`, { cause: errors });
return transformedOpenApi;
}
function getApiSchemas({ input, output, target, workspace, spec }) {
const context = {
target,
workspace,
spec,
output
};
const schemaDefinition = generateSchemasDefinition(spec.components?.schemas, context, output.override.components.schemas.suffix, input.filters);
const responseDefinition = generateComponentDefinition(spec.components?.responses, context, output.override.components.responses.suffix);
const swaggerResponseDefinition = generateComponentDefinition("responses" in spec ? spec.responses : void 0, context, "");
const bodyDefinition = generateComponentDefinition(spec.components?.requestBodies, context, output.override.components.requestBodies.suffix);
const parameters = generateParameterDefinition(spec.components?.parameters, context, output.override.components.parameters.suffix);
return [
...schemaDefinition,
...responseDefinition,
...swaggerResponseDefinition,
...bodyDefinition,
...parameters
];
}
//#endregion
//#region src/import-specs.ts
async function resolveSpec(input, parserOptions) {
const dereferencedData = dereferenceExternalRef(await bundle(input, {
plugins: [
readFiles(),
fetchUrls({ headers: parserOptions?.headers }),
parseJson(),
parseYaml()
],
treeShake: false
}));
validateComponentKeys(dereferencedData);
const { valid, errors } = await validate(dereferencedData);
if (!valid) throw new Error("Validation failed", { cause: errors });
const { specification } = upgrade(dereferencedData);
return specification;
}
async function importSpecs(workspace, options, projectName) {
const { input, output } = options;
return importOpenApi({
spec: await resolveSpec(input.target, input.parserOptions),
input,
output,
target: isString(input.target) ? input.target : workspace,
workspace,
projectName
});
}
const COMPONENT_KEY_PATTERN = /^[a-zA-Z0-9.\-_]+$/;
const COMPONENT_SECTIONS = [
"schemas",
"responses",
"parameters",
"examples",
"requestBodies",
"headers",
"securitySchemes",
"links",
"callbacks",
"pathItems"
];
/**
* Validate that all component keys conform to the OAS regex: ^[a-zA-Z0-9.\-_]+$
* @see https://spec.openapis.org/oas/v3.0.3.html#fixed-fields-5
* @see https://spec.openapis.org/oas/v3.1.0#fixed-fields-5
*/
function validateComponentKeys(data) {
const components = data.components;
if (!isObject(components)) return;
const invalidKeys = [];
for (const section of COMPONENT_SECTIONS) {
const sectionObj = components[section];
if (!isObject(sectionObj)) continue;
for (const key of Object.keys(sectionObj)) if (!COMPONENT_KEY_PATTERN.test(key)) invalidKeys.push(`components.${section}.${key}`);
}
if (invalidKeys.length > 0) throw new Error(`Invalid component key${invalidKeys.length > 1 ? "s" : ""} found. OpenAPI component keys must match the pattern ${COMPONENT_KEY_PATTERN} (non-ASCII characters are not allowed per the spec).\n See: https://spec.openapis.org/oas/v3.0.3.html#components-object\n Invalid keys:\n` + invalidKeys.map((k) => ` - ${k}`).join("\n"));
}
/**
* The plugins from `@scalar/json-magic` does not dereference $ref.
* Instead it fetches them and puts them under x-ext, and changes the $ref to point to #x-ext/<name>.
* This function:
* 1. Merges external schemas into main spec's components.schemas (with collision handling)
* 2. Replaces x-ext refs with standard component refs or inlined content
*/
function dereferenceExternalRef(data) {
const extensions = data["x-ext"] ?? {};
const schemaNameMappings = mergeExternalSchemas(data, extensions);
const result = {};
for (const [key, value] of Object.entries(data)) if (key !== "x-ext") result[key] = replaceXExtRefs(value, extensions, schemaNameMappings);
return result;
}
/**
* Merge external document schemas into main spec's components.schemas
* Returns mapping of original schema names to final names (with suffixes for collisions)
*/
function mergeExternalSchemas(data, extensions) {
const schemaNameMappings = {};
if (Object.keys(extensions).length === 0) return schemaNameMappings;
data.components ??= {};
const mainComponents = data.components;
mainComponents.schemas ??= {};
const mainSchemas = mainComponents.schemas;
for (const [extKey, extDoc] of Object.entries(extensions)) {
schemaNameMappings[extKey] = {};
if (isObject(extDoc) && "components" in extDoc) {
const extComponents = extDoc.components;
if (isObject(extComponents) && "schemas" in extComponents) {
const extSchemas = extComponents.schemas;
for (const [schemaName, schema] of Object.entries(extSchemas)) {
const existingSchema = mainSchemas[schemaName];
const isXExtRef = isObject(existingSchema) && "$ref" in existingSchema && isString(existingSchema.$ref) && existingSchema.$ref.startsWith("#/x-ext/");
let finalSchemaName = schemaName;
if (schemaName in mainSchemas && !isXExtRef) {
finalSchemaName = `${schemaName}_${extKey.replaceAll(/[^a-zA-Z0-9]/g, "_")}`;
schemaNameMappings[extKey][schemaName] = finalSchemaName;
} else schemaNameMappings[extKey][schemaName] = schemaName;
mainSchemas[finalSchemaName] = scrubUnwantedKeys(schema);
}
}
}
}
for (const [extKey, mapping] of Object.entries(schemaNameMappings)) for (const [, finalName] of Object.entries(mapping)) {
const schema = mainSchemas[finalName];
if (schema) mainSchemas[finalName] = updateInternalRefs(schema, extKey, schemaNameMappings);
}
return schemaNameMappings;
}
/**
* Remove unwanted keys like $schema and $id from objects
*/
function scrubUnwantedKeys(obj) {
const UNWANTED_KEYS = new Set(["$schema", "$id"]);
if (obj === null || obj === void 0) return obj;
if (Array.isArray(obj)) return obj.map((x) => scrubUnwantedKeys(x));
if (isObject(obj)) {
const rec = obj;
const out = {};
for (const [k, v] of Object.entries(rec)) {
if (UNWANTED_KEYS.has(k)) continue;
out[k] = scrubUnwantedKeys(v);
}
return out;
}
return obj;
}
/**
* Update internal refs within an external schema to use suffixed names
*/
function updateInternalRefs(obj, extKey, schemaNameMappings) {
if (obj === null || obj === void 0) return obj;
if (Array.isArray(obj)) return obj.map((element) => updateInternalRefs(element, extKey, schemaNameMappings));
if (isObject(obj)) {
const record = obj;
if ("$ref" in record && isString(record.$ref)) {
const refValue = record.$ref;
if (refValue.startsWith("#/components/schemas/")) {
const schemaName = refValue.replace("#/components/schemas/", "");
const mappedName = schemaNameMappings[extKey][schemaName];
if (mappedName) return { $ref: `#/components/schemas/${mappedName}` };
}
}
const result = {};
for (const [key, value] of Object.entries(record)) result[key] = updateInternalRefs(value, extKey, schemaNameMappings);
return result;
}
return obj;
}
/**
* Replace x-ext refs with either standard component refs or inlined content
*/
function replaceXExtRefs(obj, extensions, schemaNameMappings) {
if (isNullish$1(obj)) return obj;
if (Array.isArray(obj)) return obj.map((element) => replaceXExtRefs(element, extensions, schemaNameMappings));
if (isObject(obj)) {
const record = obj;
if ("$ref" in record && isString(record.$ref)) {
const refValue = record.$ref;
if (refValue.startsWith("#/x-ext/")) {
const parts = refValue.replace("#/x-ext/", "").split("/");
const extKey = parts.shift();
if (extKey) {
if (parts.length >= 3 && parts[0] === "components" && parts[1] === "schemas") {
const schemaName = parts.slice(2).join("/");
return { $ref: `#/components/schemas/${schemaNameMappings[extKey][schemaName] || schemaName}` };
}
let refObj = extensions[extKey];
for (const p of parts) if (refObj && (isObject(refObj) || Array.isArray(refObj)) && p in refObj) refObj = refObj[p];
else {
refObj = void 0;
break;
}
if (refObj) return replaceXExtRefs(scrubUnwantedKeys(refObj), extensions, schemaNameMappings);
}
}
}
const result = {};
for (const [key, value] of Object.entries(record)) result[key] = replaceXExtRefs(value, extensions, schemaNameMappings);
return result;
}
return obj;
}
//#endregion
//#region src/formatters/prettier.ts
/**
* Format files with prettier.
* Tries the programmatic API first (project dependency),
* then falls back to the globally installed CLI.
*/
async function formatWithPrettier(paths, projectTitle) {
const prettier = await tryImportPrettier();
if (prettier) {
const filePaths = [...new Set(await collectFilePaths(paths))];
if (filePaths.length === 0) return;
const config = await prettier.resolveConfig(filePaths[0]) ?? {};
await Promise.all(filePaths.map(async (filePath) => {
try {
const content = await fs$1.readFile(filePath, "utf8");
const formatted = await prettier.format(content, {
...config,
filepath: filePath
});
await fs$1.writeFile(filePath, formatted);
} catch (error) {
if (isMissingFileError(error)) return;
if (error instanceof Error) if (error.name === "UndefinedParserError") {} else log(styleText("yellow", `⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: ${error.toString()}`));
else log(styleText("yellow", `⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: unknown error}`));
}
}));
return;
}
try {
await execa("prettier", ["--write", ...paths]);
} catch {
log(styleText("yellow", `⚠️ ${projectTitle ? `${projectTitle} - ` : ""}prettier not found. Install it as a project dependency or globally.`));
}
}
function isMissingFileError(error) {
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
}
/**
* Try to import prettier from the project's dependencies.
* Returns undefined if prettier is not installed.
*/
async function tryImportPrettier() {
try {
return await import("prettier");
} catch {
return;
}
}
/**
* Recursively collect absolute file paths from a mix of files and directories.
*/
async function collectFilePaths(paths) {
const results = [];
for (const p of paths) {
const absolute = path.resolve(p);
try {
const stat = await fs$1.stat(absolute);
if (stat.isFile()) results.push(absolute);
else if (stat.isDirectory()) {
const subFiles = await collectFilePaths((await fs$1.readdir(absolute)).map((entry) => path.join(absolute, entry)));
results.push(...subFiles);
}
} catch {}
}
return results;
}
//#endregion
//#region src/utils/execute-hook.ts
const executeHook = async (name, commands = [], args = []) => {
log(styleText("white", `Running ${name} hook...`));
for (const command of commands) try {
if (isString(command)) await executeCommand(command, args);
else if (isFunction(command)) await command(args);
else if (isObject(command)) await executeObjectCommand(command, args);
} catch (error) {
logError(error, `Failed to run ${name} hook`);
}
};
async function executeCommand(command, args) {
const [cmd, ..._args] = [...parseArgsStringToArgv(command), ...args];
await execa(cmd, _args);
}
async function executeObjectCommand(command, args) {
if (command.injectGeneratedDirsAndFiles === false) args = [];
if (isString(command.command)) await executeCommand(command.command, args);
else if (isFunction(command.command)) await command.command();
}
//#endregion
//#region src/utils/package-json.ts
const loadPackageJson = async (packageJson, workspace = process.cwd()) => {
if (!packageJson) {
const pkgPath = await findUp(["package.json"], { cwd: workspace });
if (pkgPath) {
const pkg = await dynamicImport(pkgPath, workspace);
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, pkgPath);
else throw new Error("Invalid package.json file");
}
return;
}
const normalizedPath = normalizePath(packageJson, workspace);
if (fs.existsSync(normalizedPath)) {
const pkg = await dynamicImport(normalizedPath);
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, normalizedPath);
else throw new Error(`Invalid package.json file: ${normalizedPath}`);
}
};
const isPackageJson = (obj) => isObject(obj);
const resolvedCache = /* @__PURE__ */ new Map();
const resolveAndAttachVersions = (pkg, workspace, cacheKey) => {
const cached = resolvedCache.get(cacheKey);
if (cached) {
pkg.resolvedVersions = cached;
return pkg;
}
const resolved = resolveInstalledVersions(pkg, workspace);
if (Object.keys(resolved).length > 0) {
pkg.resolvedVersions = resolved;
resolvedCache.set(cacheKey, resolved);
for (const [name, version] of Object.entries(resolved)) logVerbose(styleText("dim", `Detected ${styleText("white", name)} v${styleText("white", version)}`));
}
return pkg;
};
const hasCatalogReferences = (pkg) => {
return [
...Object.entries(pkg.dependencies ?? {}),
...Object.entries(pkg.devDependencies ?? {}),
...Object.entries(pkg.peerDependencies ?? {})
].some(([, value]) => isString(value) && value.startsWith("catalog:"));
};
const loadPnpmWorkspaceCatalog = async (workspace) => {
const filePath = await findUp("pnpm-workspace.yaml", { cwd: workspace });
if (!filePath) return void 0;
try {
const file = await fs.readFile(filePath, "utf8");
const data = yaml.load(file);
if (!data?.catalog && !data?.catalogs) return void 0;
return {
catalog: data.catalog,
catalogs: data.catalogs
};
} catch {
return;
}
};
const loadPackageJsonCatalog = async (workspace) => {
const filePaths = await findUpMultiple("package.json", { cwd: workspace });
for (const filePath of filePaths) try {
const pkg = await fs.readJson(filePath);
if (pkg.catalog || pkg.catalogs) return {
catalog: pkg.catalog,
catalogs: pkg.catalogs
};
} catch {}
};
const loadYarnrcCatalog = async (workspace) => {
const filePath = await findUp(".yarnrc.yml", { cwd: workspace });
if (!filePath) return void 0;
try {
const file = await fs.readFile(filePath, "utf8");
const data = yaml.load(file);
if (!data?.catalog && !data?.catalogs) return void 0;
return {
catalog: data.catalog,
catalogs: data.catalogs
};
} catch {
return;
}
};
const maybeReplaceCatalog = async (pkg, workspace) => {
if (!hasCatalogReferences(pkg)) return pkg;
const catalogData = await loadPnpmWorkspaceCatalog(workspace) ?? await loadPackageJsonCatalog(workspace) ?? await loadYarnrcCatalog(workspace);
if (!catalogData) {
log(`⚠️ ${styleText("yellow", "package.json contains catalog: references, but no catalog source was found (checked: pnpm-workspace.yaml, package.json, .yarnrc.yml).")}`);
return pkg;
}
performSubstitution(pkg.dependencies, catalogData);
performSubstitution(pkg.devDependencies, catalogData);
performSubstitution(pkg.peerDependencies, catalogData);
return pkg;
};
const performSubstitution = (dependencies, catalogData) => {
if (!dependencies) return;
for (const [packageName, version] of Object.entries(dependencies)) if (version === "catalog:" || version === "catalog:default") {
if (!catalogData.catalog) {
log(`⚠️ ${styleText("yellow", `catalog: substitution for the package '${packageName}' failed as there is no default catalog.`)}`);
continue;
}
const sub = catalogData.catalog[packageName];
if (!sub) {
log(`⚠️ ${styleText("yellow", `catalog: substitution for the package '${packageName}' failed as there is no matching package in the default catalog.`)}`);
continue;
}
dependencies[packageName] = sub;
} else if (version.startsWith("catalog:")) {
const catalogName = version.slice(8);
const catalog = catalogData.catalogs?.[catalogName];
if (!catalog) {
log(`⚠️ ${styleText("yellow", `'${version}' substitution for the package '${packageName}' failed as there is no matching catalog named '${catalogName}'. (available named catalogs are: ${Object.keys(catalogData.catalogs ?? {}).join(", ")})`)}`);
continue;
}
const sub = catalog[packageName];
if (!sub) {
log(`⚠️ ${styleText("yellow", `'${version}' substitution for the package '${packageName}' failed as there is no package in the catalog named '${catalogName}'. (packages in the catalog are: ${Object.keys(catalog).join(", ")})`)}`);
continue;
}
dependencies[packageName] = sub;
}
};
//#endregion
//#region src/utils/tsconfig.ts
const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
if (isNullish(tsconfig)) {
const configPath = await findUp(["tsconfig.json", "jsconfig.json"], { cwd: workspace });
if (configPath) return (await parse(configPath)).tsconfig;
return;
}
if (isString(tsconfig)) {
const normalizedPath = normalizePath(tsconfig, workspace);
if (fs.existsSync(normalizedPath)) {
const config = await parse(normalizedPath);
return config.referenced?.find(({ tsconfigFile }) => tsconfigFile === normalizedPath)?.tsconfig ?? config.tsconfig;
}
return;
}
if (isObject(tsconfig)) return tsconfig;
};
//#endregion
//#region src/utils/options.ts
const INPUT_TARGET_FETCH_TIMEOUT_MS = 1e4;
/**
* Type helper to make it easier to use orval.config.ts
* accepts a direct {@link ConfigExternal} object.
*/
function defineConfig(options) {
return options;
}
/**
* Type helper to make it easier to write input transformers.
* accepts a direct {@link InputTransformerFn} function.
*/
function defineTransformer(transformer) {
return transformer;
}
function createFormData(workspace, formData) {
const defaultArrayHandling = FormDataArrayHandling.SERIALIZE;
if (formData === void 0) return {
disabled: false,
arrayHandling: defaultArrayHandling
};
if (isBoolean(formData)) return {
disabled: !formData,
arrayHandling: defaultArrayHandling
};
if (isString(formData)) return {
disabled: false,
mutator: normalizeMutator(workspace, formData),
arrayHandling: defaultArrayHandling
};
if ("mutator" in formData || "arrayHandling" in formData) return {
disabled: false,
mutator: normalizeMutator(workspace, formData.mutator),
arrayHandling: formData.arrayHandling ?? defaultArrayHandling
};
return {
disabled: false,
mutator: normalizeMutator(workspace, formData),
arrayHandling: defaultArrayHandling
};
}
function normalizeSchemasOption(schemas, workspace) {
if (!schemas) return;
if (isString(schemas)) return normalizePath(schemas, workspace);
return {
path: normalizePath(schemas.path, workspace),
type: schemas.type
};
}
async function normalizeOptions(optionsExport, workspace = process.cwd(), globalOptions = {}) {
const options = await (isFunction(optionsExport) ? optionsExport() : optionsExport);
if (!options.input) throw new Error(styleText("red", `Config requires an input.`));
if (!options.output) throw new Error(styleText("red", `Config requires an output.`));
const inputOptions = isString(options.input) || Array.isArray(options.input) ? { target: options.input } : options.input;
const outputOptions = isString(options.output) ? { target: options.output } : options.output;
const outputWorkspace = normalizePath(outputOptions.workspace ?? "", workspace);
const { clean, prettier, client, httpClient, mode, biome } = globalOptions;
const tsconfig = await loadTsconfig(outputOptions.tsconfig ?? globalOptions.tsconfig, workspace);
const packageJson = await loadPackageJson(outputOptions.packageJson ?? globalOptions.packageJson, workspace);
const mockOption = outputOptions.mock ?? globalOptions.mock;
let mock;
if (isBoolean(mockOption) && mockOption) mock = DEFAULT_MOCK_OPTIONS;
else if (isFunction(mockOption)) mock = mockOption;
else if (mockOption) mock = {
...DEFAULT_MOCK_OPTIONS,
...mockOption
};
else mock = void 0;
const defaultFileExtension = ".ts";
const globalQueryOptions = {
useQuery: true,
useMutation: true,
signal: true,
shouldExportMutatorHooks: true,
shouldExportHttpClient: true,
shouldExportQueryKey: true,
shouldSplitQueryKey: false,
...normalizeQueryOptions(outputOptions.override?.query, workspace)
};
const normalizedOptions = {
input: {
target: globalOptions.input ? Array.isArray(globalOptions.input) ? await resolveFirstValidTarget(globalOptions.input, process.cwd(), inputOptions.parserOptions) : normalizePathOrUrl(globalOptions.input, process.cwd()) : Array.isArray(inputOptions.target) ? await resolveFirstValidTarget(inputOptions.target, workspace, inputOptions.parserOptions) : normalizePathOrUrl(inputOptions.target, workspace),
override: { transformer: normalizePath(inputOptions.override?.transformer, workspace) },
filters: inputOptions.filters,
parserOptions: inputOptions.parserOptions
},
output: {
target: globalOptions.output ? normalizePath(globalOptions.output, process.cwd()) : normalizePath(outputOptions.target, outputWorkspace),
schemas: normalizeSchemasOption(outputOptions.schemas, outputWorkspace),
operationSchemas: outputOptions.operationSchemas ? normalizePath(outputOptions.operationSchemas, outputWorkspace) : void 0,
namingConvention: outputOptions.namingConvention ?? NamingConvention.CAMEL_CASE,
fileExtension: outputOptions.fileExtension ?? defaultFileExtension,
workspace: outputOptions.workspace ? outputWorkspace : void 0,
client: outputOptions.client ?? client ?? OutputClient.AXIOS_FUNCTIONS,
httpClient: outputOptions.httpClient ?? httpClient ?? ((outputOptions.client ?? client) === OutputClient.ANGULAR_QUERY ? OutputHttpClient.ANGULAR : OutputHttpClient.FETCH),
mode: normalizeOutputMode(outputOptions.mode ?? mode),
mock,
clean: outputOptions.clean ?? clean ?? false,
docs: outputOptions.docs ?? false,
prettier: outputOptions.prettier ?? prettier ?? false,
biome: outputOptions.biome ?? biome ?? false,
tsconfig,
packageJson,
headers: outputOptions.headers ?? false,
indexFiles: outputOptions.indexFiles ?? true,
baseUrl: outputOptions.baseUrl,
unionAddMissingProperties: outputOptions.unionAddMissingProperties ?? false,
override: {
...outputOptions.override,
mock: {
arrayMin: outputOptions.override?.mock?.arrayMin ?? 1,
arrayMax: outputOptions.override?.mock?.arrayMax ?? 10,
stringMin: outputOptions.override?.mock?.stringMin ?? 10,
stringMax: outputOptions.override?.mock?.stringMax ?? 20,
fractionDigits: outputOptions.override?.mock?.fractionDigits ?? 2,
...outputOptions.override?.mock
},
operations: normalizeOperationsAndTags(outputOptions.override?.operations ?? {}, outputWorkspace, { query: globalQueryOptions }),
tags: normalizeOperationsAndTags(outputOptions.override?.tags ?? {}, outputWorkspace, { query: globalQueryOptions }),
mutator: normalizeMutator(outputWorkspace, outputOptions.override?.mutator),
formData: createFormData(outputWorkspace, outputOptions.override?.formData),
formUrlEncoded: (isBoolean(outputOptions.override?.formUrlEncoded) ? outputOptions.override.formUrlEncoded : normalizeMutator(outputWorkspace, outputOptions.override?.formUrlEncoded)) ?? true,
paramsSerializer: normalizeMutator(outputWorkspace, outputOptions.override?.paramsSerializer),
header: outputOptions.override?.header === false ? false : isFunction(outputOptions.override?.header) ? outputOptions.override.header : getDefaultFilesHeader,
requestOptions: outputOptions.override?.requestOptions ?? true,
namingConvention: outputOptions.override?.namingConvention ?? {},
components: {
schemas: {
suffix: RefComponentSuffix.schemas,
itemSuffix: outputOptions.override?.components?.schemas?.itemSuffix ?? "Item",
...outputOptions.override?.components?.schemas
},
responses: {
suffix: RefComponentSuffix.responses,
...outputOptions.override?.components?.responses
},
parameters: {
suffix: RefComponentSuffix.parameters,
...outputOptions.override?.components?.parameters
},
requestBodies: {
suffix: RefComponentSuffix.requestBodies,
...outputOptions.override?.components?.requestBodies
}
},
hono: normalizeHonoOptions(outputOptions.override?.hono, workspace),
jsDoc: normalizeJSDocOptions(outputOptions.override?.jsDoc),
query: globalQueryOptions,
zod: {
strict: {
param: outputOptions.override?.zod?.strict?.param ?? false,
query: outputOptions.override?.zod?.strict?.query ?? false,
header: outputOptions.override?.zod?.strict?.header ?? false,
body: outputOptions.override?.zod?.strict?.body ?? false,
response: outputOptions.override?.zod?.strict?.response ?? false
},
generate: {
param: outputOptions.override?.zod?.generate?.param ?? true,
query: outputOptions.override?.zod?.generate?.query ?? true,
header: outputOptions.override?.zod?.generate?.header ?? true,
body: outputOptions.override?.zod?.generate?.body ?? true,
response: outputOptions.override?.zod?.generate?.response ?? true
},
coerce: {
param: outputOptions.override?.zod?.coerce?.param ?? false,
query: outputOptions.override?.zod?.coerce?.query ?? false,
header: outputOptions.override?.zod?.coerce?.header ?? false,
body: outputOptions.override?.zod?.coerce?.body ?? false,
response: outputOptions.override?.zod?.coerce?.response ?? false
},
preprocess: {
...outputOptions.override?.zod?.preprocess?.param ? { param: normalizeMutator(workspace, outputOptions.override.zod.preprocess.param) } : {},
...outputOptions.override?.zod?.preprocess?.query ? { query: normalizeMutator(workspace, outputOptions.override.zod.preprocess.query) } : {},
...outputOptions.override?.zod?.preprocess?.header ? { header: normalizeMutator(workspace, outputOptions.override.zod.preprocess.header) } : {},
...outputOptions.override?.zod?.preprocess?.body ? { body: normalizeMutator(workspace, outputOptions.override.zod.preprocess.body) } : {},
...outputOptions.override?.zod?.preprocess?.response ? { response: normalizeMutator(workspace, outputOptions.override.zod.preprocess.response) } : {}
},
generateEachHttpStatus: outputOptions.override?.zod?.generateEachHttpStatus ?? false,
dateTimeOptions: outputOptions.override?.zod?.dateTimeOptions ?? {},
timeOptions: outputOptions.override?.zod?.timeOptions ?? {}
},
swr: {
generateErrorTypes: false,
...outputOptions.override?.swr
},
angular: {
provideIn: outputOptions.override?.angular?.provideIn ?? "root",
client: outputOptions.override?.angular?.retrievalClient ?? outputOptions.override?.angular?.client ?? "httpClient",
runtimeValidation: outputOptions.override?.angular?.runtimeValidation ?? false,
...outputOptions.override?.angular?.httpResource ? { httpResource: outputOptions.override.angular.httpResource } : {}
},
fetch: {
includeHttpResponseReturnType: outputOptions.override?.fetch?.includeHttpResponseReturnType ?? true,
forceSuccessResponse: outputOptions.override?.fetch?.forceSuccessResponse ?? false,
runtimeValidation: outputOptions.override?.fetch?.runtimeValidation ?? false,
...outputOptions.override?.fetch,
...outputOptions.override?.fetch?.jsonReviver ? { jsonReviver: normalizeMutator(outputWorkspace, outputOptions.override.fetch.jsonReviver) } : {}
},
useDates: outputOptions.override?.useDates ?? false,
useDeprecatedOperations: outputOptions.override?.useDeprecatedOperations ?? true,
enumGenerationType: outputOptions.override?.enumGenerationType ?? "const",
suppressReadonlyModifier: outputOptions.override?.suppressReadonlyModifier ?? false,
preserveReadonlyRequestBodies: outputOptions.override?.preserveReadonlyRequestBodies ?? "strip",
aliasCombinedTypes: outputOptions.override?.aliasCombinedTypes ?? false
},
allParamsOptional: outputOptions.allParamsOptional ?? false,
urlEncodeParameters: outputOptions.urlEncodeParameters ?? false,
optionsParamRequired: outputOptions.optionsParamRequired ?? false,
propertySortOrder: outputOptions.propertySortOrder ?? PropertySortOrder.SPECIFICATION
},
hooks: options.hooks ? normalizeHooks(options.hooks) : {}
};
if (!normalizedOptions.input.target) throw new Error(styleText("red", `Config requires an input target.`));
if (!normalizedOptions.output.target && !normalizedOptions.output.schemas) throw new Error(styleText("red", `Config requires an output target or schemas.`));
return normalizedOptions;
}
function normalizeMutator(workspace, mutator) {
if (isObject(mutator)) {
const m = mutator;
if (!m.path) throw new Error(styleText("red", `Mutator requires a path.`));
return {
path: path.resolve(workspace, m.path),
name: m.name,
default: m.default ?? !m.name,
alias: m.alias,
external: m.external,
extension: m.extension
};
}
if (isString(mutator)) return {
path: path.resolve(workspace, mutator),
default: true
};
}
async function resolveFirstValidTarget(targets, workspace, parserOptions) {
for (const target of targets) {
if (isUrl(target)) {
try {
const headers = getHeadersForUrl(target, parserOptions?.headers);
const headResponse = await fetchWithTimeout(target, {
method: "HEAD",
headers
});
if (headResponse.ok) return target;
if (headResponse.status === 405 || headResponse.status === 501) {
if ((await fetchWithTimeout(target, {
method: "GET",
headers
})).ok) return target;
}
} catch {
continue;
}
continue;
}
const resolvedTarget = normalizePath(target, workspace);
try {
await access(resolvedTarget);
return resolvedTarget;
} catch {
continue;
}
}
throw new Error(styleText("red", `None of the input targets could be resolved:\n${targets.map((target) => ` - ${target}`).join("\n")}`));
}
function getHeadersForUrl(url, headersConfig) {
if (!headersConfig) return {};
const { hostname } = new URL(url);
const matchedHeaders = {};
for (const headerEntry of headersConfig) if (headerEntry.domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) Object.assign(matchedHeaders, headerEntry.headers);
return matchedHeaders;
}
async function fetchWithTimeout(target, init) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, INPUT_TARGET_FETCH_TIMEOUT_MS);
try {
return await fetch(target, {
...init,
signal: controller.signal
});
} finally {
clearTimeout(timeoutId);
}
}
function normalizePathOrUrl(path, workspace) {
if (isString(path) && !isUrl(path)) return normalizePath(path, workspace);
return path;
}
function normalizePath(path$1, workspace) {
if (!isString(path$1)) return path$1;
return path.resolve(workspace, path$1);
}
function normalizeOperationsAndTags(operationsOrTags, workspace, global) {
return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, query, angular, zod, ...rest }]) => {
return [key, {
...rest,
...angular ? { angular: {
provideIn: angular.provideIn ?? "root",
client: angular.retrievalClient ?? angular.client ?? "httpClient",
runtimeValidation: angular.runtimeValidation ?? false,
...angular.httpResource ? { httpResource: angular.httpResource } : {}
} } : {},
...query ? { query: normalizeQueryOptions(query, workspace, global.query) } : {},
...zod ? { zod: {
strict: {
param: zod.strict?.param ?? false,
query: zod.strict?.query ?? false,
header: zod.strict?.header ?? false,
body: zod.strict?.body ?? false,
response: zod.strict?.response ?? false
},
generate: {
param: zod.generate?.param ?? true,
query: zod.generate?.query ?? true,
header: zod.generate?.header ?? true,
body: zod.generate?.body ?? true,
response: zod.generate?.response ?? true
},
coerce: {
param: zod.coerce?.param ?? false,
query: zod.coerce?.query ?? false,
header: zod.coerce?.header ?? false,
body: zod.coerce?.body ?? false,
response: zod.coerce?.response ?? false
},
preprocess: {
...zod.preprocess?.param ? { param: normalizeMutator(workspace, zod.preprocess.param) } : {},
...zod.preprocess?.query ? { query: normalizeMutator(workspace, zod.preprocess.query) } : {},
...zod.preprocess?.header ? { header: normalizeMutator(workspace, zod.preprocess.header) } : {},
...zod.preprocess?.body ? { body: normalizeMutator(workspace, zod.preprocess.body) } : {},
...zod.preprocess?.response ? { response: normalizeMutator(workspace, zod.preprocess.response) } : {}
},
generateEachHttpStatus: zod.generateEachHttpStatus ?? false,
dateTimeOptions: zod.dateTimeOptions ?? {},
timeOptions: zod.timeOptions ?? {}
} } : {},
...transformer ? { transformer: normalizePath(transformer, workspace) } : {},
...mutator ? { mutator: normalizeMutator(workspace, mutator) } : {},
...formData === void 0 ? {} : { formData: createFormData(workspace, formData) },
...formUrlEncoded ? { formUrlEncoded: isBoolean(formUrlEncoded) ? formUrlEncoded : normalizeMutator(workspace, formUrlEncoded) } : {},
...paramsSerializer ? { paramsSerializer: normalizeMutator(workspace, paramsSerializer) } : {}
}];
}));
}
function normalizeOutputMode(mode) {
if (!mode) return OutputMode.SINGLE;
if (!Object.values(OutputMode).includes(mode)) {
createLogger().warn(styleText("yellow", `Unknown provided mode => ${mode}`));
return OutputMode.SINGLE;
}
return mode;
}
function normalizeHooks(hooks) {
const keys = Object.keys(hooks);
const result = {};
for (const key of keys) if (isString(hooks[key])) result[key] = [hooks[key]];
else if (Array.isArray(hooks[key])) result[key] = hooks[key];
else if (isFunction(hooks[key])) result[key] = [hooks[key]];
else if (isObject(hooks[key])) result[key] = [hooks[key]];
return result;
}
function normalizeHonoOptions(hono = {}, workspace) {
return {
...hono.handlers ? { handlers: path.resolve(workspace, hono.handlers) } : {},
compositeRoute: hono.compositeRoute ? path.resolve(workspace, hono.compositeRoute) : "",
validator: hono.validator ?? true,
validatorOutputPath: hono.validatorOutputPath ? path.resolve(workspace, hono.validatorOutputPath) : ""
};
}
function normalizeJSDocOptions(jsdoc = {}) {
return { ...jsdoc };
}
function normalizeQueryOptions(queryOptions = {}, outputWorkspace, globalOptions = {}) {
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.");
return {
...isNullish(queryOptions.usePrefetch) ? {} : { usePrefetch: queryOptions.usePrefetch },
...isNullish(queryOptions.useInvalidate) ? {} : { useInvalidate: queryOptions.useInvalidate },
...isNullish(queryOptions.useQuery) ? {} : { useQuery: queryOptions.useQuery },
...isNullish(queryOptions.useSuspenseQuery) ? {} : { useSuspenseQuery: queryOptions.useSuspenseQuery },
...isNullish(queryOptions.useMutation) ? {} : { useMutation: queryOptions.useMutation },
...isNullish(queryOptions.useInfinite) ? {} : { useInfinite: queryOptions.useInfinite },
...isNullish(queryOptions.useSuspenseInfiniteQuery) ? {} : { useSuspenseInfiniteQuery: queryOptions.useSuspenseInfiniteQuery },
...queryOptions.useInfiniteQueryParam ? { useInfiniteQueryParam: queryOptions.useInfiniteQueryParam } : {},
...queryOptions.options ? { options: queryOptions.options } : {},
...globalOptions.queryKey ? { queryKey: globalOptions.queryKey } : {},
...queryOptions.queryKey ? { queryKey: normalizeMutator(outputWorkspace, queryOptions.queryKey) } : {},
...globalOptions.queryOptions ? { queryOptions: globalOptions.queryOptions } : {},
...queryOptions.queryOptions ? { queryOptions: normalizeMutator(outputWorkspace, queryOptions.queryOptions) } : {},
...globalOptions.mutationOptions ? { mutationOptions: globalOptions.mutationOptions } : {},
...queryOptions.mutationOptions ? { mutationOptions: normalizeMutator(outputWorkspace, queryOptions.mutationOptions) } : {},
...isNullish(globalOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: globalOptions.shouldExportQueryKey },
...isNullish(queryOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: queryOptions.shouldExportQueryKey },
...isNullish(globalOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: globalOptions.shouldExportHttpClient },
...isNullish(queryOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: queryOptions.shouldExportHttpClient },
...isNullish(globalOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: globalOptions.shouldExportMutatorHooks },
...isNullish(queryOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: queryOptions.shouldExportMutatorHooks },
...isNullish(globalOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: globalOptions.shouldSplitQueryKey },
...isNullish(queryOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: queryOptions.shouldSplitQueryKey },
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
...isNullish(globalOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: globalOptions.useOperationIdAsQueryKey },
...isNullish(queryOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: queryOptions.useOperationIdAsQueryKey },
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
...isNullish(queryOptions.signal) ? {} : { signal: queryOptions.signal },
...isNullish(globalOptions.version) ? {} : { version: globalOptions.version },
...isNullish(queryOptions.version) ? {} : { version: queryOptions.version },
...queryOptions.mutationInvalidates ? { mutationInvalidates: queryOptions.mutationInvalidates } : {},
...isNullish(globalOptions.runtimeValidation) ? {} : { runtimeValidation: globalOptions.runtimeValidation },
...isNullish(queryOptions.runtimeValidation) ? {} : { runtimeValidation: queryOptions.runtimeValidation }
};
}
function getDefaultFilesHeader({ title, description, version: version$1 } = {}) {
return [
`Generated by ${name} v${version} 🍺`,
`Do not edit manually.`,
...title ? [title] : [],
...description ? [description] : [],
...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
];
}
//#endregion
//#region src/utils/watcher.ts
/**
* Start a file watcher and invoke an async callback on file changes.
*
* If `watchOptions` is falsy the watcher is not started. Supported shapes:
* - boolean: when true the `defaultTarget` is watched
* - string: a single path to watch
* - string[]: an array of paths to watch
*
* @param watchOptions - false to disable watching, or a path/paths to watch
* @param watchFn - async callback executed on change events
* @param defaultTarget - path(s) to watch when `watchOptions` is `true` (default: '.')
* @returns Resolves once the watcher has been started (or imme