orval 8.21.0 → 8.23.0
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,10 +1,12 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { DefaultTag, FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SupportedFormatter, asyncReduce, buildDynamicScope, buildSchemaTagMap, collectReferencedComponents, conventionName, createSuccessMessage, dynamicImport, fixCrossDirectoryImports, fixRegularSchemaImports, generateComponentDefinition, generateDependencyImports, generateMutator, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getBaseUrlRuntimeImports, getFileInfo, getFullRoute, getImportExtension, getMockFileExtensionByTypeName, getRefInfo, getRoute, isBoolean, isComponentRef, isFunction, isNullish, isObject, isReference, isString, isUrl, jsDoc, kebab, log, logError, logVerbose, logWarning, pascal, removeFilesAndEmptyFolders, resolveInstalledVersions, resolveRef, resolveValue, splitSchemasByType, upath, writeGeneratedFile, writeSchemas, writeSchemasTagsSplit, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
|
|
3
|
+
import { DefaultTag, FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SupportedFormatter, asyncReduce, buildDynamicScope, buildSchemaTagMap, collectReferencedComponents, conventionName, createSuccessMessage, dynamicImport, fixCrossDirectoryImports, fixRegularSchemaImports, generateComponentDefinition, generateDependencyImports, generateMutator, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getBaseUrlRuntimeImports, getFileInfo, getFullRoute, getImportExtension, getMockFileExtensionByTypeName, getRefInfo, getRoute, isBoolean, isComponentRef, isFunction, isNullish, isObject, isReference, isString, isUrl, jsDoc, kebab, log, logError, logVerbose, logWarning, pascal, removeFilesAndEmptyFolders, resolveInstalledVersions, resolveRef, resolveValue, splitSchemasByType, upath, writeGeneratedFile, writeSchemas, writeSchemasTagsSplit, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode, writeTagsOperationsMode, writeTagsOperationsSplitMode } from "@orval/core";
|
|
4
4
|
import { bundle } from "@scalar/json-magic/bundle";
|
|
5
5
|
import { fetchUrls, parseJson, parseYaml, readFiles } from "@scalar/json-magic/bundle/plugins/node";
|
|
6
6
|
import { upgrade, validate } from "@scalar/openapi-parser";
|
|
7
|
-
import {
|
|
7
|
+
import fs, { access, readFile } from "node:fs/promises";
|
|
8
|
+
import { isNullish as isNullish$1, pick } from "remeda";
|
|
9
|
+
import jsYaml from "js-yaml";
|
|
8
10
|
import * as mock from "@orval/mock";
|
|
9
11
|
import { dedupeStrictMockTypeDeclarations, generateFakerForSchemas, generateMockImports, getDefaultMockOptionsForType } from "@orval/mock";
|
|
10
12
|
import angular from "@orval/angular";
|
|
@@ -17,19 +19,17 @@ import query from "@orval/query";
|
|
|
17
19
|
import solidStart from "@orval/solid-start";
|
|
18
20
|
import swr from "@orval/swr";
|
|
19
21
|
import zod, { assertZodTarget, dereference, generateFormDataZodSchema, generateZodValidationSchemaDefinition, getZodImportSource, getZodTypeName, parseZodValidationSchemaDefinition, resolveIsZodV4 } from "@orval/zod";
|
|
20
|
-
import {
|
|
21
|
-
import fs from "fs-extra";
|
|
22
|
-
import fs$1, { access } from "node:fs/promises";
|
|
22
|
+
import fs$1, { existsSync } from "node:fs";
|
|
23
23
|
import { styleText } from "node:util";
|
|
24
|
-
import { parseArgsStringToArgv } from "string-argv";
|
|
25
|
-
import fs$2, { existsSync } from "node:fs";
|
|
26
24
|
import { findUp, findUpMultiple } from "find-up";
|
|
27
|
-
import
|
|
25
|
+
import fs$2 from "fs-extra";
|
|
28
26
|
import { parseTsconfig } from "get-tsconfig";
|
|
27
|
+
import { ExecaError, execa } from "execa";
|
|
28
|
+
import { parseArgsStringToArgv } from "string-argv";
|
|
29
29
|
import { createJiti } from "jiti";
|
|
30
30
|
//#region package.json
|
|
31
31
|
var name = "orval";
|
|
32
|
-
var version = "8.
|
|
32
|
+
var version = "8.23.0";
|
|
33
33
|
var description = "A swagger client generator for typescript";
|
|
34
34
|
//#endregion
|
|
35
35
|
//#region src/client.ts
|
|
@@ -348,594 +348,247 @@ function getApiSchemas({ input, output, target, workspace, spec }) {
|
|
|
348
348
|
];
|
|
349
349
|
}
|
|
350
350
|
//#endregion
|
|
351
|
-
//#region src/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
351
|
+
//#region src/utils/package-json.ts
|
|
352
|
+
const loadPackageJson = async (packageJson, workspace = process.cwd()) => {
|
|
353
|
+
if (!packageJson) {
|
|
354
|
+
const pkgPath = await findUp(["package.json"], { cwd: workspace });
|
|
355
|
+
if (pkgPath) {
|
|
356
|
+
const pkg = await dynamicImport(pkgPath, workspace);
|
|
357
|
+
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, pkgPath);
|
|
358
|
+
else throw new Error("Invalid package.json file");
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
358
361
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
const normalizedPath = normalizePath(packageJson, workspace);
|
|
363
|
+
if (fs$2.existsSync(normalizedPath)) {
|
|
364
|
+
const pkg = await dynamicImport(normalizedPath);
|
|
365
|
+
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, normalizedPath);
|
|
366
|
+
else throw new Error(`Invalid package.json file: ${normalizedPath}`);
|
|
364
367
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
throw new Error(`input.override.transformer must return an OpenAPI document object; got ${result === void 0 ? "undefined" : typeof result} from ${source}. Ensure your transformer returns the (possibly modified) spec.`);
|
|
368
|
+
};
|
|
369
|
+
const isPackageJson = (obj) => isObject(obj);
|
|
370
|
+
const resolvedCache = /* @__PURE__ */ new Map();
|
|
371
|
+
const resolveAndAttachVersions = (pkg, workspace, cacheKey) => {
|
|
372
|
+
const cached = resolvedCache.get(cacheKey);
|
|
373
|
+
if (cached) {
|
|
374
|
+
pkg.resolvedVersions = cached;
|
|
375
|
+
return pkg;
|
|
374
376
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
const resolved = resolveInstalledVersions(pkg, workspace);
|
|
378
|
+
if (Object.keys(resolved).length > 0) {
|
|
379
|
+
pkg.resolvedVersions = resolved;
|
|
380
|
+
resolvedCache.set(cacheKey, resolved);
|
|
381
|
+
for (const [name, version] of Object.entries(resolved)) logVerbose(styleText("dim", `Detected ${styleText("white", name)} v${styleText("white", version)}`));
|
|
382
|
+
}
|
|
383
|
+
return pkg;
|
|
384
|
+
};
|
|
385
|
+
const hasCatalogReferences = (pkg) => {
|
|
386
|
+
return [
|
|
387
|
+
...Object.entries(pkg.dependencies ?? {}),
|
|
388
|
+
...Object.entries(pkg.devDependencies ?? {}),
|
|
389
|
+
...Object.entries(pkg.peerDependencies ?? {})
|
|
390
|
+
].some(([, value]) => isString(value) && value.startsWith("catalog:"));
|
|
391
|
+
};
|
|
392
|
+
const loadPnpmWorkspaceCatalog = async (workspace) => {
|
|
393
|
+
const filePath = await findUp("pnpm-workspace.yaml", { cwd: workspace });
|
|
394
|
+
if (!filePath) return void 0;
|
|
395
|
+
try {
|
|
396
|
+
const file = await fs$2.readFile(filePath, "utf8");
|
|
397
|
+
const data = jsYaml.load(file);
|
|
398
|
+
if (!data?.catalog && !data?.catalogs) return void 0;
|
|
399
|
+
return {
|
|
400
|
+
catalog: data.catalog,
|
|
401
|
+
catalogs: data.catalogs
|
|
402
|
+
};
|
|
403
|
+
} catch {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const loadPackageJsonCatalog = async (workspace) => {
|
|
408
|
+
const filePaths = await findUpMultiple("package.json", { cwd: workspace });
|
|
409
|
+
for (const filePath of filePaths) try {
|
|
410
|
+
const pkg = await fs$2.readJson(filePath);
|
|
411
|
+
if (pkg.catalog || pkg.catalogs) return {
|
|
412
|
+
catalog: pkg.catalog,
|
|
413
|
+
catalogs: pkg.catalogs
|
|
414
|
+
};
|
|
415
|
+
} catch {}
|
|
416
|
+
};
|
|
417
|
+
const loadYarnrcCatalog = async (workspace) => {
|
|
418
|
+
const filePath = await findUp(".yarnrc.yml", { cwd: workspace });
|
|
419
|
+
if (!filePath) return void 0;
|
|
420
|
+
try {
|
|
421
|
+
const file = await fs$2.readFile(filePath, "utf8");
|
|
422
|
+
const data = jsYaml.load(file);
|
|
423
|
+
if (!data?.catalog && !data?.catalogs) return void 0;
|
|
424
|
+
return {
|
|
425
|
+
catalog: data.catalog,
|
|
426
|
+
catalogs: data.catalogs
|
|
427
|
+
};
|
|
428
|
+
} catch {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
const maybeReplaceCatalog = async (pkg, workspace) => {
|
|
433
|
+
if (!hasCatalogReferences(pkg)) return pkg;
|
|
434
|
+
const catalogData = await loadPnpmWorkspaceCatalog(workspace) ?? await loadPackageJsonCatalog(workspace) ?? await loadYarnrcCatalog(workspace);
|
|
435
|
+
if (!catalogData) {
|
|
436
|
+
logWarning("⚠️ package.json contains catalog: references, but no catalog source was found (checked: pnpm-workspace.yaml, package.json, .yarnrc.yml).");
|
|
437
|
+
return pkg;
|
|
438
|
+
}
|
|
439
|
+
performSubstitution(pkg.dependencies, catalogData);
|
|
440
|
+
performSubstitution(pkg.devDependencies, catalogData);
|
|
441
|
+
performSubstitution(pkg.peerDependencies, catalogData);
|
|
442
|
+
return pkg;
|
|
443
|
+
};
|
|
444
|
+
const performSubstitution = (dependencies, catalogData) => {
|
|
445
|
+
if (!dependencies) return;
|
|
446
|
+
for (const [packageName, version] of Object.entries(dependencies)) if (version === "catalog:" || version === "catalog:default") {
|
|
447
|
+
if (!catalogData.catalog) {
|
|
448
|
+
logWarning(`⚠️ catalog: substitution for the package '${packageName}' failed as there is no default catalog.`);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const sub = catalogData.catalog[packageName];
|
|
452
|
+
if (!sub) {
|
|
453
|
+
logWarning(`⚠️ catalog: substitution for the package '${packageName}' failed as there is no matching package in the default catalog.`);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
dependencies[packageName] = sub;
|
|
457
|
+
} else if (version.startsWith("catalog:")) {
|
|
458
|
+
const catalogName = version.slice(8);
|
|
459
|
+
const catalog = catalogData.catalogs?.[catalogName];
|
|
460
|
+
if (!catalog) {
|
|
461
|
+
logWarning(`⚠️ '${version}' substitution for the package '${packageName}' failed as there is no matching catalog named '${catalogName}'. (available named catalogs are: ${Object.keys(catalogData.catalogs ?? {}).join(", ")})`);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const sub = catalog[packageName];
|
|
465
|
+
if (!sub) {
|
|
466
|
+
logWarning(`⚠️ '${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(", ")})`);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
dependencies[packageName] = sub;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/utils/tsconfig.ts
|
|
474
|
+
const convertTarget = (config) => {
|
|
475
|
+
if (!config.compilerOptions?.target) return {
|
|
476
|
+
baseUrl: config.compilerOptions?.baseUrl,
|
|
477
|
+
...config
|
|
478
|
+
};
|
|
479
|
+
const lowercaseTarget = config.compilerOptions.target.toLowerCase();
|
|
480
|
+
return {
|
|
481
|
+
baseUrl: config.compilerOptions.baseUrl,
|
|
482
|
+
...config,
|
|
483
|
+
compilerOptions: {
|
|
484
|
+
...config.compilerOptions,
|
|
485
|
+
target: lowercaseTarget
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
|
|
490
|
+
if (isNullish(tsconfig)) {
|
|
491
|
+
const configPath = await findUp(["tsconfig.json", "jsconfig.json"], { cwd: workspace });
|
|
492
|
+
if (configPath) return convertTarget(parseTsconfig(configPath));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (isString(tsconfig)) {
|
|
496
|
+
const normalizedPath = normalizePath(tsconfig, workspace);
|
|
497
|
+
if (fs$2.existsSync(normalizedPath)) return convertTarget(parseTsconfig(normalizedPath));
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (isObject(tsconfig)) return tsconfig;
|
|
501
|
+
};
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/utils/options.ts
|
|
504
|
+
const INPUT_TARGET_FETCH_TIMEOUT_MS = 1e4;
|
|
377
505
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* the post-transformer pass (#3327); `origin` lets the second pass resolve refs
|
|
381
|
-
* relative to the original spec file when the input is an in-memory object.
|
|
506
|
+
* Type helper to make it easier to use orval.config.ts
|
|
507
|
+
* accepts a direct {@link ConfigExternal} object.
|
|
382
508
|
*/
|
|
383
|
-
|
|
384
|
-
return
|
|
385
|
-
plugins: [
|
|
386
|
-
readFiles(),
|
|
387
|
-
fetchUrls({ headers: parserOptions?.headers }),
|
|
388
|
-
parseJson(),
|
|
389
|
-
parseYaml()
|
|
390
|
-
],
|
|
391
|
-
treeShake: false,
|
|
392
|
-
...origin ? { origin } : {}
|
|
393
|
-
}));
|
|
509
|
+
function defineConfig(options) {
|
|
510
|
+
return options;
|
|
394
511
|
}
|
|
395
512
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
* `#` (an in-document pointer). Used to decide whether a transformer introduced
|
|
399
|
-
* new external refs that need a second bundle pass (#3327) — when it did not,
|
|
400
|
-
* the already-bundled spec is returned untouched.
|
|
513
|
+
* Type helper to make it easier to write input transformers.
|
|
514
|
+
* accepts a direct {@link InputTransformerFn} function.
|
|
401
515
|
*/
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
if (isObject(obj)) {
|
|
405
|
-
if ("$ref" in obj && isString(obj.$ref) && !obj.$ref.startsWith("#")) return true;
|
|
406
|
-
return Object.values(obj).some((value) => hasExternalRef(value));
|
|
407
|
-
}
|
|
408
|
-
return false;
|
|
516
|
+
function defineTransformer(transformer) {
|
|
517
|
+
return transformer;
|
|
409
518
|
}
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
return
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
workspace,
|
|
423
|
-
|
|
424
|
-
}
|
|
519
|
+
function createFormData(workspace, formData) {
|
|
520
|
+
const defaultArrayHandling = FormDataArrayHandling.SERIALIZE;
|
|
521
|
+
if (formData === void 0) return {
|
|
522
|
+
disabled: false,
|
|
523
|
+
arrayHandling: defaultArrayHandling
|
|
524
|
+
};
|
|
525
|
+
if (isBoolean(formData)) return {
|
|
526
|
+
disabled: !formData,
|
|
527
|
+
arrayHandling: defaultArrayHandling
|
|
528
|
+
};
|
|
529
|
+
if (isString(formData)) return {
|
|
530
|
+
disabled: false,
|
|
531
|
+
mutator: normalizeMutator(workspace, formData),
|
|
532
|
+
arrayHandling: defaultArrayHandling
|
|
533
|
+
};
|
|
534
|
+
if ("mutator" in formData || "arrayHandling" in formData) return {
|
|
535
|
+
disabled: false,
|
|
536
|
+
mutator: normalizeMutator(workspace, formData.mutator),
|
|
537
|
+
arrayHandling: formData.arrayHandling ?? defaultArrayHandling
|
|
538
|
+
};
|
|
539
|
+
return {
|
|
540
|
+
disabled: false,
|
|
541
|
+
mutator: normalizeMutator(workspace, formData),
|
|
542
|
+
arrayHandling: defaultArrayHandling
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function normalizeSchemasOption(schemas, workspace) {
|
|
546
|
+
if (!schemas) return;
|
|
547
|
+
if (isString(schemas)) return normalizePath(schemas, workspace);
|
|
548
|
+
if (!isString(schemas.path) || schemas.path.trim() === "") throw new Error(styleText("red", `\`schemas.path\` is required when \`schemas\` is an object (e.g. \`schemas: { path: './model', type: 'zod' }\`). To generate schemas alongside the target instead, omit \`schemas\` or set \`schemas: false\`.`));
|
|
549
|
+
validatePackageSpecifier(schemas.importPath, "schemas.importPath");
|
|
550
|
+
return {
|
|
551
|
+
path: normalizePath(schemas.path, workspace),
|
|
552
|
+
type: schemas.type ?? "typescript",
|
|
553
|
+
importPath: schemas.importPath,
|
|
554
|
+
splitByTags: schemas.splitByTags ?? false
|
|
555
|
+
};
|
|
425
556
|
}
|
|
426
|
-
const COMPONENT_KEY_PATTERN = /^[a-zA-Z0-9.\-_]+$/;
|
|
427
|
-
const COMPONENT_SECTIONS = [
|
|
428
|
-
"schemas",
|
|
429
|
-
"responses",
|
|
430
|
-
"parameters",
|
|
431
|
-
"examples",
|
|
432
|
-
"requestBodies",
|
|
433
|
-
"headers",
|
|
434
|
-
"securitySchemes",
|
|
435
|
-
"links",
|
|
436
|
-
"callbacks",
|
|
437
|
-
"pathItems"
|
|
438
|
-
];
|
|
439
557
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
|
|
444
|
-
function validateComponentKeys(data) {
|
|
445
|
-
const components = data.components;
|
|
446
|
-
if (!isObject(components)) return;
|
|
447
|
-
const invalidKeys = [];
|
|
448
|
-
for (const section of COMPONENT_SECTIONS) {
|
|
449
|
-
const sectionObj = components[section];
|
|
450
|
-
if (!isObject(sectionObj)) continue;
|
|
451
|
-
for (const key of Object.keys(sectionObj)) if (!COMPONENT_KEY_PATTERN.test(key)) invalidKeys.push(`components.${section}.${key}`);
|
|
452
|
-
}
|
|
453
|
-
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"));
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* The plugins from `@scalar/json-magic` does not dereference $ref.
|
|
457
|
-
* Instead it fetches them and puts them under x-ext, and changes the $ref to point to #x-ext/<name>.
|
|
458
|
-
* This function:
|
|
459
|
-
* 1. Merges external schemas into main spec's components.schemas (with collision handling)
|
|
460
|
-
* 2. Replaces x-ext refs with standard component refs or inlined content
|
|
461
|
-
*/
|
|
462
|
-
function dereferenceExternalRef(data) {
|
|
463
|
-
const extensions = data["x-ext"] ?? {};
|
|
464
|
-
const schemaNameMappings = mergeExternalSchemas(data, extensions);
|
|
465
|
-
const result = {};
|
|
466
|
-
for (const [key, value] of Object.entries(data)) if (key !== "x-ext") result[key] = replaceXExtRefs(value, extensions, schemaNameMappings);
|
|
467
|
-
return result;
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Merge external document schemas into main spec's components.schemas
|
|
471
|
-
* Returns mapping of original schema names to final names (with suffixes for collisions)
|
|
472
|
-
*/
|
|
473
|
-
function mergeExternalSchemas(data, extensions) {
|
|
474
|
-
const schemaNameMappings = {};
|
|
475
|
-
if (Object.keys(extensions).length === 0) return schemaNameMappings;
|
|
476
|
-
data.components ??= {};
|
|
477
|
-
const mainComponents = data.components;
|
|
478
|
-
mainComponents.schemas ??= {};
|
|
479
|
-
const mainSchemas = mainComponents.schemas;
|
|
480
|
-
for (const [extKey, extDoc] of Object.entries(extensions)) {
|
|
481
|
-
schemaNameMappings[extKey] = {};
|
|
482
|
-
if (isObject(extDoc) && "components" in extDoc) {
|
|
483
|
-
const extComponents = extDoc.components;
|
|
484
|
-
if (isObject(extComponents) && "schemas" in extComponents) {
|
|
485
|
-
const extSchemas = extComponents.schemas;
|
|
486
|
-
for (const [schemaName, schema] of Object.entries(extSchemas)) {
|
|
487
|
-
const existingSchema = mainSchemas[schemaName];
|
|
488
|
-
const isXExtRef = isObject(existingSchema) && "$ref" in existingSchema && isString(existingSchema.$ref) && existingSchema.$ref.startsWith("#/x-ext/");
|
|
489
|
-
let finalSchemaName = schemaName;
|
|
490
|
-
if (schemaName in mainSchemas && !isXExtRef) {
|
|
491
|
-
finalSchemaName = `${schemaName}_${extKey.replaceAll(/[^a-zA-Z0-9]/g, "_")}`;
|
|
492
|
-
schemaNameMappings[extKey][schemaName] = finalSchemaName;
|
|
493
|
-
} else schemaNameMappings[extKey][schemaName] = schemaName;
|
|
494
|
-
mainSchemas[finalSchemaName] = scrubUnwantedKeys(schema);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
for (const [extKey, mapping] of Object.entries(schemaNameMappings)) for (const [, finalName] of Object.entries(mapping)) {
|
|
500
|
-
const schema = mainSchemas[finalName];
|
|
501
|
-
if (schema) mainSchemas[finalName] = updateInternalRefs(schema, extKey, schemaNameMappings);
|
|
502
|
-
}
|
|
503
|
-
return schemaNameMappings;
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Remove unwanted keys like $schema and $id from objects
|
|
507
|
-
*/
|
|
508
|
-
function scrubUnwantedKeys(obj) {
|
|
509
|
-
const UNWANTED_KEYS = new Set(["$schema", "$id"]);
|
|
510
|
-
if (obj === null || obj === void 0) return obj;
|
|
511
|
-
if (Array.isArray(obj)) return obj.map((x) => scrubUnwantedKeys(x));
|
|
512
|
-
if (isObject(obj)) {
|
|
513
|
-
const rec = obj;
|
|
514
|
-
const out = {};
|
|
515
|
-
for (const [k, v] of Object.entries(rec)) {
|
|
516
|
-
if (UNWANTED_KEYS.has(k)) continue;
|
|
517
|
-
out[k] = scrubUnwantedKeys(v);
|
|
518
|
-
}
|
|
519
|
-
return out;
|
|
520
|
-
}
|
|
521
|
-
return obj;
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Update internal refs within an external schema to use suffixed names
|
|
525
|
-
*/
|
|
526
|
-
function updateInternalRefs(obj, extKey, schemaNameMappings) {
|
|
527
|
-
if (obj === null || obj === void 0) return obj;
|
|
528
|
-
if (Array.isArray(obj)) return obj.map((element) => updateInternalRefs(element, extKey, schemaNameMappings));
|
|
529
|
-
if (isObject(obj)) {
|
|
530
|
-
const record = obj;
|
|
531
|
-
if ("$ref" in record && isString(record.$ref)) {
|
|
532
|
-
const refValue = record.$ref;
|
|
533
|
-
if (refValue.startsWith("#/components/schemas/")) {
|
|
534
|
-
const schemaName = refValue.replace("#/components/schemas/", "");
|
|
535
|
-
const mappedName = schemaNameMappings[extKey][schemaName];
|
|
536
|
-
if (mappedName) return { $ref: `#/components/schemas/${mappedName}` };
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
const result = {};
|
|
540
|
-
for (const [key, value] of Object.entries(record)) result[key] = updateInternalRefs(value, extKey, schemaNameMappings);
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
return obj;
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Decode a single JSON Pointer reference token taken from an x-ext `$ref`.
|
|
547
|
-
*
|
|
548
|
-
* The token carries two layers of encoding: it sits in a URI fragment, so it
|
|
549
|
-
* may be percent-encoded (e.g. `%7B` for `{` in templated paths), and it is a
|
|
550
|
-
* JSON Pointer token, so `~1`/`~0` stand for `/`/`~` (RFC 6901). Percent-
|
|
551
|
-
* encoding is the outer layer and is removed first; a malformed sequence is
|
|
552
|
-
* left as-is rather than throwing. Without this, tokens such as `~1pets`
|
|
553
|
-
* never match the real `/pets` key and the external `$ref` fails to resolve.
|
|
554
|
-
*/
|
|
555
|
-
function decodeRefToken(token) {
|
|
556
|
-
let decoded = token;
|
|
557
|
-
try {
|
|
558
|
-
decoded = decodeURIComponent(token);
|
|
559
|
-
} catch {}
|
|
560
|
-
return decoded.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Replace x-ext refs with standard component refs, or inline the content.
|
|
564
|
-
* `inliningRefs` tracks the inline chain to break cycles in recursive
|
|
565
|
-
* external schemas that aren't under `components.schemas` (#1642).
|
|
566
|
-
*/
|
|
567
|
-
function replaceXExtRefs(obj, extensions, schemaNameMappings, inliningRefs = /* @__PURE__ */ new Set()) {
|
|
568
|
-
if (isNullish$1(obj)) return obj;
|
|
569
|
-
if (Array.isArray(obj)) return obj.map((element) => replaceXExtRefs(element, extensions, schemaNameMappings, inliningRefs));
|
|
570
|
-
if (isObject(obj)) {
|
|
571
|
-
const record = obj;
|
|
572
|
-
if ("$ref" in record && isString(record.$ref)) {
|
|
573
|
-
const refValue = record.$ref;
|
|
574
|
-
if (refValue.startsWith("#/x-ext/")) {
|
|
575
|
-
const parts = refValue.replace("#/x-ext/", "").split("/");
|
|
576
|
-
const extKey = parts.shift();
|
|
577
|
-
if (extKey) {
|
|
578
|
-
if (parts.length >= 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
579
|
-
const schemaName = parts.slice(2).join("/");
|
|
580
|
-
return { $ref: `#/components/schemas/${schemaNameMappings[extKey][schemaName] || schemaName}` };
|
|
581
|
-
}
|
|
582
|
-
if (inliningRefs.has(refValue)) {
|
|
583
|
-
logWarning(`Detected a circular external $ref while inlining "${refValue}". Replacing with an empty schema to avoid infinite recursion. Move the schema under "components.schemas" in its source file or pre-bundle the spec to keep the recursion intact.`);
|
|
584
|
-
return {};
|
|
585
|
-
}
|
|
586
|
-
let refObj = extensions[extKey];
|
|
587
|
-
for (const rawPart of parts) {
|
|
588
|
-
const p = decodeRefToken(rawPart);
|
|
589
|
-
if (refObj && (isObject(refObj) || Array.isArray(refObj)) && p in refObj) refObj = refObj[p];
|
|
590
|
-
else {
|
|
591
|
-
refObj = void 0;
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (refObj) {
|
|
596
|
-
const cleaned = scrubUnwantedKeys(refObj);
|
|
597
|
-
const nextInlining = new Set(inliningRefs);
|
|
598
|
-
nextInlining.add(refValue);
|
|
599
|
-
return replaceXExtRefs(cleaned, extensions, schemaNameMappings, nextInlining);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
const result = {};
|
|
605
|
-
for (const [key, value] of Object.entries(record)) result[key] = replaceXExtRefs(value, extensions, schemaNameMappings, inliningRefs);
|
|
606
|
-
return result;
|
|
607
|
-
}
|
|
608
|
-
return obj;
|
|
609
|
-
}
|
|
610
|
-
//#endregion
|
|
611
|
-
//#region src/formatters/prettier.ts
|
|
612
|
-
/**
|
|
613
|
-
* Format files with prettier.
|
|
614
|
-
* Tries the programmatic API first (project dependency),
|
|
615
|
-
* then falls back to the globally installed CLI.
|
|
558
|
+
* Validates that a config value is a valid package specifier (bare specifier
|
|
559
|
+
* or sub-path import like `@acme/models` / `@acme/models/fakers`). Rejects
|
|
560
|
+
* empty, whitespace-only, relative (`./`, `../`), and absolute paths with a
|
|
561
|
+
* clear, actionable error message. No-op when the value is `undefined`.
|
|
616
562
|
*/
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
try {
|
|
625
|
-
const content = await fs$1.readFile(filePath, "utf8");
|
|
626
|
-
const formatted = await prettier.format(content, {
|
|
627
|
-
...config,
|
|
628
|
-
filepath: filePath
|
|
629
|
-
});
|
|
630
|
-
await fs$1.writeFile(filePath, formatted);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
if (isMissingFileError(error)) return;
|
|
633
|
-
if (error instanceof Error) if (error.name === "UndefinedParserError") {} else logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: ${error.toString()}`);
|
|
634
|
-
else logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: unknown error`);
|
|
635
|
-
}
|
|
636
|
-
}));
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
try {
|
|
640
|
-
await execa("prettier", ["--write", ...paths]);
|
|
641
|
-
} catch {
|
|
642
|
-
logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}prettier not found. Install it as a project dependency or globally.`);
|
|
643
|
-
}
|
|
563
|
+
function validatePackageSpecifier(value, fieldName) {
|
|
564
|
+
if (value === void 0) return;
|
|
565
|
+
if (!value) throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received an empty string.`);
|
|
566
|
+
if (value.trim() === "") throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received a whitespace-only string.`);
|
|
567
|
+
if (value.trim() !== value) throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received a value with leading or trailing whitespace: "${value}"`);
|
|
568
|
+
if (value.startsWith(".")) throw new Error(`\`${fieldName}\` must be a package specifier (e.g. '@acme/models'), not a relative path. Received: "${value}"`);
|
|
569
|
+
if (path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\")) throw new Error(`\`${fieldName}\` must be a package specifier (e.g. '@acme/models'), not an absolute path. Received: "${value}"`);
|
|
644
570
|
}
|
|
645
|
-
function
|
|
646
|
-
return
|
|
571
|
+
function looksLikePackageSpecifier(value) {
|
|
572
|
+
return !!value && value.trim() === value && !value.startsWith(".") && !path.isAbsolute(value) && !/^[A-Za-z]:[\\/]/.test(value) && !value.startsWith("\\\\");
|
|
647
573
|
}
|
|
648
|
-
|
|
649
|
-
* Try to import prettier from the project's dependencies.
|
|
650
|
-
* Returns undefined if prettier is not installed.
|
|
651
|
-
*/
|
|
652
|
-
async function tryImportPrettier() {
|
|
574
|
+
function resolvePackageSpecifier(workspace, value) {
|
|
653
575
|
try {
|
|
654
|
-
return
|
|
576
|
+
return createRequire(path.join(workspace, "package.json")).resolve(value);
|
|
655
577
|
} catch {
|
|
656
578
|
return;
|
|
657
579
|
}
|
|
658
580
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const subFiles = await collectFilePaths((await fs$1.readdir(absolute)).map((entry) => path.join(absolute, entry)));
|
|
671
|
-
results.push(...subFiles);
|
|
672
|
-
}
|
|
673
|
-
} catch {}
|
|
674
|
-
}
|
|
675
|
-
return results;
|
|
676
|
-
}
|
|
677
|
-
//#endregion
|
|
678
|
-
//#region src/utils/execute-hook.ts
|
|
679
|
-
const executeHook = async (name, commands = [], args = []) => {
|
|
680
|
-
log(styleText("white", `Running ${name} hook...`));
|
|
681
|
-
for (const command of commands) try {
|
|
682
|
-
if (isString(command)) await executeCommand(command, args);
|
|
683
|
-
else if (isFunction(command)) await command(args);
|
|
684
|
-
else if (isObject(command)) await executeObjectCommand(command, args);
|
|
685
|
-
} catch (error) {
|
|
686
|
-
logError(error, `Failed to run ${name} hook`);
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
async function executeCommand(command, args) {
|
|
690
|
-
const [cmd, ..._args] = [...parseArgsStringToArgv(command), ...args];
|
|
691
|
-
await execa(cmd, _args);
|
|
692
|
-
}
|
|
693
|
-
async function executeObjectCommand(command, args) {
|
|
694
|
-
if (command.injectGeneratedDirsAndFiles === false) args = [];
|
|
695
|
-
if (isString(command.command)) await executeCommand(command.command, args);
|
|
696
|
-
else if (isFunction(command.command)) await command.command();
|
|
697
|
-
}
|
|
698
|
-
//#endregion
|
|
699
|
-
//#region src/utils/package-json.ts
|
|
700
|
-
const loadPackageJson = async (packageJson, workspace = process.cwd()) => {
|
|
701
|
-
if (!packageJson) {
|
|
702
|
-
const pkgPath = await findUp(["package.json"], { cwd: workspace });
|
|
703
|
-
if (pkgPath) {
|
|
704
|
-
const pkg = await dynamicImport(pkgPath, workspace);
|
|
705
|
-
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, pkgPath);
|
|
706
|
-
else throw new Error("Invalid package.json file");
|
|
707
|
-
}
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
const normalizedPath = normalizePath(packageJson, workspace);
|
|
711
|
-
if (fs.existsSync(normalizedPath)) {
|
|
712
|
-
const pkg = await dynamicImport(normalizedPath);
|
|
713
|
-
if (isPackageJson(pkg)) return resolveAndAttachVersions(await maybeReplaceCatalog(pkg, workspace), workspace, normalizedPath);
|
|
714
|
-
else throw new Error(`Invalid package.json file: ${normalizedPath}`);
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
const isPackageJson = (obj) => isObject(obj);
|
|
718
|
-
const resolvedCache = /* @__PURE__ */ new Map();
|
|
719
|
-
const resolveAndAttachVersions = (pkg, workspace, cacheKey) => {
|
|
720
|
-
const cached = resolvedCache.get(cacheKey);
|
|
721
|
-
if (cached) {
|
|
722
|
-
pkg.resolvedVersions = cached;
|
|
723
|
-
return pkg;
|
|
724
|
-
}
|
|
725
|
-
const resolved = resolveInstalledVersions(pkg, workspace);
|
|
726
|
-
if (Object.keys(resolved).length > 0) {
|
|
727
|
-
pkg.resolvedVersions = resolved;
|
|
728
|
-
resolvedCache.set(cacheKey, resolved);
|
|
729
|
-
for (const [name, version] of Object.entries(resolved)) logVerbose(styleText("dim", `Detected ${styleText("white", name)} v${styleText("white", version)}`));
|
|
730
|
-
}
|
|
731
|
-
return pkg;
|
|
732
|
-
};
|
|
733
|
-
const hasCatalogReferences = (pkg) => {
|
|
734
|
-
return [
|
|
735
|
-
...Object.entries(pkg.dependencies ?? {}),
|
|
736
|
-
...Object.entries(pkg.devDependencies ?? {}),
|
|
737
|
-
...Object.entries(pkg.peerDependencies ?? {})
|
|
738
|
-
].some(([, value]) => isString(value) && value.startsWith("catalog:"));
|
|
739
|
-
};
|
|
740
|
-
const loadPnpmWorkspaceCatalog = async (workspace) => {
|
|
741
|
-
const filePath = await findUp("pnpm-workspace.yaml", { cwd: workspace });
|
|
742
|
-
if (!filePath) return void 0;
|
|
743
|
-
try {
|
|
744
|
-
const file = await fs.readFile(filePath, "utf8");
|
|
745
|
-
const data = yaml.load(file);
|
|
746
|
-
if (!data?.catalog && !data?.catalogs) return void 0;
|
|
747
|
-
return {
|
|
748
|
-
catalog: data.catalog,
|
|
749
|
-
catalogs: data.catalogs
|
|
750
|
-
};
|
|
751
|
-
} catch {
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
const loadPackageJsonCatalog = async (workspace) => {
|
|
756
|
-
const filePaths = await findUpMultiple("package.json", { cwd: workspace });
|
|
757
|
-
for (const filePath of filePaths) try {
|
|
758
|
-
const pkg = await fs.readJson(filePath);
|
|
759
|
-
if (pkg.catalog || pkg.catalogs) return {
|
|
760
|
-
catalog: pkg.catalog,
|
|
761
|
-
catalogs: pkg.catalogs
|
|
762
|
-
};
|
|
763
|
-
} catch {}
|
|
764
|
-
};
|
|
765
|
-
const loadYarnrcCatalog = async (workspace) => {
|
|
766
|
-
const filePath = await findUp(".yarnrc.yml", { cwd: workspace });
|
|
767
|
-
if (!filePath) return void 0;
|
|
768
|
-
try {
|
|
769
|
-
const file = await fs.readFile(filePath, "utf8");
|
|
770
|
-
const data = yaml.load(file);
|
|
771
|
-
if (!data?.catalog && !data?.catalogs) return void 0;
|
|
772
|
-
return {
|
|
773
|
-
catalog: data.catalog,
|
|
774
|
-
catalogs: data.catalogs
|
|
775
|
-
};
|
|
776
|
-
} catch {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
const maybeReplaceCatalog = async (pkg, workspace) => {
|
|
781
|
-
if (!hasCatalogReferences(pkg)) return pkg;
|
|
782
|
-
const catalogData = await loadPnpmWorkspaceCatalog(workspace) ?? await loadPackageJsonCatalog(workspace) ?? await loadYarnrcCatalog(workspace);
|
|
783
|
-
if (!catalogData) {
|
|
784
|
-
logWarning("⚠️ package.json contains catalog: references, but no catalog source was found (checked: pnpm-workspace.yaml, package.json, .yarnrc.yml).");
|
|
785
|
-
return pkg;
|
|
786
|
-
}
|
|
787
|
-
performSubstitution(pkg.dependencies, catalogData);
|
|
788
|
-
performSubstitution(pkg.devDependencies, catalogData);
|
|
789
|
-
performSubstitution(pkg.peerDependencies, catalogData);
|
|
790
|
-
return pkg;
|
|
791
|
-
};
|
|
792
|
-
const performSubstitution = (dependencies, catalogData) => {
|
|
793
|
-
if (!dependencies) return;
|
|
794
|
-
for (const [packageName, version] of Object.entries(dependencies)) if (version === "catalog:" || version === "catalog:default") {
|
|
795
|
-
if (!catalogData.catalog) {
|
|
796
|
-
logWarning(`⚠️ catalog: substitution for the package '${packageName}' failed as there is no default catalog.`);
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
const sub = catalogData.catalog[packageName];
|
|
800
|
-
if (!sub) {
|
|
801
|
-
logWarning(`⚠️ catalog: substitution for the package '${packageName}' failed as there is no matching package in the default catalog.`);
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
dependencies[packageName] = sub;
|
|
805
|
-
} else if (version.startsWith("catalog:")) {
|
|
806
|
-
const catalogName = version.slice(8);
|
|
807
|
-
const catalog = catalogData.catalogs?.[catalogName];
|
|
808
|
-
if (!catalog) {
|
|
809
|
-
logWarning(`⚠️ '${version}' substitution for the package '${packageName}' failed as there is no matching catalog named '${catalogName}'. (available named catalogs are: ${Object.keys(catalogData.catalogs ?? {}).join(", ")})`);
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
const sub = catalog[packageName];
|
|
813
|
-
if (!sub) {
|
|
814
|
-
logWarning(`⚠️ '${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(", ")})`);
|
|
815
|
-
continue;
|
|
816
|
-
}
|
|
817
|
-
dependencies[packageName] = sub;
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
//#endregion
|
|
821
|
-
//#region src/utils/tsconfig.ts
|
|
822
|
-
const convertTarget = (config) => {
|
|
823
|
-
if (!config.compilerOptions?.target) return {
|
|
824
|
-
baseUrl: config.compilerOptions?.baseUrl,
|
|
825
|
-
...config
|
|
826
|
-
};
|
|
827
|
-
const lowercaseTarget = config.compilerOptions.target.toLowerCase();
|
|
828
|
-
return {
|
|
829
|
-
baseUrl: config.compilerOptions.baseUrl,
|
|
830
|
-
...config,
|
|
831
|
-
compilerOptions: {
|
|
832
|
-
...config.compilerOptions,
|
|
833
|
-
target: lowercaseTarget
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
};
|
|
837
|
-
const loadTsconfig = async (tsconfig, workspace = process.cwd()) => {
|
|
838
|
-
if (isNullish(tsconfig)) {
|
|
839
|
-
const configPath = await findUp(["tsconfig.json", "jsconfig.json"], { cwd: workspace });
|
|
840
|
-
if (configPath) return convertTarget(parseTsconfig(configPath));
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
if (isString(tsconfig)) {
|
|
844
|
-
const normalizedPath = normalizePath(tsconfig, workspace);
|
|
845
|
-
if (fs.existsSync(normalizedPath)) return convertTarget(parseTsconfig(normalizedPath));
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
if (isObject(tsconfig)) return tsconfig;
|
|
849
|
-
};
|
|
850
|
-
//#endregion
|
|
851
|
-
//#region src/utils/options.ts
|
|
852
|
-
const INPUT_TARGET_FETCH_TIMEOUT_MS = 1e4;
|
|
853
|
-
/**
|
|
854
|
-
* Type helper to make it easier to use orval.config.ts
|
|
855
|
-
* accepts a direct {@link ConfigExternal} object.
|
|
856
|
-
*/
|
|
857
|
-
function defineConfig(options) {
|
|
858
|
-
return options;
|
|
859
|
-
}
|
|
860
|
-
/**
|
|
861
|
-
* Type helper to make it easier to write input transformers.
|
|
862
|
-
* accepts a direct {@link InputTransformerFn} function.
|
|
863
|
-
*/
|
|
864
|
-
function defineTransformer(transformer) {
|
|
865
|
-
return transformer;
|
|
866
|
-
}
|
|
867
|
-
function createFormData(workspace, formData) {
|
|
868
|
-
const defaultArrayHandling = FormDataArrayHandling.SERIALIZE;
|
|
869
|
-
if (formData === void 0) return {
|
|
870
|
-
disabled: false,
|
|
871
|
-
arrayHandling: defaultArrayHandling
|
|
872
|
-
};
|
|
873
|
-
if (isBoolean(formData)) return {
|
|
874
|
-
disabled: !formData,
|
|
875
|
-
arrayHandling: defaultArrayHandling
|
|
876
|
-
};
|
|
877
|
-
if (isString(formData)) return {
|
|
878
|
-
disabled: false,
|
|
879
|
-
mutator: normalizeMutator(workspace, formData),
|
|
880
|
-
arrayHandling: defaultArrayHandling
|
|
881
|
-
};
|
|
882
|
-
if ("mutator" in formData || "arrayHandling" in formData) return {
|
|
883
|
-
disabled: false,
|
|
884
|
-
mutator: normalizeMutator(workspace, formData.mutator),
|
|
885
|
-
arrayHandling: formData.arrayHandling ?? defaultArrayHandling
|
|
886
|
-
};
|
|
887
|
-
return {
|
|
888
|
-
disabled: false,
|
|
889
|
-
mutator: normalizeMutator(workspace, formData),
|
|
890
|
-
arrayHandling: defaultArrayHandling
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
function normalizeSchemasOption(schemas, workspace) {
|
|
894
|
-
if (!schemas) return;
|
|
895
|
-
if (isString(schemas)) return normalizePath(schemas, workspace);
|
|
896
|
-
validatePackageSpecifier(schemas.importPath, "schemas.importPath");
|
|
897
|
-
return {
|
|
898
|
-
path: normalizePath(schemas.path, workspace),
|
|
899
|
-
type: schemas.type ?? "typescript",
|
|
900
|
-
importPath: schemas.importPath,
|
|
901
|
-
splitByTags: schemas.splitByTags ?? false
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Validates that a config value is a valid package specifier (bare specifier
|
|
906
|
-
* or sub-path import like `@acme/models` / `@acme/models/fakers`). Rejects
|
|
907
|
-
* empty, whitespace-only, relative (`./`, `../`), and absolute paths with a
|
|
908
|
-
* clear, actionable error message. No-op when the value is `undefined`.
|
|
909
|
-
*/
|
|
910
|
-
function validatePackageSpecifier(value, fieldName) {
|
|
911
|
-
if (value === void 0) return;
|
|
912
|
-
if (!value) throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received an empty string.`);
|
|
913
|
-
if (value.trim() === "") throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received a whitespace-only string.`);
|
|
914
|
-
if (value.trim() !== value) throw new Error(`\`${fieldName}\` must be a non-empty package specifier (e.g. '@acme/models'). Received a value with leading or trailing whitespace: "${value}"`);
|
|
915
|
-
if (value.startsWith(".")) throw new Error(`\`${fieldName}\` must be a package specifier (e.g. '@acme/models'), not a relative path. Received: "${value}"`);
|
|
916
|
-
if (path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\")) throw new Error(`\`${fieldName}\` must be a package specifier (e.g. '@acme/models'), not an absolute path. Received: "${value}"`);
|
|
917
|
-
}
|
|
918
|
-
function looksLikePackageSpecifier(value) {
|
|
919
|
-
return !!value && value.trim() === value && !value.startsWith(".") && !path.isAbsolute(value) && !/^[A-Za-z]:[\\/]/.test(value) && !value.startsWith("\\\\");
|
|
920
|
-
}
|
|
921
|
-
function resolvePackageSpecifier(workspace, value) {
|
|
922
|
-
try {
|
|
923
|
-
return createRequire(path.join(workspace, "package.json")).resolve(value);
|
|
924
|
-
} catch {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
function isPackageSpecifierCandidate(workspace, value) {
|
|
929
|
-
if (!looksLikePackageSpecifier(value)) return false;
|
|
930
|
-
if (existsSync(path.resolve(workspace, value))) return false;
|
|
931
|
-
if (value.startsWith("@")) return true;
|
|
932
|
-
const [packageName] = value.split("/");
|
|
933
|
-
if (!value.includes("/")) return true;
|
|
934
|
-
for (let dir = workspace;;) {
|
|
935
|
-
if (existsSync(path.join(dir, "node_modules", packageName))) return true;
|
|
936
|
-
const parent = path.dirname(dir);
|
|
937
|
-
if (parent === dir) return false;
|
|
938
|
-
dir = parent;
|
|
581
|
+
function isPackageSpecifierCandidate(workspace, value) {
|
|
582
|
+
if (!looksLikePackageSpecifier(value)) return false;
|
|
583
|
+
if (existsSync(path.resolve(workspace, value))) return false;
|
|
584
|
+
if (value.startsWith("@")) return true;
|
|
585
|
+
const [packageName] = value.split("/");
|
|
586
|
+
if (!value.includes("/")) return true;
|
|
587
|
+
for (let dir = workspace;;) {
|
|
588
|
+
if (existsSync(path.join(dir, "node_modules", packageName))) return true;
|
|
589
|
+
const parent = path.dirname(dir);
|
|
590
|
+
if (parent === dir) return false;
|
|
591
|
+
dir = parent;
|
|
939
592
|
}
|
|
940
593
|
}
|
|
941
594
|
function normalizeEffectOptions(effect) {
|
|
@@ -955,7 +608,8 @@ function normalizeEffectOptions(effect) {
|
|
|
955
608
|
response: effect?.generate?.response ?? true
|
|
956
609
|
},
|
|
957
610
|
generateEachHttpStatus: effect?.generateEachHttpStatus ?? false,
|
|
958
|
-
useBrandedTypes: effect?.useBrandedTypes ?? false
|
|
611
|
+
useBrandedTypes: effect?.useBrandedTypes ?? false,
|
|
612
|
+
exactOptional: effect?.exactOptional ?? false
|
|
959
613
|
};
|
|
960
614
|
}
|
|
961
615
|
async function normalizeOptions(optionsExport, workspace = process.cwd(), globalOptions = {}) {
|
|
@@ -1132,6 +786,7 @@ async function normalizeOptions(optionsExport, workspace = process.cwd(), global
|
|
|
1132
786
|
generateReusableSchemas: outputOptions.override?.zod?.generateReusableSchemas ?? false,
|
|
1133
787
|
generateMeta: outputOptions.override?.zod?.generateMeta ?? false,
|
|
1134
788
|
generateDiscriminatedUnion: outputOptions.override?.zod?.generateDiscriminatedUnion ?? false,
|
|
789
|
+
exactOptional: outputOptions.override?.zod?.exactOptional ?? false,
|
|
1135
790
|
dateTimeOptions: outputOptions.override?.zod?.dateTimeOptions ?? { offset: true },
|
|
1136
791
|
timeOptions: outputOptions.override?.zod?.timeOptions ?? {}
|
|
1137
792
|
},
|
|
@@ -1232,205 +887,692 @@ async function resolveFirstValidTarget(targets, workspace, parserOptions) {
|
|
|
1232
887
|
} catch {
|
|
1233
888
|
continue;
|
|
1234
889
|
}
|
|
1235
|
-
continue;
|
|
1236
|
-
}
|
|
1237
|
-
const resolvedTarget = normalizePath(target, workspace);
|
|
1238
|
-
try {
|
|
1239
|
-
await access(resolvedTarget);
|
|
1240
|
-
return resolvedTarget;
|
|
1241
|
-
} catch {
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
const resolvedTarget = normalizePath(target, workspace);
|
|
893
|
+
try {
|
|
894
|
+
await access(resolvedTarget);
|
|
895
|
+
return resolvedTarget;
|
|
896
|
+
} catch {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
throw new Error(styleText("red", `None of the input targets could be resolved:\n${targets.map((target) => ` - ${target}`).join("\n")}`));
|
|
901
|
+
}
|
|
902
|
+
function getHeadersForUrl(url, headersConfig) {
|
|
903
|
+
if (!headersConfig) return {};
|
|
904
|
+
const { hostname } = new URL(url);
|
|
905
|
+
const matchedHeaders = {};
|
|
906
|
+
for (const headerEntry of headersConfig) if (headerEntry.domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) Object.assign(matchedHeaders, headerEntry.headers);
|
|
907
|
+
return matchedHeaders;
|
|
908
|
+
}
|
|
909
|
+
async function fetchWithTimeout(target, init) {
|
|
910
|
+
const controller = new AbortController();
|
|
911
|
+
const timeoutId = setTimeout(() => {
|
|
912
|
+
controller.abort();
|
|
913
|
+
}, INPUT_TARGET_FETCH_TIMEOUT_MS);
|
|
914
|
+
try {
|
|
915
|
+
return await fetch(target, {
|
|
916
|
+
...init,
|
|
917
|
+
signal: controller.signal
|
|
918
|
+
});
|
|
919
|
+
} finally {
|
|
920
|
+
clearTimeout(timeoutId);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function normalizePathOrUrl(path, workspace) {
|
|
924
|
+
if (isString(path) && !isUrl(path)) return normalizePath(path, workspace);
|
|
925
|
+
return path;
|
|
926
|
+
}
|
|
927
|
+
function normalizePath(path$1, workspace) {
|
|
928
|
+
if (!isString(path$1)) return path$1;
|
|
929
|
+
return path.resolve(workspace, path$1);
|
|
930
|
+
}
|
|
931
|
+
function normalizeOperationsAndTags(operationsOrTags, workspace, global, source) {
|
|
932
|
+
const unsupportedZodKeys = [
|
|
933
|
+
"version",
|
|
934
|
+
"variant",
|
|
935
|
+
"dateTimeOptions",
|
|
936
|
+
"timeOptions",
|
|
937
|
+
"generateEachHttpStatus",
|
|
938
|
+
"generateReusableSchemas",
|
|
939
|
+
"generateMeta",
|
|
940
|
+
"generateDiscriminatedUnion"
|
|
941
|
+
];
|
|
942
|
+
return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, paramsFilter, query, angular, zod, effect, ...rest }]) => {
|
|
943
|
+
const unsupportedOperationZodKeys = zod && unsupportedZodKeys.filter((unsupportedKey) => zod[unsupportedKey] !== void 0);
|
|
944
|
+
if (unsupportedOperationZodKeys && unsupportedOperationZodKeys.length) logWarning(`⚠️ override.${source}.${key}.zod only supports strict, generate, coerce, preprocess, params, and useBrandedTypes. Ignoring unsupported ${unsupportedOperationZodKeys.length === 1 ? "field" : "fields"}: ${unsupportedOperationZodKeys.map((unsupportedKey) => `zod.${unsupportedKey}`).join(", ")}.`);
|
|
945
|
+
const hasSupportedOperationZodConfig = !!zod && (zod.strict !== void 0 || zod.generate !== void 0 || zod.coerce !== void 0 || zod.preprocess !== void 0 || zod.params !== void 0 || zod.useBrandedTypes !== void 0);
|
|
946
|
+
return [key, {
|
|
947
|
+
...rest,
|
|
948
|
+
...angular ? { angular: {
|
|
949
|
+
provideIn: angular.provideIn ?? "root",
|
|
950
|
+
client: angular.retrievalClient ?? angular.client ?? "httpClient",
|
|
951
|
+
runtimeValidation: angular.runtimeValidation ?? false,
|
|
952
|
+
queryObjectSerialization: angular.queryObjectSerialization ?? "spec",
|
|
953
|
+
...angular.httpResource ? { httpResource: angular.httpResource } : {}
|
|
954
|
+
} } : {},
|
|
955
|
+
...query ? { query: normalizeQueryOptions(query, workspace, global.query) } : {},
|
|
956
|
+
...hasSupportedOperationZodConfig && zod ? { zod: {
|
|
957
|
+
strict: {
|
|
958
|
+
param: zod.strict?.param ?? false,
|
|
959
|
+
query: zod.strict?.query ?? false,
|
|
960
|
+
header: zod.strict?.header ?? false,
|
|
961
|
+
body: zod.strict?.body ?? false,
|
|
962
|
+
response: zod.strict?.response ?? false
|
|
963
|
+
},
|
|
964
|
+
generate: {
|
|
965
|
+
param: zod.generate?.param ?? true,
|
|
966
|
+
query: zod.generate?.query ?? true,
|
|
967
|
+
header: zod.generate?.header ?? true,
|
|
968
|
+
body: zod.generate?.body ?? true,
|
|
969
|
+
response: zod.generate?.response ?? true
|
|
970
|
+
},
|
|
971
|
+
coerce: {
|
|
972
|
+
param: zod.coerce?.param ?? false,
|
|
973
|
+
query: zod.coerce?.query ?? false,
|
|
974
|
+
header: zod.coerce?.header ?? false,
|
|
975
|
+
body: zod.coerce?.body ?? false,
|
|
976
|
+
response: zod.coerce?.response ?? false
|
|
977
|
+
},
|
|
978
|
+
preprocess: {
|
|
979
|
+
...zod.preprocess?.param ? { param: normalizeMutator(workspace, zod.preprocess.param) } : {},
|
|
980
|
+
...zod.preprocess?.query ? { query: normalizeMutator(workspace, zod.preprocess.query) } : {},
|
|
981
|
+
...zod.preprocess?.header ? { header: normalizeMutator(workspace, zod.preprocess.header) } : {},
|
|
982
|
+
...zod.preprocess?.body ? { body: normalizeMutator(workspace, zod.preprocess.body) } : {},
|
|
983
|
+
...zod.preprocess?.response ? { response: normalizeMutator(workspace, zod.preprocess.response) } : {}
|
|
984
|
+
},
|
|
985
|
+
...zod.params ? { params: normalizeMutator(workspace, zod.params) } : {},
|
|
986
|
+
useBrandedTypes: zod.useBrandedTypes ?? false
|
|
987
|
+
} } : {},
|
|
988
|
+
...effect ? { effect: normalizeEffectOptions(effect) } : {},
|
|
989
|
+
...transformer ? { transformer: normalizePath(transformer, workspace) } : {},
|
|
990
|
+
...mutator ? { mutator: normalizeMutator(workspace, mutator) } : {},
|
|
991
|
+
...formData === void 0 ? {} : { formData: createFormData(workspace, formData) },
|
|
992
|
+
...formUrlEncoded ? { formUrlEncoded: isBoolean(formUrlEncoded) ? formUrlEncoded : normalizeMutator(workspace, formUrlEncoded) } : {},
|
|
993
|
+
...paramsSerializer ? { paramsSerializer: normalizeMutator(workspace, paramsSerializer) } : {},
|
|
994
|
+
...paramsFilter ? { paramsFilter: normalizeMutator(workspace, paramsFilter) } : {}
|
|
995
|
+
}];
|
|
996
|
+
}));
|
|
997
|
+
}
|
|
998
|
+
function normalizeOutputMode(mode) {
|
|
999
|
+
if (!mode) return OutputMode.SINGLE;
|
|
1000
|
+
if (!Object.values(OutputMode).includes(mode)) {
|
|
1001
|
+
logWarning(`⚠️ Unknown provided mode => ${mode}`);
|
|
1002
|
+
return OutputMode.SINGLE;
|
|
1003
|
+
}
|
|
1004
|
+
return mode;
|
|
1005
|
+
}
|
|
1006
|
+
function normalizeHooks(hooks) {
|
|
1007
|
+
const keys = Object.keys(hooks);
|
|
1008
|
+
const result = {};
|
|
1009
|
+
for (const key of keys) if (isString(hooks[key])) result[key] = [hooks[key]];
|
|
1010
|
+
else if (Array.isArray(hooks[key])) result[key] = hooks[key];
|
|
1011
|
+
else if (isFunction(hooks[key])) result[key] = [hooks[key]];
|
|
1012
|
+
else if (isObject(hooks[key])) result[key] = [hooks[key]];
|
|
1013
|
+
return result;
|
|
1014
|
+
}
|
|
1015
|
+
function normalizeHonoOptions(hono = {}, workspace) {
|
|
1016
|
+
return {
|
|
1017
|
+
...hono.handlers ? { handlers: path.resolve(workspace, hono.handlers) } : {},
|
|
1018
|
+
handlerGenerationStrategy: hono.handlerGenerationStrategy ?? "smart",
|
|
1019
|
+
compositeRoute: hono.compositeRoute ? path.resolve(workspace, hono.compositeRoute) : "",
|
|
1020
|
+
validator: hono.validator ?? true,
|
|
1021
|
+
validatorOutputPath: hono.validatorOutputPath ? path.resolve(workspace, hono.validatorOutputPath) : ""
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function normalizeMcpServerOptions(server, workspace) {
|
|
1025
|
+
return {
|
|
1026
|
+
path: path.resolve(workspace, server.path),
|
|
1027
|
+
name: server.name,
|
|
1028
|
+
default: server.default ?? !server.name
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function normalizeMcpOptions(mcp = {}, workspace) {
|
|
1032
|
+
return mcp.server ? { server: normalizeMcpServerOptions(mcp.server, workspace) } : {};
|
|
1033
|
+
}
|
|
1034
|
+
function normalizeJSDocOptions(jsdoc = {}) {
|
|
1035
|
+
return { ...jsdoc };
|
|
1036
|
+
}
|
|
1037
|
+
function normalizeQueryOptions(queryOptions = {}, outputWorkspace, globalOptions = {}) {
|
|
1038
|
+
if (queryOptions.options) logWarning("⚠️ Using query options is deprecated and will be removed in a future major release. Please use queryOptions or mutationOptions instead.");
|
|
1039
|
+
return {
|
|
1040
|
+
...isNullish(queryOptions.usePrefetch) ? {} : { usePrefetch: queryOptions.usePrefetch },
|
|
1041
|
+
...isNullish(queryOptions.useInvalidate) ? {} : { useInvalidate: queryOptions.useInvalidate },
|
|
1042
|
+
...isNullish(queryOptions.useSetQueryData) ? {} : { useSetQueryData: queryOptions.useSetQueryData },
|
|
1043
|
+
...isNullish(queryOptions.useGetQueryData) ? {} : { useGetQueryData: queryOptions.useGetQueryData },
|
|
1044
|
+
...isNullish(queryOptions.useQuery) ? {} : { useQuery: queryOptions.useQuery },
|
|
1045
|
+
...isNullish(queryOptions.useSuspenseQuery) ? {} : { useSuspenseQuery: queryOptions.useSuspenseQuery },
|
|
1046
|
+
...isNullish(queryOptions.useMutation) ? {} : { useMutation: queryOptions.useMutation },
|
|
1047
|
+
...isNullish(queryOptions.useInfinite) ? {} : { useInfinite: queryOptions.useInfinite },
|
|
1048
|
+
...isNullish(queryOptions.useSuspenseInfiniteQuery) ? {} : { useSuspenseInfiniteQuery: queryOptions.useSuspenseInfiniteQuery },
|
|
1049
|
+
...queryOptions.useInfiniteQueryParam ? { useInfiniteQueryParam: queryOptions.useInfiniteQueryParam } : {},
|
|
1050
|
+
...queryOptions.options ? { options: queryOptions.options } : {},
|
|
1051
|
+
...globalOptions.queryKey ? { queryKey: globalOptions.queryKey } : {},
|
|
1052
|
+
...queryOptions.queryKey ? { queryKey: normalizeMutator(outputWorkspace, queryOptions.queryKey) } : {},
|
|
1053
|
+
...globalOptions.queryOptions ? { queryOptions: globalOptions.queryOptions } : {},
|
|
1054
|
+
...queryOptions.queryOptions ? { queryOptions: normalizeMutator(outputWorkspace, queryOptions.queryOptions) } : {},
|
|
1055
|
+
...globalOptions.mutationOptions ? { mutationOptions: globalOptions.mutationOptions } : {},
|
|
1056
|
+
...queryOptions.mutationOptions ? { mutationOptions: normalizeMutator(outputWorkspace, queryOptions.mutationOptions) } : {},
|
|
1057
|
+
...isNullish(globalOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: globalOptions.shouldExportQueryKey },
|
|
1058
|
+
...isNullish(queryOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: queryOptions.shouldExportQueryKey },
|
|
1059
|
+
...isNullish(globalOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: globalOptions.shouldFilterQueryKey },
|
|
1060
|
+
...isNullish(queryOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: queryOptions.shouldFilterQueryKey },
|
|
1061
|
+
...isNullish(globalOptions.queryKeyFilter) ? {} : { queryKeyFilter: globalOptions.queryKeyFilter },
|
|
1062
|
+
...isNullish(queryOptions.queryKeyFilter) ? {} : { queryKeyFilter: queryOptions.queryKeyFilter },
|
|
1063
|
+
...isNullish(globalOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: globalOptions.shouldExportHttpClient },
|
|
1064
|
+
...isNullish(queryOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: queryOptions.shouldExportHttpClient },
|
|
1065
|
+
...isNullish(globalOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: globalOptions.shouldExportMutatorHooks },
|
|
1066
|
+
...isNullish(queryOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: queryOptions.shouldExportMutatorHooks },
|
|
1067
|
+
...isNullish(globalOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: globalOptions.shouldSplitQueryKey },
|
|
1068
|
+
...isNullish(queryOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: queryOptions.shouldSplitQueryKey },
|
|
1069
|
+
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
|
|
1070
|
+
...isNullish(globalOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: globalOptions.useOperationIdAsQueryKey },
|
|
1071
|
+
...isNullish(queryOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: queryOptions.useOperationIdAsQueryKey },
|
|
1072
|
+
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
|
|
1073
|
+
...isNullish(queryOptions.signal) ? {} : { signal: queryOptions.signal },
|
|
1074
|
+
...isNullish(globalOptions.version) ? {} : { version: globalOptions.version },
|
|
1075
|
+
...isNullish(queryOptions.version) ? {} : { version: queryOptions.version },
|
|
1076
|
+
...queryOptions.mutationInvalidates ? { mutationInvalidates: queryOptions.mutationInvalidates } : {},
|
|
1077
|
+
...isNullish(globalOptions.runtimeValidation) ? {} : { runtimeValidation: globalOptions.runtimeValidation },
|
|
1078
|
+
...isNullish(queryOptions.runtimeValidation) ? {} : { runtimeValidation: queryOptions.runtimeValidation }
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function getDefaultFilesHeader({ title, description, version: version$1 } = {}) {
|
|
1082
|
+
return [
|
|
1083
|
+
`Generated by ${name} v${version} 🍺`,
|
|
1084
|
+
`Do not edit manually.`,
|
|
1085
|
+
...title ? [title] : [],
|
|
1086
|
+
...description ? [description] : [],
|
|
1087
|
+
...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
|
|
1088
|
+
];
|
|
1089
|
+
}
|
|
1090
|
+
//#endregion
|
|
1091
|
+
//#region src/import-specs.ts
|
|
1092
|
+
async function resolveSpec(input, { parserOptions, transformer, workspace, unsafeDisableValidation = false }) {
|
|
1093
|
+
const allowedRefs = parserOptions?.externalRefs?.allow ?? [];
|
|
1094
|
+
const isWildcard = allowedRefs.includes("*");
|
|
1095
|
+
const { data: specData, origin } = await loadSpec(input, parserOptions?.headers);
|
|
1096
|
+
if (!isWildcard) {
|
|
1097
|
+
const disallowed = collectExternalRefs(specData).filter((ref) => !isAllowedRef(ref, allowedRefs, origin));
|
|
1098
|
+
if (disallowed.length > 0) throw new Error(formatDisallowedRefsError(disallowed, allowedRefs));
|
|
1099
|
+
} else {
|
|
1100
|
+
const docs = [...new Set(collectExternalRefs(specData).map(getRefDocument))];
|
|
1101
|
+
if (docs.length > 0) logWarning(`External $ref documents being resolved:\n` + docs.map((d) => ` - ${d}`).join("\n"));
|
|
1102
|
+
}
|
|
1103
|
+
const dereferencedData = await bundleAndDereferenceExternalRefs(specData, parserOptions, origin, isWildcard, allowedRefs);
|
|
1104
|
+
let transformedData = dereferencedData;
|
|
1105
|
+
if (transformer) {
|
|
1106
|
+
const applied = await applyInputTransformer(dereferencedData, transformer, workspace);
|
|
1107
|
+
transformedData = collectExternalRefs(applied).length > 0 ? await bundleAndDereferenceExternalRefs(applied, parserOptions, origin, isWildcard, allowedRefs) : applied;
|
|
1108
|
+
}
|
|
1109
|
+
if (unsafeDisableValidation) logWarning("🚨 OpenAPI spec validation is disabled.\n Code generation with invalid specs is not guaranteed to work and may break in minor updates.\n Bug reports with validation disabled will not be accepted.");
|
|
1110
|
+
else {
|
|
1111
|
+
validateComponentKeys(transformedData);
|
|
1112
|
+
const { valid, errors } = await validate(transformedData);
|
|
1113
|
+
if (!valid) throw new Error(`OpenAPI spec validation failed:\n${JSON.stringify(errors, void 0, 2)}`);
|
|
1114
|
+
}
|
|
1115
|
+
const { specification } = upgrade(transformedData);
|
|
1116
|
+
return specification;
|
|
1117
|
+
}
|
|
1118
|
+
async function applyInputTransformer(data, transformer, workspace) {
|
|
1119
|
+
const transformerFn = await dynamicImport(transformer, workspace);
|
|
1120
|
+
const result = await transformerFn(data);
|
|
1121
|
+
if (!isObject(result)) {
|
|
1122
|
+
const source = isString(transformer) ? transformer : transformerFn.name || "<inline function>";
|
|
1123
|
+
throw new Error(`input.override.transformer must return an OpenAPI document object; got ${result === void 0 ? "undefined" : typeof result} from ${source}. Ensure your transformer returns the (possibly modified) spec.`);
|
|
1124
|
+
}
|
|
1125
|
+
return result;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Bundle external references into the document and then resolve the `x-ext`
|
|
1129
|
+
* entries that `@scalar/json-magic` produces. Shared by the initial pass and
|
|
1130
|
+
* the post-transformer pass (#3327); `origin` lets the second pass resolve refs
|
|
1131
|
+
* relative to the original spec file when the input is an in-memory object.
|
|
1132
|
+
*/
|
|
1133
|
+
async function bundleAndDereferenceExternalRefs(input, parserOptions, origin, isWildcard = false, allowedExternalRefs = []) {
|
|
1134
|
+
return dereferenceExternalRef(await bundle(input, {
|
|
1135
|
+
plugins: [
|
|
1136
|
+
createSafeFileLoader(origin, isWildcard, allowedExternalRefs),
|
|
1137
|
+
createSafeUrlLoader(origin, isWildcard, allowedExternalRefs, parserOptions?.headers),
|
|
1138
|
+
parseJson(),
|
|
1139
|
+
parseYaml()
|
|
1140
|
+
],
|
|
1141
|
+
treeShake: false,
|
|
1142
|
+
...origin ? { origin } : {}
|
|
1143
|
+
}));
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Load the top-level spec into an inline object so we can scan it for external
|
|
1147
|
+
* `$ref`s before `bundle()` resolves them. The top-level target is trusted
|
|
1148
|
+
* (user-configured `input.target`); only `$ref` values inside the spec are
|
|
1149
|
+
* untrusted.
|
|
1150
|
+
*/
|
|
1151
|
+
function parseSpec(text) {
|
|
1152
|
+
const result = jsYaml.load(text);
|
|
1153
|
+
if (!isObject(result)) throw new Error("OpenAPI spec must be a valid JSON/YAML object.");
|
|
1154
|
+
return result;
|
|
1155
|
+
}
|
|
1156
|
+
async function loadSpec(input, headers) {
|
|
1157
|
+
if (!isString(input)) return { data: input };
|
|
1158
|
+
if (isUrl(input)) {
|
|
1159
|
+
const response = await fetch(input, { headers: getHeadersForUrl(input, headers) });
|
|
1160
|
+
if (!response.ok) throw new Error(`Failed to fetch OpenAPI spec from ${input}: ${response.status} ${response.statusText}`);
|
|
1161
|
+
return {
|
|
1162
|
+
data: parseSpec(await response.text()),
|
|
1163
|
+
origin: input
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
return {
|
|
1167
|
+
data: parseSpec(await readFile(input, "utf-8")),
|
|
1168
|
+
origin: input
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Strip the JSON pointer fragment (`#/...`) from a `$ref` value, leaving only
|
|
1173
|
+
* the document target (file path or URL).
|
|
1174
|
+
*/
|
|
1175
|
+
function getRefDocument(ref) {
|
|
1176
|
+
const hashIndex = ref.indexOf("#");
|
|
1177
|
+
return hashIndex === -1 ? ref : ref.slice(0, hashIndex);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Collect all external `$ref` document targets from a spec object. Returns
|
|
1181
|
+
* deduplicated ref strings in their raw form (before fragment stripping).
|
|
1182
|
+
*/
|
|
1183
|
+
function collectExternalRefs(obj) {
|
|
1184
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1185
|
+
function walk(val) {
|
|
1186
|
+
if (Array.isArray(val)) {
|
|
1187
|
+
val.forEach(walk);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (isObject(val)) {
|
|
1191
|
+
if ("$ref" in val && isString(val.$ref) && !val.$ref.startsWith("#")) refs.add(val.$ref);
|
|
1192
|
+
Object.values(val).forEach(walk);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
walk(obj);
|
|
1196
|
+
return [...refs];
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Resolve a ref document target (the part before `#`) to a canonical path or
|
|
1200
|
+
* URL, so it can be compared against allow-list entries that were resolved the
|
|
1201
|
+
* same way.
|
|
1202
|
+
*/
|
|
1203
|
+
function resolveRefTarget(ref, origin) {
|
|
1204
|
+
const doc = getRefDocument(ref);
|
|
1205
|
+
if (isUrl(doc)) return new URL(doc).href;
|
|
1206
|
+
if (origin && isUrl(origin)) return new URL(doc, origin).href;
|
|
1207
|
+
if (origin) return path.resolve(path.dirname(origin), doc);
|
|
1208
|
+
return path.resolve(doc);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Check whether a `$ref` is allowed by the user's allow-list. Both the ref and
|
|
1212
|
+
* the allow-list entries are resolved against the spec origin so that
|
|
1213
|
+
* `./schemas/pet.yaml` in the spec matches `./schemas/pet.yaml` in the config.
|
|
1214
|
+
*/
|
|
1215
|
+
function isAllowedRef(ref, allowedExternalRefs, origin) {
|
|
1216
|
+
const resolved = resolveRefTarget(ref, origin);
|
|
1217
|
+
return allowedExternalRefs.some((entry) => resolveRefTarget(entry, origin) === resolved);
|
|
1218
|
+
}
|
|
1219
|
+
function formatDisallowedRefsError(disallowed, currentAllowed) {
|
|
1220
|
+
const docs = [...new Set(disallowed.map(getRefDocument))];
|
|
1221
|
+
const all = [...new Set([...currentAllowed, ...docs])];
|
|
1222
|
+
const configSnippet = JSON.stringify({ input: { parserOptions: { externalRefs: { allow: all } } } }, null, 2);
|
|
1223
|
+
return `External \$ref targets are not allowed by default.
|
|
1224
|
+
Add them to your config, or use externalRefs.allow: ['*'] to allow all.
|
|
1225
|
+
|
|
1226
|
+
Disallowed refs:\n${disallowed.map((r) => ` - ${r}`).join("\n")}\n\nSuggested config:\n${configSnippet}`;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Wrap `readFiles()` so every file read is checked against the allow-list.
|
|
1230
|
+
* The top-level spec file (matching `origin`) is always allowed; subsequent
|
|
1231
|
+
* reads must match an explicit entry or the wildcard.
|
|
1232
|
+
*/
|
|
1233
|
+
function createSafeFileLoader(origin, isWildcard, allowedExternalRefs) {
|
|
1234
|
+
const base = readFiles();
|
|
1235
|
+
return {
|
|
1236
|
+
type: "loader",
|
|
1237
|
+
validate: base.validate,
|
|
1238
|
+
async exec(value) {
|
|
1239
|
+
if (isWildcard) return base.exec(value);
|
|
1240
|
+
if (origin && path.resolve(value) === path.resolve(origin)) return base.exec(value);
|
|
1241
|
+
if (!isAllowedRef(value, allowedExternalRefs, origin)) throw new Error(`Refused to read external file: ${value}\nAdd it to externalRefs.allow or use ['*'] to allow all.`);
|
|
1242
|
+
return base.exec(value);
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Wrap `fetchUrls()` so every URL fetch is checked against the allow-list.
|
|
1248
|
+
* The top-level spec URL (matching `origin`) is always allowed; subsequent
|
|
1249
|
+
* fetches must match an explicit entry or the wildcard.
|
|
1250
|
+
*/
|
|
1251
|
+
function createSafeUrlLoader(origin, isWildcard, allowedExternalRefs, headers) {
|
|
1252
|
+
const base = fetchUrls({ headers });
|
|
1253
|
+
return {
|
|
1254
|
+
type: "loader",
|
|
1255
|
+
validate: base.validate,
|
|
1256
|
+
async exec(value) {
|
|
1257
|
+
if (isWildcard) return base.exec(value);
|
|
1258
|
+
const resolved = resolveRefTarget(value, origin);
|
|
1259
|
+
if (origin && resolved === resolveRefTarget(origin)) return base.exec(value);
|
|
1260
|
+
if (!isAllowedRef(value, allowedExternalRefs, origin)) throw new Error(`Refused to fetch external URL: ${value}\nAdd it to externalRefs.allow or use ['*'] to allow all.`);
|
|
1261
|
+
return base.exec(value);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
async function importSpecs(workspace, options, projectName) {
|
|
1266
|
+
const { input, output } = options;
|
|
1267
|
+
return importOpenApi({
|
|
1268
|
+
spec: await resolveSpec(input.target, {
|
|
1269
|
+
parserOptions: input.parserOptions,
|
|
1270
|
+
transformer: input.override.transformer,
|
|
1271
|
+
workspace,
|
|
1272
|
+
unsafeDisableValidation: input.unsafeDisableValidation
|
|
1273
|
+
}),
|
|
1274
|
+
input,
|
|
1275
|
+
output,
|
|
1276
|
+
target: isString(input.target) ? input.target : workspace,
|
|
1277
|
+
workspace,
|
|
1278
|
+
projectName
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
const COMPONENT_KEY_PATTERN = /^[a-zA-Z0-9.\-_]+$/;
|
|
1282
|
+
const COMPONENT_SECTIONS = [
|
|
1283
|
+
"schemas",
|
|
1284
|
+
"responses",
|
|
1285
|
+
"parameters",
|
|
1286
|
+
"examples",
|
|
1287
|
+
"requestBodies",
|
|
1288
|
+
"headers",
|
|
1289
|
+
"securitySchemes",
|
|
1290
|
+
"links",
|
|
1291
|
+
"callbacks",
|
|
1292
|
+
"pathItems"
|
|
1293
|
+
];
|
|
1294
|
+
/**
|
|
1295
|
+
* Validate that all component keys conform to the OAS regex: ^[a-zA-Z0-9.\-_]+$
|
|
1296
|
+
* @see https://spec.openapis.org/oas/v3.0.3.html#fixed-fields-5
|
|
1297
|
+
* @see https://spec.openapis.org/oas/v3.1.0#fixed-fields-5
|
|
1298
|
+
*/
|
|
1299
|
+
function validateComponentKeys(data) {
|
|
1300
|
+
const components = data.components;
|
|
1301
|
+
if (!isObject(components)) return;
|
|
1302
|
+
const invalidKeys = [];
|
|
1303
|
+
for (const section of COMPONENT_SECTIONS) {
|
|
1304
|
+
const sectionObj = components[section];
|
|
1305
|
+
if (!isObject(sectionObj)) continue;
|
|
1306
|
+
for (const key of Object.keys(sectionObj)) if (!COMPONENT_KEY_PATTERN.test(key)) invalidKeys.push(`components.${section}.${key}`);
|
|
1307
|
+
}
|
|
1308
|
+
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"));
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* The plugins from `@scalar/json-magic` does not dereference $ref.
|
|
1312
|
+
* Instead it fetches them and puts them under x-ext, and changes the $ref to point to #x-ext/<name>.
|
|
1313
|
+
* This function:
|
|
1314
|
+
* 1. Merges external schemas into main spec's components.schemas (with collision handling)
|
|
1315
|
+
* 2. Replaces x-ext refs with standard component refs or inlined content
|
|
1316
|
+
*/
|
|
1317
|
+
function dereferenceExternalRef(data) {
|
|
1318
|
+
const extensions = data["x-ext"] ?? {};
|
|
1319
|
+
const schemaNameMappings = mergeExternalSchemas(data, extensions);
|
|
1320
|
+
const result = {};
|
|
1321
|
+
for (const [key, value] of Object.entries(data)) if (key !== "x-ext") result[key] = replaceXExtRefs(value, extensions, schemaNameMappings);
|
|
1322
|
+
return result;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Merge external document schemas into main spec's components.schemas
|
|
1326
|
+
* Returns mapping of original schema names to final names (with suffixes for collisions)
|
|
1327
|
+
*/
|
|
1328
|
+
function mergeExternalSchemas(data, extensions) {
|
|
1329
|
+
const schemaNameMappings = {};
|
|
1330
|
+
if (Object.keys(extensions).length === 0) return schemaNameMappings;
|
|
1331
|
+
data.components ??= {};
|
|
1332
|
+
const mainComponents = data.components;
|
|
1333
|
+
mainComponents.schemas ??= {};
|
|
1334
|
+
const mainSchemas = mainComponents.schemas;
|
|
1335
|
+
for (const [extKey, extDoc] of Object.entries(extensions)) {
|
|
1336
|
+
schemaNameMappings[extKey] = {};
|
|
1337
|
+
if (isObject(extDoc) && "components" in extDoc) {
|
|
1338
|
+
const extComponents = extDoc.components;
|
|
1339
|
+
if (isObject(extComponents) && "schemas" in extComponents) {
|
|
1340
|
+
const extSchemas = extComponents.schemas;
|
|
1341
|
+
for (const [schemaName, schema] of Object.entries(extSchemas)) {
|
|
1342
|
+
const existingSchema = mainSchemas[schemaName];
|
|
1343
|
+
const isXExtRef = isObject(existingSchema) && "$ref" in existingSchema && isString(existingSchema.$ref) && existingSchema.$ref.startsWith("#/x-ext/");
|
|
1344
|
+
let finalSchemaName = schemaName;
|
|
1345
|
+
if (schemaName in mainSchemas && !isXExtRef) {
|
|
1346
|
+
finalSchemaName = `${schemaName}_${extKey.replaceAll(/[^a-zA-Z0-9]/g, "_")}`;
|
|
1347
|
+
schemaNameMappings[extKey][schemaName] = finalSchemaName;
|
|
1348
|
+
} else schemaNameMappings[extKey][schemaName] = schemaName;
|
|
1349
|
+
mainSchemas[finalSchemaName] = scrubUnwantedKeys(schema);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
for (const [extKey, mapping] of Object.entries(schemaNameMappings)) for (const [, finalName] of Object.entries(mapping)) {
|
|
1355
|
+
const schema = mainSchemas[finalName];
|
|
1356
|
+
if (schema) mainSchemas[finalName] = updateInternalRefs(schema, extKey, schemaNameMappings);
|
|
1357
|
+
}
|
|
1358
|
+
return schemaNameMappings;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Remove unwanted keys like $schema and $id from objects
|
|
1362
|
+
*/
|
|
1363
|
+
function scrubUnwantedKeys(obj) {
|
|
1364
|
+
const UNWANTED_KEYS = new Set(["$schema", "$id"]);
|
|
1365
|
+
if (obj === null || obj === void 0) return obj;
|
|
1366
|
+
if (Array.isArray(obj)) return obj.map((x) => scrubUnwantedKeys(x));
|
|
1367
|
+
if (isObject(obj)) {
|
|
1368
|
+
const rec = obj;
|
|
1369
|
+
const out = {};
|
|
1370
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
1371
|
+
if (UNWANTED_KEYS.has(k)) continue;
|
|
1372
|
+
out[k] = scrubUnwantedKeys(v);
|
|
1373
|
+
}
|
|
1374
|
+
return out;
|
|
1375
|
+
}
|
|
1376
|
+
return obj;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Update internal refs within an external schema to use suffixed names
|
|
1380
|
+
*/
|
|
1381
|
+
function updateInternalRefs(obj, extKey, schemaNameMappings) {
|
|
1382
|
+
if (obj === null || obj === void 0) return obj;
|
|
1383
|
+
if (Array.isArray(obj)) return obj.map((element) => updateInternalRefs(element, extKey, schemaNameMappings));
|
|
1384
|
+
if (isObject(obj)) {
|
|
1385
|
+
const record = obj;
|
|
1386
|
+
if ("$ref" in record && isString(record.$ref)) {
|
|
1387
|
+
const refValue = record.$ref;
|
|
1388
|
+
if (refValue.startsWith("#/components/schemas/")) {
|
|
1389
|
+
const schemaName = refValue.replace("#/components/schemas/", "");
|
|
1390
|
+
const mappedName = schemaNameMappings[extKey][schemaName];
|
|
1391
|
+
if (mappedName) return { $ref: `#/components/schemas/${mappedName}` };
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const result = {};
|
|
1395
|
+
for (const [key, value] of Object.entries(record)) result[key] = updateInternalRefs(value, extKey, schemaNameMappings);
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
return obj;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Decode a single JSON Pointer reference token taken from an x-ext `$ref`.
|
|
1402
|
+
*
|
|
1403
|
+
* The token carries two layers of encoding: it sits in a URI fragment, so it
|
|
1404
|
+
* may be percent-encoded (e.g. `%7B` for `{` in templated paths), and it is a
|
|
1405
|
+
* JSON Pointer token, so `~1`/`~0` stand for `/`/`~` (RFC 6901). Percent-
|
|
1406
|
+
* encoding is the outer layer and is removed first; a malformed sequence is
|
|
1407
|
+
* left as-is rather than throwing. Without this, tokens such as `~1pets`
|
|
1408
|
+
* never match the real `/pets` key and the external `$ref` fails to resolve.
|
|
1409
|
+
*/
|
|
1410
|
+
function decodeRefToken(token) {
|
|
1411
|
+
let decoded = token;
|
|
1412
|
+
try {
|
|
1413
|
+
decoded = decodeURIComponent(token);
|
|
1414
|
+
} catch {}
|
|
1415
|
+
return decoded.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Replace x-ext refs with standard component refs, or inline the content.
|
|
1419
|
+
* `inliningRefs` tracks the inline chain to break cycles in recursive
|
|
1420
|
+
* external schemas that aren't under `components.schemas` (#1642).
|
|
1421
|
+
*/
|
|
1422
|
+
function replaceXExtRefs(obj, extensions, schemaNameMappings, inliningRefs = /* @__PURE__ */ new Set()) {
|
|
1423
|
+
if (isNullish$1(obj)) return obj;
|
|
1424
|
+
if (Array.isArray(obj)) return obj.map((element) => replaceXExtRefs(element, extensions, schemaNameMappings, inliningRefs));
|
|
1425
|
+
if (isObject(obj)) {
|
|
1426
|
+
const record = obj;
|
|
1427
|
+
if ("$ref" in record && isString(record.$ref)) {
|
|
1428
|
+
const refValue = record.$ref;
|
|
1429
|
+
if (refValue.startsWith("#/x-ext/")) {
|
|
1430
|
+
const parts = refValue.replace("#/x-ext/", "").split("/");
|
|
1431
|
+
const extKey = parts.shift();
|
|
1432
|
+
if (extKey) {
|
|
1433
|
+
if (parts.length >= 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
1434
|
+
const schemaName = parts.slice(2).join("/");
|
|
1435
|
+
return { $ref: `#/components/schemas/${schemaNameMappings[extKey][schemaName] || schemaName}` };
|
|
1436
|
+
}
|
|
1437
|
+
if (inliningRefs.has(refValue)) {
|
|
1438
|
+
logWarning(`Detected a circular external $ref while inlining "${refValue}". Replacing with an empty schema to avoid infinite recursion. Move the schema under "components.schemas" in its source file or pre-bundle the spec to keep the recursion intact.`);
|
|
1439
|
+
return {};
|
|
1440
|
+
}
|
|
1441
|
+
let refObj = extensions[extKey];
|
|
1442
|
+
for (const rawPart of parts) {
|
|
1443
|
+
const p = decodeRefToken(rawPart);
|
|
1444
|
+
if (refObj && (isObject(refObj) || Array.isArray(refObj)) && p in refObj) refObj = refObj[p];
|
|
1445
|
+
else {
|
|
1446
|
+
refObj = void 0;
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (refObj) {
|
|
1451
|
+
const cleaned = scrubUnwantedKeys(refObj);
|
|
1452
|
+
const nextInlining = new Set(inliningRefs);
|
|
1453
|
+
nextInlining.add(refValue);
|
|
1454
|
+
return replaceXExtRefs(cleaned, extensions, schemaNameMappings, nextInlining);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
const result = {};
|
|
1460
|
+
for (const [key, value] of Object.entries(record)) result[key] = replaceXExtRefs(value, extensions, schemaNameMappings, inliningRefs);
|
|
1461
|
+
return result;
|
|
1462
|
+
}
|
|
1463
|
+
return obj;
|
|
1464
|
+
}
|
|
1465
|
+
//#endregion
|
|
1466
|
+
//#region src/formatters/prettier.ts
|
|
1467
|
+
/**
|
|
1468
|
+
* Format files with prettier.
|
|
1469
|
+
* Tries the programmatic API first (project dependency),
|
|
1470
|
+
* then falls back to the globally installed CLI.
|
|
1471
|
+
*/
|
|
1472
|
+
async function formatWithPrettier(paths, projectTitle) {
|
|
1473
|
+
const prettier = await tryImportPrettier();
|
|
1474
|
+
if (prettier) {
|
|
1475
|
+
const filePaths = [...new Set(await collectFilePaths(paths))];
|
|
1476
|
+
if (filePaths.length === 0) return;
|
|
1477
|
+
const config = await prettier.resolveConfig(filePaths[0]) ?? {};
|
|
1478
|
+
await Promise.all(filePaths.map(async (filePath) => {
|
|
1479
|
+
try {
|
|
1480
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
1481
|
+
const formatted = await prettier.format(content, {
|
|
1482
|
+
...config,
|
|
1483
|
+
filepath: filePath
|
|
1484
|
+
});
|
|
1485
|
+
await fs.writeFile(filePath, formatted);
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
if (isMissingFileError(error)) return;
|
|
1488
|
+
if (error instanceof Error) if (error.name === "UndefinedParserError") {} else logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: ${error.toString()}`);
|
|
1489
|
+
else logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}Failed to format file ${filePath}: unknown error`);
|
|
1490
|
+
}
|
|
1491
|
+
}));
|
|
1492
|
+
return;
|
|
1244
1493
|
}
|
|
1245
|
-
throw new Error(styleText("red", `None of the input targets could be resolved:\n${targets.map((target) => ` - ${target}`).join("\n")}`));
|
|
1246
|
-
}
|
|
1247
|
-
function getHeadersForUrl(url, headersConfig) {
|
|
1248
|
-
if (!headersConfig) return {};
|
|
1249
|
-
const { hostname } = new URL(url);
|
|
1250
|
-
const matchedHeaders = {};
|
|
1251
|
-
for (const headerEntry of headersConfig) if (headerEntry.domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) Object.assign(matchedHeaders, headerEntry.headers);
|
|
1252
|
-
return matchedHeaders;
|
|
1253
|
-
}
|
|
1254
|
-
async function fetchWithTimeout(target, init) {
|
|
1255
|
-
const controller = new AbortController();
|
|
1256
|
-
const timeoutId = setTimeout(() => {
|
|
1257
|
-
controller.abort();
|
|
1258
|
-
}, INPUT_TARGET_FETCH_TIMEOUT_MS);
|
|
1259
1494
|
try {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
});
|
|
1264
|
-
} finally {
|
|
1265
|
-
clearTimeout(timeoutId);
|
|
1495
|
+
await execa("prettier", ["--write", ...paths]);
|
|
1496
|
+
} catch {
|
|
1497
|
+
logWarning(`⚠️ ${projectTitle ? `${projectTitle} - ` : ""}prettier not found. Install it as a project dependency or globally.`);
|
|
1266
1498
|
}
|
|
1267
1499
|
}
|
|
1268
|
-
function
|
|
1269
|
-
|
|
1270
|
-
return path;
|
|
1271
|
-
}
|
|
1272
|
-
function normalizePath(path$1, workspace) {
|
|
1273
|
-
if (!isString(path$1)) return path$1;
|
|
1274
|
-
return path.resolve(workspace, path$1);
|
|
1275
|
-
}
|
|
1276
|
-
function normalizeOperationsAndTags(operationsOrTags, workspace, global, source) {
|
|
1277
|
-
const unsupportedZodKeys = [
|
|
1278
|
-
"version",
|
|
1279
|
-
"variant",
|
|
1280
|
-
"dateTimeOptions",
|
|
1281
|
-
"timeOptions",
|
|
1282
|
-
"generateEachHttpStatus",
|
|
1283
|
-
"generateReusableSchemas",
|
|
1284
|
-
"generateMeta",
|
|
1285
|
-
"generateDiscriminatedUnion"
|
|
1286
|
-
];
|
|
1287
|
-
return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, paramsFilter, query, angular, zod, effect, ...rest }]) => {
|
|
1288
|
-
const unsupportedOperationZodKeys = zod && unsupportedZodKeys.filter((unsupportedKey) => zod[unsupportedKey] !== void 0);
|
|
1289
|
-
if (unsupportedOperationZodKeys && unsupportedOperationZodKeys.length) logWarning(`⚠️ override.${source}.${key}.zod only supports strict, generate, coerce, preprocess, params, and useBrandedTypes. Ignoring unsupported ${unsupportedOperationZodKeys.length === 1 ? "field" : "fields"}: ${unsupportedOperationZodKeys.map((unsupportedKey) => `zod.${unsupportedKey}`).join(", ")}.`);
|
|
1290
|
-
const hasSupportedOperationZodConfig = !!zod && (zod.strict !== void 0 || zod.generate !== void 0 || zod.coerce !== void 0 || zod.preprocess !== void 0 || zod.params !== void 0 || zod.useBrandedTypes !== void 0);
|
|
1291
|
-
return [key, {
|
|
1292
|
-
...rest,
|
|
1293
|
-
...angular ? { angular: {
|
|
1294
|
-
provideIn: angular.provideIn ?? "root",
|
|
1295
|
-
client: angular.retrievalClient ?? angular.client ?? "httpClient",
|
|
1296
|
-
runtimeValidation: angular.runtimeValidation ?? false,
|
|
1297
|
-
queryObjectSerialization: angular.queryObjectSerialization ?? "spec",
|
|
1298
|
-
...angular.httpResource ? { httpResource: angular.httpResource } : {}
|
|
1299
|
-
} } : {},
|
|
1300
|
-
...query ? { query: normalizeQueryOptions(query, workspace, global.query) } : {},
|
|
1301
|
-
...hasSupportedOperationZodConfig && zod ? { zod: {
|
|
1302
|
-
strict: {
|
|
1303
|
-
param: zod.strict?.param ?? false,
|
|
1304
|
-
query: zod.strict?.query ?? false,
|
|
1305
|
-
header: zod.strict?.header ?? false,
|
|
1306
|
-
body: zod.strict?.body ?? false,
|
|
1307
|
-
response: zod.strict?.response ?? false
|
|
1308
|
-
},
|
|
1309
|
-
generate: {
|
|
1310
|
-
param: zod.generate?.param ?? true,
|
|
1311
|
-
query: zod.generate?.query ?? true,
|
|
1312
|
-
header: zod.generate?.header ?? true,
|
|
1313
|
-
body: zod.generate?.body ?? true,
|
|
1314
|
-
response: zod.generate?.response ?? true
|
|
1315
|
-
},
|
|
1316
|
-
coerce: {
|
|
1317
|
-
param: zod.coerce?.param ?? false,
|
|
1318
|
-
query: zod.coerce?.query ?? false,
|
|
1319
|
-
header: zod.coerce?.header ?? false,
|
|
1320
|
-
body: zod.coerce?.body ?? false,
|
|
1321
|
-
response: zod.coerce?.response ?? false
|
|
1322
|
-
},
|
|
1323
|
-
preprocess: {
|
|
1324
|
-
...zod.preprocess?.param ? { param: normalizeMutator(workspace, zod.preprocess.param) } : {},
|
|
1325
|
-
...zod.preprocess?.query ? { query: normalizeMutator(workspace, zod.preprocess.query) } : {},
|
|
1326
|
-
...zod.preprocess?.header ? { header: normalizeMutator(workspace, zod.preprocess.header) } : {},
|
|
1327
|
-
...zod.preprocess?.body ? { body: normalizeMutator(workspace, zod.preprocess.body) } : {},
|
|
1328
|
-
...zod.preprocess?.response ? { response: normalizeMutator(workspace, zod.preprocess.response) } : {}
|
|
1329
|
-
},
|
|
1330
|
-
...zod.params ? { params: normalizeMutator(workspace, zod.params) } : {},
|
|
1331
|
-
useBrandedTypes: zod.useBrandedTypes ?? false
|
|
1332
|
-
} } : {},
|
|
1333
|
-
...effect ? { effect: normalizeEffectOptions(effect) } : {},
|
|
1334
|
-
...transformer ? { transformer: normalizePath(transformer, workspace) } : {},
|
|
1335
|
-
...mutator ? { mutator: normalizeMutator(workspace, mutator) } : {},
|
|
1336
|
-
...formData === void 0 ? {} : { formData: createFormData(workspace, formData) },
|
|
1337
|
-
...formUrlEncoded ? { formUrlEncoded: isBoolean(formUrlEncoded) ? formUrlEncoded : normalizeMutator(workspace, formUrlEncoded) } : {},
|
|
1338
|
-
...paramsSerializer ? { paramsSerializer: normalizeMutator(workspace, paramsSerializer) } : {},
|
|
1339
|
-
...paramsFilter ? { paramsFilter: normalizeMutator(workspace, paramsFilter) } : {}
|
|
1340
|
-
}];
|
|
1341
|
-
}));
|
|
1500
|
+
function isMissingFileError(error) {
|
|
1501
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1342
1502
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1503
|
+
/**
|
|
1504
|
+
* Try to import prettier from the project's dependencies.
|
|
1505
|
+
* Returns undefined if prettier is not installed.
|
|
1506
|
+
*/
|
|
1507
|
+
async function tryImportPrettier() {
|
|
1508
|
+
try {
|
|
1509
|
+
return await import("prettier");
|
|
1510
|
+
} catch {
|
|
1511
|
+
return;
|
|
1348
1512
|
}
|
|
1349
|
-
return mode;
|
|
1350
|
-
}
|
|
1351
|
-
function normalizeHooks(hooks) {
|
|
1352
|
-
const keys = Object.keys(hooks);
|
|
1353
|
-
const result = {};
|
|
1354
|
-
for (const key of keys) if (isString(hooks[key])) result[key] = [hooks[key]];
|
|
1355
|
-
else if (Array.isArray(hooks[key])) result[key] = hooks[key];
|
|
1356
|
-
else if (isFunction(hooks[key])) result[key] = [hooks[key]];
|
|
1357
|
-
else if (isObject(hooks[key])) result[key] = [hooks[key]];
|
|
1358
|
-
return result;
|
|
1359
|
-
}
|
|
1360
|
-
function normalizeHonoOptions(hono = {}, workspace) {
|
|
1361
|
-
return {
|
|
1362
|
-
...hono.handlers ? { handlers: path.resolve(workspace, hono.handlers) } : {},
|
|
1363
|
-
handlerGenerationStrategy: hono.handlerGenerationStrategy ?? "smart",
|
|
1364
|
-
compositeRoute: hono.compositeRoute ? path.resolve(workspace, hono.compositeRoute) : "",
|
|
1365
|
-
validator: hono.validator ?? true,
|
|
1366
|
-
validatorOutputPath: hono.validatorOutputPath ? path.resolve(workspace, hono.validatorOutputPath) : ""
|
|
1367
|
-
};
|
|
1368
1513
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1514
|
+
/**
|
|
1515
|
+
* Recursively collect absolute file paths from a mix of files and directories.
|
|
1516
|
+
*/
|
|
1517
|
+
async function collectFilePaths(paths) {
|
|
1518
|
+
const results = [];
|
|
1519
|
+
for (const p of paths) {
|
|
1520
|
+
const absolute = path.resolve(p);
|
|
1521
|
+
try {
|
|
1522
|
+
const stat = await fs.stat(absolute);
|
|
1523
|
+
if (stat.isFile()) results.push(absolute);
|
|
1524
|
+
else if (stat.isDirectory()) {
|
|
1525
|
+
const subFiles = await collectFilePaths((await fs.readdir(absolute)).map((entry) => path.join(absolute, entry)));
|
|
1526
|
+
results.push(...subFiles);
|
|
1527
|
+
}
|
|
1528
|
+
} catch {}
|
|
1529
|
+
}
|
|
1530
|
+
return results;
|
|
1375
1531
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1532
|
+
//#endregion
|
|
1533
|
+
//#region src/utils/barrel.ts
|
|
1534
|
+
const RE_EXPORT_SPECIFIER = /export\s+\*\s+from\s*['"]([^'"]+)['"]/g;
|
|
1535
|
+
/**
|
|
1536
|
+
* Extract the set of `export * from '...'` module specifiers from barrel
|
|
1537
|
+
* content. Quote-agnostic (matches both `'` and `"`) so it is unaffected by a
|
|
1538
|
+
* formatter changing quote style between generations.
|
|
1539
|
+
*/
|
|
1540
|
+
function readReExportSpecifiers(content) {
|
|
1541
|
+
return new Set([...content.matchAll(RE_EXPORT_SPECIFIER)].map((m) => m[1]));
|
|
1378
1542
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1543
|
+
/**
|
|
1544
|
+
* Return the deduplicated list of specifiers for a barrel by unioning the
|
|
1545
|
+
* freshly generated `specifiers` with whatever re-exports already exist on
|
|
1546
|
+
* disk. Merging on the bare specifier (rather than the formatted line) makes
|
|
1547
|
+
* the barrel idempotent across regenerations regardless of how an external
|
|
1548
|
+
* formatter rewrites quotes, semicolons, or whitespace. On-disk order is
|
|
1549
|
+
* preserved and new specifiers are appended — matching the previous append
|
|
1550
|
+
* ordering — so callers control any sorting they want.
|
|
1551
|
+
*/
|
|
1552
|
+
async function mergeBarrelSpecifiers(filePath, specifiers) {
|
|
1553
|
+
const existing = await fs$2.pathExists(filePath) ? readReExportSpecifiers(await fs$2.readFile(filePath, "utf8")) : /* @__PURE__ */ new Set();
|
|
1554
|
+
return [...new Set([...existing, ...specifiers])];
|
|
1381
1555
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
...queryOptions.queryKey ? { queryKey: normalizeMutator(outputWorkspace, queryOptions.queryKey) } : {},
|
|
1398
|
-
...globalOptions.queryOptions ? { queryOptions: globalOptions.queryOptions } : {},
|
|
1399
|
-
...queryOptions.queryOptions ? { queryOptions: normalizeMutator(outputWorkspace, queryOptions.queryOptions) } : {},
|
|
1400
|
-
...globalOptions.mutationOptions ? { mutationOptions: globalOptions.mutationOptions } : {},
|
|
1401
|
-
...queryOptions.mutationOptions ? { mutationOptions: normalizeMutator(outputWorkspace, queryOptions.mutationOptions) } : {},
|
|
1402
|
-
...isNullish(globalOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: globalOptions.shouldExportQueryKey },
|
|
1403
|
-
...isNullish(queryOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: queryOptions.shouldExportQueryKey },
|
|
1404
|
-
...isNullish(globalOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: globalOptions.shouldFilterQueryKey },
|
|
1405
|
-
...isNullish(queryOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: queryOptions.shouldFilterQueryKey },
|
|
1406
|
-
...isNullish(globalOptions.queryKeyFilter) ? {} : { queryKeyFilter: globalOptions.queryKeyFilter },
|
|
1407
|
-
...isNullish(queryOptions.queryKeyFilter) ? {} : { queryKeyFilter: queryOptions.queryKeyFilter },
|
|
1408
|
-
...isNullish(globalOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: globalOptions.shouldExportHttpClient },
|
|
1409
|
-
...isNullish(queryOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: queryOptions.shouldExportHttpClient },
|
|
1410
|
-
...isNullish(globalOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: globalOptions.shouldExportMutatorHooks },
|
|
1411
|
-
...isNullish(queryOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: queryOptions.shouldExportMutatorHooks },
|
|
1412
|
-
...isNullish(globalOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: globalOptions.shouldSplitQueryKey },
|
|
1413
|
-
...isNullish(queryOptions.shouldSplitQueryKey) ? {} : { shouldSplitQueryKey: queryOptions.shouldSplitQueryKey },
|
|
1414
|
-
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
|
|
1415
|
-
...isNullish(globalOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: globalOptions.useOperationIdAsQueryKey },
|
|
1416
|
-
...isNullish(queryOptions.useOperationIdAsQueryKey) ? {} : { useOperationIdAsQueryKey: queryOptions.useOperationIdAsQueryKey },
|
|
1417
|
-
...isNullish(globalOptions.signal) ? {} : { signal: globalOptions.signal },
|
|
1418
|
-
...isNullish(queryOptions.signal) ? {} : { signal: queryOptions.signal },
|
|
1419
|
-
...isNullish(globalOptions.version) ? {} : { version: globalOptions.version },
|
|
1420
|
-
...isNullish(queryOptions.version) ? {} : { version: queryOptions.version },
|
|
1421
|
-
...queryOptions.mutationInvalidates ? { mutationInvalidates: queryOptions.mutationInvalidates } : {},
|
|
1422
|
-
...isNullish(globalOptions.runtimeValidation) ? {} : { runtimeValidation: globalOptions.runtimeValidation },
|
|
1423
|
-
...isNullish(queryOptions.runtimeValidation) ? {} : { runtimeValidation: queryOptions.runtimeValidation }
|
|
1424
|
-
};
|
|
1556
|
+
//#endregion
|
|
1557
|
+
//#region src/utils/execute-hook.ts
|
|
1558
|
+
const executeHook = async (name, commands = [], args = []) => {
|
|
1559
|
+
log(styleText("white", `Running ${name} hook...`));
|
|
1560
|
+
for (const command of commands) try {
|
|
1561
|
+
if (isString(command)) await executeCommand(command, args);
|
|
1562
|
+
else if (isFunction(command)) await command(args);
|
|
1563
|
+
else if (isObject(command)) await executeObjectCommand(command, args);
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
logError(error, `Failed to run ${name} hook`);
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
async function executeCommand(command, args) {
|
|
1569
|
+
const [cmd, ..._args] = [...parseArgsStringToArgv(command), ...args];
|
|
1570
|
+
await execa(cmd, _args);
|
|
1425
1571
|
}
|
|
1426
|
-
function
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
...title ? [title] : [],
|
|
1431
|
-
...description ? [description] : [],
|
|
1432
|
-
...version$1 ? [`OpenAPI spec version: ${version$1}`] : []
|
|
1433
|
-
];
|
|
1572
|
+
async function executeObjectCommand(command, args) {
|
|
1573
|
+
if (command.injectGeneratedDirsAndFiles === false) args = [];
|
|
1574
|
+
if (isString(command.command)) await executeCommand(command.command, args);
|
|
1575
|
+
else if (isFunction(command.command)) await command.command();
|
|
1434
1576
|
}
|
|
1435
1577
|
//#endregion
|
|
1436
1578
|
//#region src/utils/watcher.ts
|
|
@@ -1535,7 +1677,7 @@ const generateReusableSchemaSet = (refs, context, options) => {
|
|
|
1535
1677
|
operationId: "",
|
|
1536
1678
|
location: "schema",
|
|
1537
1679
|
schemaName: name
|
|
1538
|
-
} : void 0, options.variant);
|
|
1680
|
+
} : void 0, options.variant, options.exactOptional);
|
|
1539
1681
|
entries.push({
|
|
1540
1682
|
ref,
|
|
1541
1683
|
name,
|
|
@@ -1800,19 +1942,11 @@ const groupSchemasByFilePath = (schemas) => {
|
|
|
1800
1942
|
async function writeZodSchemaIndex(schemasPath, fileExtension, header, schemaNames, namingConvention, shouldMergeExisting = false, tsconfig) {
|
|
1801
1943
|
const importFileExtension = getImportExtension(fileExtension, tsconfig);
|
|
1802
1944
|
const indexPath = path.join(schemasPath, `index.ts`);
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
existingExports = existingContent.slice(headerPart.length).trim();
|
|
1809
|
-
}
|
|
1810
|
-
const newExports = schemaNames.map((schemaName) => {
|
|
1811
|
-
return `export * from './${conventionName(schemaName, namingConvention)}${importFileExtension}';`;
|
|
1812
|
-
}).toSorted().join("\n");
|
|
1813
|
-
const allExports = existingExports ? `${existingExports}\n${newExports}` : newExports;
|
|
1814
|
-
const uniqueExports = [...new Set(allExports.split("\n"))].filter((line) => line.trim()).toSorted().join("\n");
|
|
1815
|
-
await fs.outputFile(indexPath, `${header}\n${uniqueExports}\n`);
|
|
1945
|
+
const newSpecifiers = [...new Set(schemaNames.map((schemaName) => {
|
|
1946
|
+
return `./${conventionName(schemaName, namingConvention)}${importFileExtension}`;
|
|
1947
|
+
}))];
|
|
1948
|
+
const exports = (shouldMergeExisting ? await mergeBarrelSpecifiers(indexPath, newSpecifiers) : newSpecifiers).toSorted().map((specifier) => `export * from '${specifier}';`).join("\n");
|
|
1949
|
+
await fs$2.outputFile(indexPath, `${header}\n${exports}\n`);
|
|
1816
1950
|
}
|
|
1817
1951
|
async function writeZodSchemaTagsSplitBarrel(schemasPath, fileExtension, header, componentDirs, verbDirs, namingConvention, tsconfig) {
|
|
1818
1952
|
const importExt = getImportExtension(fileExtension, tsconfig);
|
|
@@ -1835,7 +1969,7 @@ async function writeZodSchemaTagsSplitBarrel(schemasPath, fileExtension, header,
|
|
|
1835
1969
|
const allExports = [...rootExports, ...tagExports];
|
|
1836
1970
|
const rootIndexPath = path.join(schemasPath, "index.ts");
|
|
1837
1971
|
const content = `${header}\n${allExports.join("\n")}\n`;
|
|
1838
|
-
await fs.outputFile(rootIndexPath, content);
|
|
1972
|
+
await fs$2.outputFile(rootIndexPath, content);
|
|
1839
1973
|
}
|
|
1840
1974
|
function generateZodSchemasInline(builder, output, includeZodImport = true, paramsMutator, includeParamsImport = false) {
|
|
1841
1975
|
if (output.override.zod.generateReusableSchemas === true) return generateZodSchemasInlineReusable(builder, output, includeZodImport, paramsMutator, includeParamsImport);
|
|
@@ -1860,7 +1994,7 @@ function generateZodSchemasInline(builder, output, includeZodImport = true, para
|
|
|
1860
1994
|
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1861
1995
|
required: true,
|
|
1862
1996
|
emitMeta: output.override.zod.generateMeta
|
|
1863
|
-
}), context, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant);
|
|
1997
|
+
}), context, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant, output.override.zod.exactOptional);
|
|
1864
1998
|
schemas.push({
|
|
1865
1999
|
schemaName: name,
|
|
1866
2000
|
consts: parsedZodDefinition.consts,
|
|
@@ -1894,6 +2028,7 @@ function generateZodSchemasInlineReusable(builder, output, includeZodImport = tr
|
|
|
1894
2028
|
coerce,
|
|
1895
2029
|
variant: output.override.zod.variant,
|
|
1896
2030
|
generateMeta: output.override.zod.generateMeta,
|
|
2031
|
+
exactOptional: output.override.zod.exactOptional,
|
|
1897
2032
|
paramsMutator
|
|
1898
2033
|
})).map((entry) => renderReusableSchemaEntry(entry, context, output.override.zod.variant).content).join("\n\n");
|
|
1899
2034
|
const zodImport = includeZodImport ? `${getZodSchemaImportStatement(output.override.zod.variant)}\n` : "";
|
|
@@ -1927,7 +2062,7 @@ async function writeZodSchemas(builder, schemasPath, fileExtension, header, outp
|
|
|
1927
2062
|
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1928
2063
|
required: true,
|
|
1929
2064
|
emitMeta: output.override.zod.generateMeta
|
|
1930
|
-
}), context, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant);
|
|
2065
|
+
}), context, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant, output.override.zod.exactOptional);
|
|
1931
2066
|
schemasToWrite.push({
|
|
1932
2067
|
schemaName: name,
|
|
1933
2068
|
filePath,
|
|
@@ -1938,7 +2073,7 @@ async function writeZodSchemas(builder, schemasPath, fileExtension, header, outp
|
|
|
1938
2073
|
const groupedSchemasToWrite = groupSchemasByFilePath(schemasToWrite);
|
|
1939
2074
|
for (const schemaGroup of groupedSchemasToWrite) {
|
|
1940
2075
|
const fileContent = generateZodSchemaFileContent(header, schemaGroup, output.override.zod.variant);
|
|
1941
|
-
await fs.outputFile(schemaGroup[0].filePath, fileContent);
|
|
2076
|
+
await fs$2.outputFile(schemaGroup[0].filePath, fileContent);
|
|
1942
2077
|
}
|
|
1943
2078
|
const writtenSchemaNames = groupedSchemasToWrite.map((schemaGroup) => schemaGroup[0].schemaName);
|
|
1944
2079
|
if (output.indexFiles && !isSplit) await writeZodSchemaIndex(schemasPath, fileExtension, header, writtenSchemaNames, output.namingConvention, false, output.tsconfig);
|
|
@@ -1977,6 +2112,7 @@ async function writeZodSchemasReusable(builder, schemasPath, fileExtension, head
|
|
|
1977
2112
|
coerce,
|
|
1978
2113
|
variant: output.override.zod.variant,
|
|
1979
2114
|
generateMeta: output.override.zod.generateMeta,
|
|
2115
|
+
exactOptional: output.override.zod.exactOptional,
|
|
1980
2116
|
paramsMutator
|
|
1981
2117
|
}));
|
|
1982
2118
|
const componentNames = new Set(Object.keys(builder.spec.components?.schemas ?? {}).map((schemaName) => resolveSchemaName(`#/components/schemas/${schemaName}`, context)));
|
|
@@ -2005,7 +2141,7 @@ async function writeZodSchemasReusable(builder, schemasPath, fileExtension, head
|
|
|
2005
2141
|
}) : void 0;
|
|
2006
2142
|
const imports = [...mutatorImportStr ? [mutatorImportStr] : [], ...refImports ? [refImports] : []].join("\n");
|
|
2007
2143
|
const fileContent = `${header}${getZodSchemaImportStatement(output.override.zod.variant)}\n` + (imports ? `${imports}\n\n` : "\n") + `${rendered.content}\n`;
|
|
2008
|
-
await fs.outputFile(filePath, fileContent);
|
|
2144
|
+
await fs$2.outputFile(filePath, fileContent);
|
|
2009
2145
|
}
|
|
2010
2146
|
if (output.indexFiles && !isSplit && rewritten.length > 0) await writeZodSchemaIndex(schemasPath, fileExtension, header, rewritten.map((e) => e.name), output.namingConvention, true, output.tsconfig);
|
|
2011
2147
|
if (isSplit) {
|
|
@@ -2105,7 +2241,7 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
2105
2241
|
const parsedZodDefinition = parseZodValidationSchemaDefinition("bodyContentType" in entry && entry.bodyContentType === "multipart/form-data" ? generateFormDataZodSchema(schema, zodContext, name, strict, isZodV4, "encoding" in entry ? entry.encoding : void 0, useReusableSchemas) : generateZodValidationSchemaDefinition(schema, zodContext, name, strict, isZodV4, {
|
|
2106
2242
|
required: true,
|
|
2107
2243
|
useReusableSchemas
|
|
2108
|
-
}), zodContext, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant);
|
|
2244
|
+
}), zodContext, coerce, strict, isZodV4, void 0, void 0, output.override.zod.variant, output.override.zod.exactOptional);
|
|
2109
2245
|
let zodExpression = parsedZodDefinition.zod;
|
|
2110
2246
|
let importStatements;
|
|
2111
2247
|
if (useReusableSchemas && parsedZodDefinition.usedRefs.size > 0) {
|
|
@@ -2127,7 +2263,7 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
2127
2263
|
const groupedSchemasToWrite = groupSchemasByFilePath(schemasToWrite);
|
|
2128
2264
|
for (const schemaGroup of groupedSchemasToWrite) {
|
|
2129
2265
|
const fileContent = generateZodSchemaFileContent(header, schemaGroup, output.override.zod.variant);
|
|
2130
|
-
await fs.outputFile(schemaGroup[0].filePath, fileContent);
|
|
2266
|
+
await fs$2.outputFile(schemaGroup[0].filePath, fileContent);
|
|
2131
2267
|
}
|
|
2132
2268
|
const writtenSchemaNames = groupedSchemasToWrite.map((schemaGroup) => schemaGroup[0].schemaName);
|
|
2133
2269
|
if (output.indexFiles && !isSplit && uniqueVerbsSchemas.length > 0) await writeZodSchemaIndex(schemasPath, fileExtension, header, writtenSchemaNames, output.namingConvention, true, output.tsconfig);
|
|
@@ -2177,7 +2313,7 @@ function getComparableFilePath(filePath) {
|
|
|
2177
2313
|
const resolvedPath = path.resolve(filePath);
|
|
2178
2314
|
let comparablePath = resolvedPath;
|
|
2179
2315
|
try {
|
|
2180
|
-
comparablePath = fs.realpathSync(resolvedPath);
|
|
2316
|
+
comparablePath = fs$2.realpathSync(resolvedPath);
|
|
2181
2317
|
} catch (error) {
|
|
2182
2318
|
if (error.code !== "ENOENT") throw error;
|
|
2183
2319
|
}
|
|
@@ -2207,12 +2343,11 @@ async function addOperationSchemasReExport(schemaPath, operationSchemasPath, hea
|
|
|
2207
2343
|
const schemaIndexPath = path.join(schemaPath, `index.ts`);
|
|
2208
2344
|
const esmImportPath = upath.getRelativeImportPath(schemaIndexPath, operationSchemasPath);
|
|
2209
2345
|
const exportLine = `export * from '${esmImportPath}';\n`;
|
|
2210
|
-
if (await fs.pathExists(schemaIndexPath)) {
|
|
2211
|
-
|
|
2212
|
-
if (!new RegExp(String.raw`export\s*\*\s*from\s*['"]${esmImportPath.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)}['"]`).test(existingContent)) await fs.appendFile(schemaIndexPath, exportLine);
|
|
2346
|
+
if (await fs$2.pathExists(schemaIndexPath)) {
|
|
2347
|
+
if (!readReExportSpecifiers(await fs$2.readFile(schemaIndexPath, "utf8")).has(esmImportPath)) await fs$2.appendFile(schemaIndexPath, exportLine);
|
|
2213
2348
|
} else {
|
|
2214
2349
|
const content = header && header.trim().length > 0 ? `${header}\n${exportLine}` : exportLine;
|
|
2215
|
-
await fs.outputFile(schemaIndexPath, content);
|
|
2350
|
+
await fs$2.outputFile(schemaIndexPath, content);
|
|
2216
2351
|
}
|
|
2217
2352
|
}
|
|
2218
2353
|
/**
|
|
@@ -2316,9 +2451,15 @@ function shouldGenerateSchemas(output, hasOperations) {
|
|
|
2316
2451
|
function getImplementationPathsForIndex(output, implementationPaths, indexFile) {
|
|
2317
2452
|
const shouldExcludeSelf = output.indexFiles;
|
|
2318
2453
|
const paths = shouldExcludeSelf ? excludeFilePath(implementationPaths, indexFile) : implementationPaths;
|
|
2319
|
-
if (
|
|
2454
|
+
if (shouldExcludeSelf && output.mode === OutputMode.SPLIT && getComparableFilePath(output.target) === getComparableFilePath(indexFile)) {
|
|
2455
|
+
const targetInfo = getFileInfo(output.target, { extension: output.fileExtension });
|
|
2456
|
+
return excludeFilePath(paths, path.join(targetInfo.dirname, `${targetInfo.filename}.schemas${output.fileExtension}`));
|
|
2457
|
+
}
|
|
2458
|
+
if (!(output.mode === OutputMode.TAGS_OPERATIONS || output.mode === OutputMode.TAGS_OPERATIONS_SPLIT) || !shouldExcludeSelf) return paths;
|
|
2320
2459
|
const targetInfo = getFileInfo(output.target, { extension: output.fileExtension });
|
|
2321
|
-
|
|
2460
|
+
const rootBarrel = path.join(targetInfo.dirname, `index${output.fileExtension}`);
|
|
2461
|
+
const globalSchemas = path.join(targetInfo.dirname, `${targetInfo.filename}.schemas${output.fileExtension}`);
|
|
2462
|
+
return paths.filter((p) => getComparableFilePath(p) === getComparableFilePath(rootBarrel) || getComparableFilePath(p) === getComparableFilePath(globalSchemas));
|
|
2322
2463
|
}
|
|
2323
2464
|
async function writeSpecs(builder, workspace, options, projectName) {
|
|
2324
2465
|
const { info, schemas, target } = builder;
|
|
@@ -2454,8 +2595,7 @@ async function writeSpecs(builder, workspace, options, projectName) {
|
|
|
2454
2595
|
const indexFile = path.join(workspacePath, "index.ts");
|
|
2455
2596
|
const mockExtensions = output.mock.generators.map((g) => getMockFileExtensionByTypeName(g));
|
|
2456
2597
|
const importExtension = getImportExtension(output.fileExtension, output.tsconfig);
|
|
2457
|
-
const
|
|
2458
|
-
const imports = implementationPathsForIndex.filter((p) => mockExtensions.length === 0 || !mockExtensions.some((ext) => p.endsWith(`.${ext}.ts`))).map((p) => {
|
|
2598
|
+
const imports = getImplementationPathsForIndex(output, implementationPaths, indexFile).filter((p) => mockExtensions.length === 0 || !mockExtensions.some((ext) => p.endsWith(`.${ext}.ts`))).map((p) => {
|
|
2459
2599
|
const relative = upath.getRelativeImportPath(indexFile, p, true);
|
|
2460
2600
|
return (relative.endsWith(output.fileExtension) ? relative.slice(0, -output.fileExtension.length) : relative.replace(/\.[^/.]+$/, "")) + importExtension;
|
|
2461
2601
|
});
|
|
@@ -2465,16 +2605,16 @@ async function writeSpecs(builder, workspace, options, projectName) {
|
|
|
2465
2605
|
}
|
|
2466
2606
|
if (output.operationSchemas) imports.push(upath.getRelativeImportPath(indexFile, getFileInfo(output.operationSchemas).dirname));
|
|
2467
2607
|
if (output.indexFiles) {
|
|
2468
|
-
if (await fs.pathExists(indexFile)) {
|
|
2469
|
-
const
|
|
2470
|
-
const
|
|
2471
|
-
await fs.appendFile(indexFile,
|
|
2472
|
-
} else await fs.outputFile(indexFile,
|
|
2473
|
-
implementationPaths = [indexFile, ...
|
|
2608
|
+
if (await fs$2.pathExists(indexFile)) {
|
|
2609
|
+
const declared = readReExportSpecifiers(await fs$2.readFile(indexFile, "utf8"));
|
|
2610
|
+
const toAdd = [...new Set(imports.filter((imp) => !declared.has(imp)))];
|
|
2611
|
+
if (toAdd.length > 0) await fs$2.appendFile(indexFile, toAdd.map((imp) => `export * from '${imp}';\n`).join(""));
|
|
2612
|
+
} else await fs$2.outputFile(indexFile, `${[...new Set(imports)].map((imp) => `export * from '${imp}';`).join("\n")}\n`);
|
|
2613
|
+
implementationPaths = [indexFile, ...excludeFilePath(implementationPaths, indexFile)];
|
|
2474
2614
|
}
|
|
2475
2615
|
}
|
|
2476
2616
|
if (builder.extraFiles.length > 0) {
|
|
2477
|
-
await Promise.all(builder.extraFiles.map(async (file) => fs.outputFile(file.path, file.content)));
|
|
2617
|
+
await Promise.all(builder.extraFiles.map(async (file) => fs$2.outputFile(file.path, file.content)));
|
|
2478
2618
|
implementationPaths = [...implementationPaths, ...builder.extraFiles.map((file) => file.path)];
|
|
2479
2619
|
}
|
|
2480
2620
|
const paths = [
|
|
@@ -2521,6 +2661,8 @@ function getWriteMode(mode) {
|
|
|
2521
2661
|
case OutputMode.SPLIT: return writeSplitMode;
|
|
2522
2662
|
case OutputMode.TAGS: return writeTagsMode;
|
|
2523
2663
|
case OutputMode.TAGS_SPLIT: return writeSplitTagsMode;
|
|
2664
|
+
case OutputMode.TAGS_OPERATIONS: return writeTagsOperationsMode;
|
|
2665
|
+
case OutputMode.TAGS_OPERATIONS_SPLIT: return writeTagsOperationsSplitMode;
|
|
2524
2666
|
default: return writeSingleMode;
|
|
2525
2667
|
}
|
|
2526
2668
|
}
|
|
@@ -2577,7 +2719,7 @@ async function generateSpec(workspace, options, projectName) {
|
|
|
2577
2719
|
function findConfigFile(configFilePath) {
|
|
2578
2720
|
if (configFilePath) {
|
|
2579
2721
|
const absolutePath = path.isAbsolute(configFilePath) ? configFilePath : path.resolve(process.cwd(), configFilePath);
|
|
2580
|
-
if (!fs$
|
|
2722
|
+
if (!fs$1.existsSync(absolutePath)) throw new Error(`Config file ${configFilePath} does not exist`);
|
|
2581
2723
|
return absolutePath;
|
|
2582
2724
|
}
|
|
2583
2725
|
const root = process.cwd();
|
|
@@ -2588,7 +2730,7 @@ function findConfigFile(configFilePath) {
|
|
|
2588
2730
|
".mts"
|
|
2589
2731
|
]) {
|
|
2590
2732
|
const fullPath = path.resolve(root, `orval.config${ext}`);
|
|
2591
|
-
if (fs$
|
|
2733
|
+
if (fs$1.existsSync(fullPath)) return fullPath;
|
|
2592
2734
|
}
|
|
2593
2735
|
throw new Error(`No config file found in ${root}`);
|
|
2594
2736
|
}
|
|
@@ -2610,4 +2752,4 @@ async function loadConfigFile(configFilePath) {
|
|
|
2610
2752
|
//#endregion
|
|
2611
2753
|
export { defineConfig as a, description as c, startWatcher as i, name as l, loadConfigFile as n, defineTransformer as o, generateSpec as r, normalizeOptions as s, findConfigFile as t, version as u };
|
|
2612
2754
|
|
|
2613
|
-
//# sourceMappingURL=config-
|
|
2755
|
+
//# sourceMappingURL=config-18E5SVPo.mjs.map
|