openapi-axios
Version:
OpenAPI(2.0/3.0/3.1) Schema → Type-safe Axios
1 lines • 145 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/const.ts","../src/types/openapi.ts","../src/utils/path.ts","../src/utils/string.ts","../src/utils/type-is.ts","../src/printer/const.ts","../src/printer/helpers.ts","../src/printer/JsDoc.ts","../src/printer/Parser.ts","../src/printer/VarPath.ts","../src/printer/Arg.ts","../src/printer/Args.ts","../src/printer/Content.ts","../src/printer/Named.ts","../src/printer/index.ts","../src/utils/object.ts","../src/migrations/openapi-2_0.ts","../src/migrations/openapi-3_0.ts","../src/migrations/index.ts","../src/generator/Reader.ts","../src/generator/Generator.ts","../src/generator/Logger.ts","../src/command.ts","../src/cli.ts"],"sourcesContent":["export const pkgName = PKG_NAME;\nexport const pkgVersion = PKG_VERSION;\nexport const configFileBaseName = 'openapi-axios.config';\n","export type * as OpenAPIV2 from './openapi-2_0';\nexport type * as OpenAPIV3 from './openapi-3_0';\nexport type * as OpenAPIV3_1 from './openapi-3_1';\nexport type * as OpenAPILatest from './openapi-3_1';\nexport type * as OpenAPIAll from './openapi-all';\n\nexport enum OpenAPIVersion {\n /**\n * OpenAPI 2.0.0\n * {@link} https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md\n */\n V2_0 = '2.0.0',\n\n /**\n * OpenAPI 3.0.3\n * {@link} https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md\n */\n V3_0 = '3.0.3',\n\n /**\n * OpenAPI 3.1.0\n * {@link} https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md\n */\n V3_1 = '3.1.0',\n}\n","import path from 'node:path';\n\nexport function isRelative(toFile: string) {\n return /^\\.{1,2}[/\\\\]/.test(toFile);\n}\n\nexport function toRelative(toFile: string, fromFile?: string) {\n if (!fromFile)\n return toFile;\n\n if (!path.isAbsolute(toFile))\n return toFile;\n\n const relative = path.relative(path.dirname(fromFile), toFile);\n return /^\\.{1,2}\\//.test(relative) ? relative : `./${relative}`;\n}\n\nexport function toImportPath(toFile: string, cwd: string, fromFile?: string) {\n // /a/b/c, /cwd\n if (path.isAbsolute(toFile)) {\n return fromFile ? toRelative(toFile, fromFile) : toFile;\n }\n\n // ./a/b/c, /cwd\n if (isRelative(toFile)) {\n return toImportPath(path.join(cwd, toFile), cwd, fromFile);\n }\n\n // a/b/c, /cwd\n return toFile;\n}\n","import type { Options } from 'prettier';\nimport path from 'node:path';\nimport process from 'node:process';\n\nexport function fixVarName(origin: string, bigger = false, fallback = 'var') {\n const name\n = origin\n .replace(/\\W/g, '_')\n .replace(/(^_+|_+$)/g, '')\n .replace(/_(.)/g, ($0, $1: string) => $1.toUpperCase())\n .replace(/^\\d+/, '') || fallback;\n\n return (bigger ? name[0].toUpperCase() : name[0].toLowerCase()) + name.slice(1);\n}\n\nexport function nextUniqueName(refName: string, nameCountMap: Map<string, number>) {\n // abc_123 -> abc\n const baseName = refName.replace(/_[1-9]\\d*$/, '');\n const count = nameCountMap.get(baseName) || 0;\n const nextCount = count + 1;\n nameCountMap.set(baseName, nextCount);\n return nextCount === 1 ? baseName : `${baseName}_${nextCount}`;\n}\n\nexport function refToType(ref: string): string {\n const segs = ref.split('/').slice(3);\n const props = segs.slice(1);\n return segs[0]! + props.map(prop => `[${JSON.stringify(prop)}]`).join('');\n}\n\nexport async function formatTsCode(tsCode: string, userOptions?: Options, cwd = process.cwd()) {\n try {\n const prettier = await import('prettier');\n // 这里末尾加上 1 是因为 prettier 内部会有容错查找\n const cwdOptions = (await prettier.resolveConfig(cwd)) || (await prettier.resolveConfig(path.join(cwd, '1')));\n return prettier.format(tsCode, {\n ...cwdOptions,\n ...userOptions,\n parser: 'typescript',\n });\n }\n catch (cause) {\n return tsCode;\n }\n}\n","export function isString(any: unknown): any is string {\n return typeof any === 'string';\n}\n\nexport function isBoolean(any: unknown): any is boolean {\n return typeof any === 'boolean';\n}\n\nexport function isNumber(any: unknown): any is number {\n return typeof any === 'number';\n}\n\nexport function isArray(any: unknown): any is unknown[] {\n return Array.isArray(any);\n}\n\nexport function isVarName(varName: string) {\n return /^[a-z_$]\\w*$/i.test(varName);\n}\n\nexport function isUndefined(any: unknown): any is undefined {\n return any === undefined;\n}\n\nexport function isNever(never: never) {\n //\n}\n","// @ref https://github.com/microsoft/TypeScript/issues/2536\nexport const KEYWORD_VARS = [\n // 保留字\n 'break',\n 'case',\n 'catch',\n 'class',\n 'const',\n 'continue',\n 'debugger',\n 'default',\n 'delete',\n 'do',\n 'else',\n 'enum',\n 'export',\n 'extends',\n 'false',\n 'finally',\n 'for',\n 'function',\n 'if',\n 'import',\n 'in',\n 'instanceof',\n 'new',\n 'null',\n 'return',\n 'super',\n 'switch',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typeof',\n 'var',\n 'void',\n 'while',\n 'with',\n // 严格保留字\n 'as',\n 'implements',\n 'interface',\n 'let',\n 'package',\n 'private',\n 'protected',\n 'public',\n 'static',\n 'yield',\n 'namespace',\n 'async',\n 'await',\n // // 上下文关键字\n // 'any',\n // 'boolean',\n // 'constructor',\n // 'declare',\n // 'get',\n // 'module',\n // 'require',\n // 'number',\n // 'set',\n // 'string',\n // 'symbol',\n // 'type',\n // 'from',\n // 'of',\n];\n\nexport const AXIOS_IMPORT_NAME = 'axios';\nexport const ZOD_IMPORT_NAME = 'z';\nexport const FAKER_IMPORT_NAME = 'faker';\nexport const AXIOS_IMPORT_FILE = 'axios';\nexport const FAKER_IMPORT_FILE = '@faker-js/faker';\nexport const AXIOS_PARAM_CONFIG_NAME = 'config';\nexport const AXIOS_RESPONSE_NAME = 'resp';\nexport const ENABLE_MOCK_NAME = 'enableMock';\nexport const AXIOS_PARAM_TRANSFORM_RESPONSE_NAME = 'transformResponse';\nexport const AXIOS_REQUEST_TYPE_NAME = 'AxiosRequestConfig';\nexport const INTERNAL_VARS = [\n AXIOS_IMPORT_NAME,\n ZOD_IMPORT_NAME,\n FAKER_IMPORT_NAME,\n AXIOS_PARAM_CONFIG_NAME,\n AXIOS_RESPONSE_NAME,\n ENABLE_MOCK_NAME,\n];\n/**\n * 合并导出类型名称\n */\nexport const TYPE_FILE_EXPORT_NAME = 'Type';\nexport const INTERNAL_TYPES = [\n // native\n 'Blob',\n 'Array',\n 'Object',\n // typeScript\n 'string',\n 'number',\n 'boolean',\n 'any',\n 'unknown',\n 'void',\n 'never',\n 'null',\n 'undefined',\n 'object',\n 'symbol',\n 'bigint',\n 'Record',\n // type\n TYPE_FILE_EXPORT_NAME,\n AXIOS_REQUEST_TYPE_NAME,\n];\n\nexport const DEFAULT_ENABLE_CONDITION = 'process.env.NODE_ENV !== \"production\"';\nexport const DEFAULT_RESPONSE_DATA_PROPS = ['data'];\n","import type { OpenAPILatest } from '../types/openapi';\nimport { isString } from '../utils/type-is';\n\nexport type OpenApiLatest_Schema = OpenAPILatest.SchemaObject | OpenAPILatest.ReferenceObject;\n\nexport function isRefSchema(schema: OpenApiLatest_Schema): schema is OpenAPILatest.ReferenceObject {\n return '$ref' in schema && isString(schema.$ref);\n}\n\nexport type OpenApiLatest_Parameter = OpenAPILatest.ReferenceObject | OpenAPILatest.ParameterObject;\n\nexport function isRefParameter(parameter: OpenApiLatest_Parameter): parameter is OpenAPILatest.ReferenceObject {\n return '$ref' in parameter && isString(parameter.$ref);\n}\n\nexport type OpenApiLatest_Request = OpenAPILatest.ReferenceObject | OpenAPILatest.RequestBodyObject;\n\nexport function isRefRequest(request: OpenApiLatest_Request): request is OpenAPILatest.ReferenceObject {\n return '$ref' in request && isString(request.$ref);\n}\n\nexport type OpenApiLatest_Media = OpenAPILatest.ReferenceObject | OpenAPILatest.MediaTypeObject;\n\nexport function isRefMedia(request: OpenApiLatest_Media): request is OpenAPILatest.ReferenceObject {\n return '$ref' in request && isString(request.$ref);\n}\n\nexport type OpenApiLatest_PathItem = OpenAPILatest.PathItemObject | OpenAPILatest.ReferenceObject;\n\nexport function isRefPathItem(pathItem: OpenApiLatest_PathItem): pathItem is OpenAPILatest.ReferenceObject {\n return '$ref' in pathItem && isString(pathItem.$ref);\n}\n\nexport type OpenApiLatest_Operation = OpenAPILatest.OperationObject | OpenAPILatest.ReferenceObject;\n\nexport function isRefOperation(operation: OpenApiLatest_Operation): operation is OpenAPILatest.ReferenceObject {\n return '$ref' in operation && isString(operation.$ref);\n}\n\nexport type OpenApiLatest_Response = OpenAPILatest.ResponseObject | OpenAPILatest.ReferenceObject;\n\nexport function isRefResponse(response: OpenApiLatest_Response): response is OpenAPILatest.ReferenceObject {\n return '$ref' in response && isString(response.$ref);\n}\n\nexport function requiredTypeStringify(required?: boolean) {\n return required ? ':' : '?:';\n}\n\nexport function requiredKeyStringify(key: string, required: boolean) {\n return required ? key : `[${key}]`;\n}\n\nexport function toImportString(id: string, name: string, path: string, isType = false) {\n const isDefault = name === '';\n const type = isType ? (isDefault ? ' type ' : 'type ') : (isDefault ? ' ' : '');\n return isDefault\n // 默认导入\n ? `import${type}${id} from \"${path}\";`\n // 具名导入\n : `import {${type}${name} as ${id}} from \"${path}\";`;\n}\n\nexport function toZodName(typeName: string) {\n return `z-${typeName}`;\n}\n\nexport interface OrderlyItem {\n name: string;\n deps: string[];\n}\n/**\n * 根据依赖进行排序\n * @param depList\n * @returns\n */\nexport function sortingByDeps<T extends OrderlyItem>(depList: T[]) {\n return depList.sort((a, b) => {\n if (a.deps.includes(b.name)) {\n return 1;\n }\n else if (b.deps.includes(a.name)) {\n return -1;\n }\n else {\n return 0;\n }\n });\n}\n\nexport function withGroup(texts: string[], options?: {\n sep?: string;\n wrap?: [start: string, end: string];\n always?: boolean;\n}) {\n const {\n sep = ',',\n wrap = ['(', ')'],\n always = false,\n } = options || {};\n const [start, end] = wrap;\n return !always && texts.length < 2 ? (texts.at(0) || '') : start + texts.join(sep) + end;\n}\n","import type { OpenAPILatest } from '../types/openapi';\nimport { isArray, isBoolean, isNumber, isString } from '../utils/type-is';\n\nfunction formatLine(key: string, val: unknown) {\n val = key === 'externalDocs' ? JsDoc.printExternalDoc(val as OpenAPILatest.ExternalDocumentationObject) : val;\n key = key === 'externalDocs' ? 'see' : key;\n\n if (isBoolean(val) && val)\n return `@${key}`;\n if (isString(val) || isNumber(val))\n return `@${key} ${val}`;\n}\n\nconst supportTypes = ['string', 'number', 'boolean', 'object', 'array', 'null'];\n\nexport class JsDoc {\n lines: string[] = [];\n\n constructor(private tags: OpenAPILatest.TagObject[] = []) {}\n\n addComments(comments: Record<string, unknown>) {\n if ('tags' in comments) {\n const tags = (comments.tags || []) as string[];\n comments.tags = undefined;\n tags.forEach((tag) => {\n const info = this.tags.find(t => t.name === tag);\n if (!info)\n return;\n\n comments[`see ${tag}`] = [info.description, JsDoc.printExternalDoc(info.externalDocs)].filter(Boolean).join(' ');\n });\n }\n\n this.addLines(JsDoc.toLines(comments));\n }\n\n addLines(lines: string[]) {\n this.lines.push(...lines);\n }\n\n print() {\n if (this.lines.length === 0)\n return '';\n\n return [\n //\n '/**',\n ...this.lines.map((line) => {\n const slices = line.split('\\n');\n return slices.map(s => ` * ${s}`).join('\\n');\n }),\n ' */',\n ].join('\\n');\n }\n\n static toLines(comments: Record<string, unknown>) {\n return Object.entries(comments)\n .map(([key, val]) => {\n if (isArray(val))\n return val.map(v => formatLine(key, v));\n return formatLine(key, val);\n })\n .flat()\n .filter(Boolean) as string[];\n }\n\n static fromRef(ref: OpenAPILatest.ReferenceObject) {\n const { description, summary } = ref;\n return { description, summary };\n }\n\n static fromSchema(schema: OpenAPILatest.SchemaObject) {\n const { deprecated, description, default: defaultValue, format, example, examples, title, externalDocs, type } = schema;\n const types = isArray(type) ? type : isString(type) ? [type] : undefined;\n\n return {\n summary: title,\n description,\n deprecated,\n default: defaultValue,\n format: format || types?.filter(type => !supportTypes.includes(type)).join(' | ') || undefined,\n example,\n examples,\n externalDocs,\n };\n }\n\n static fromParameter(parameter: OpenAPILatest.ParameterObject) {\n const { deprecated, description, example, examples } = parameter;\n return {\n deprecated,\n description,\n example,\n };\n }\n\n static fromOperation(operation: OpenAPILatest.OperationObject) {\n const { deprecated, description, summary, tags } = operation;\n return {\n deprecated,\n description,\n summary,\n tags,\n };\n }\n\n static printExternalDoc(externalDoc?: OpenAPILatest.ExternalDocumentationObject) {\n const { url, description } = externalDoc || {};\n\n // {@link https://github.com GitHub}\n if (url && description)\n return `{@link ${url} ${description}}`;\n if (url)\n return `{@link ${url}}`;\n if (description)\n return description;\n return false;\n }\n}\n","import type { OpenAPILatest } from '../types/openapi';\nimport type { OpenApiLatest_Schema } from './helpers';\nimport type { Named } from './Named';\nimport { isArray, isBoolean, isNever, isNumber, isString, isUndefined } from '../utils/type-is';\nimport { ZOD_IMPORT_NAME } from './const';\nimport { isRefSchema, requiredTypeStringify, toZodName, withGroup } from './helpers';\nimport { JsDoc } from './JsDoc';\n\nexport interface ParseResult {\n required: boolean;\n comments: Record<string, unknown>;\n deps: string[];\n type: string;\n zod: string;\n}\n\nexport class Parser {\n constructor(readonly named: Named, readonly schema: OpenApiLatest_Schema) {\n //\n }\n\n #depSets = new Set<string>();\n\n #prepareVarName(refId: string) {\n const typeName = this.named.getRefType(refId);\n\n if (!typeName) {\n throw new Error(`未找到 refId: ${refId} 的类型`);\n }\n\n const varName = this.named.prepareVarName(toZodName(typeName));\n this.#depSets.add(varName);\n return varName;\n }\n\n get #depNames() {\n return [...this.#depSets.values()];\n }\n\n #mergeResultDeps(parseResult: ParseResult) {\n parseResult.deps.forEach((d) => {\n this.#depSets.add(d);\n });\n }\n\n #mergeParserDeps(parser: Parser) {\n parser.#depSets.forEach((d) => {\n this.#depSets.add(d);\n });\n }\n\n parse(): ParseResult {\n const { schema } = this;\n\n if (isRefSchema(schema)) {\n const typeName = this.named.getRefType(schema.$ref);\n const zodName = typeName ? this.#prepareVarName(schema.$ref) : `${ZOD_IMPORT_NAME}.unknown()`;\n\n if (!typeName) {\n throw new Error(`未找到 refId: ${schema.$ref} 的类型`);\n }\n\n return {\n type: typeName,\n zod: zodName,\n comments: JsDoc.fromRef(schema),\n required: false,\n deps: [...this.#depSets.values()],\n };\n }\n\n const { type, allOf, oneOf, anyOf, required } = schema;\n const requiredBool = isBoolean(required) ? required : false;\n const comments = JsDoc.fromSchema(schema);\n\n if (allOf && allOf.length > 0) {\n const group = allOf.map(a => Parser.#parseInner(this, a));\n\n return {\n comments,\n required: false,\n deps: this.#depNames,\n type: withGroup(group.map(g => g.type), {\n sep: '&',\n }),\n zod: withGroup(\n group.map(g => g.zod),\n {\n sep: ',',\n wrap: [`${ZOD_IMPORT_NAME}.intersection(`, ')'],\n },\n ),\n };\n }\n\n // TODO 不是精确的 oneof\n // https://arif.thedev.id/blogs/typescript/the-oneof-type\n // 但为了能够将类型转换为 zod schema,暂时保持模糊\n if (oneOf && oneOf.length > 0) {\n const group = oneOf.map(o => Parser.#parseInner(this, o));\n\n return {\n comments,\n required: false,\n deps: this.#depNames,\n type: withGroup(group.map(g => g.type), {\n sep: '|',\n }),\n zod: withGroup(\n group.map(g => g.zod),\n {\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n ),\n };\n }\n\n if (anyOf && anyOf.length > 0) {\n const group = anyOf.map(a => Parser.#parseInner(this, a));\n\n return {\n comments,\n required: false,\n deps: this.#depNames,\n type: withGroup(group.map(g => g.type), {\n sep: '|',\n }),\n zod: withGroup(\n group.map(g => g.zod),\n {\n sep: ',',\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n ),\n };\n }\n\n if (isArray(type)) {\n if (type.length === 0) {\n return Parser.#parseAsUnknown(schema, requiredBool);\n }\n\n if (type.length === 1) {\n return Parser.#parseInner(this, {\n ...schema,\n // eslint-disable-next-line ts/ban-ts-comment\n // @ts-ignore\n type: type[0],\n });\n }\n\n const group = type.map(type => Parser.#parseInner(this, type === 'null'\n // null\n ? { type }\n // origin\n : ({ ...schema, type } as OpenAPILatest.SchemaObject)),\n );\n\n return {\n comments,\n required: false,\n deps: this.#depNames,\n type: withGroup(group.map(g => g.type), {\n sep: '|',\n }),\n zod: withGroup(\n group.map(g => g.zod),\n {\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n ),\n };\n }\n\n switch (type) {\n case 'string': {\n const { enum: enumValues = [], format, minLength, maxLength, pattern } = schema;\n const isBlob = format === 'binary';\n const required = Boolean(schema.required);\n\n return {\n comments: {\n ...comments,\n minLength,\n maxLength,\n pattern,\n },\n required,\n deps: this.#depNames,\n type: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isString(e)\n ? JSON.stringify(e)\n : this.named.getRefType(e.$ref) || 'unknown')),\n {\n sep: '|',\n },\n )\n : isBlob\n ? 'Blob'\n : 'string',\n zod: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isString(e))\n ? `${ZOD_IMPORT_NAME}.literal(${JSON.stringify(e)})`\n : this.#prepareVarName(e.$ref)),\n {\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n )\n : isBlob\n ? `${ZOD_IMPORT_NAME}.instanceof(Blob)`\n : `${ZOD_IMPORT_NAME}.string()`,\n };\n }\n\n case 'number':\n case 'integer': {\n const { enum: enumValues = [], const: const_, minimum, maximum } = schema;\n const required = Boolean(schema.required);\n\n if (!isUndefined(const_))\n enumValues.push(const_);\n\n return {\n comments: {\n ...comments,\n minimum,\n maximum,\n },\n required,\n deps: this.#depNames,\n type: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isNumber(e)\n ? String(e)\n : this.named.getRefType(e.$ref) || 'unknown')),\n {\n sep: '|',\n },\n )\n : 'number',\n zod: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isNumber(e)\n ? `${ZOD_IMPORT_NAME}.literal(${e})`\n : this.#prepareVarName(e.$ref))),\n {\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n )\n : `${ZOD_IMPORT_NAME}.number()`,\n };\n }\n\n case 'boolean': {\n const { enum: enumValues = [] } = schema;\n const required = Boolean(schema.required);\n\n return {\n comments,\n required,\n deps: this.#depNames,\n type: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isBoolean(e)\n ? String(e)\n : this.named.getRefType(e.$ref) || 'unknown')),\n {\n sep: '|',\n },\n )\n : type,\n zod: enumValues.length > 0\n ? withGroup(\n enumValues.map(e => (isBoolean(e)\n ? `${ZOD_IMPORT_NAME}.literal(${e})`\n : this.#prepareVarName(e.$ref))),\n {\n sep: ',',\n wrap: [`${ZOD_IMPORT_NAME}.union([`, '])'],\n },\n )\n : `${ZOD_IMPORT_NAME}.boolean()`,\n };\n }\n\n case 'null': {\n const required = Boolean(schema.required);\n\n return {\n comments,\n required,\n deps: this.#depNames,\n type,\n zod: `${ZOD_IMPORT_NAME}.null()`,\n };\n }\n\n case 'array':\n return Parser.#parseArray(this, schema);\n\n case 'object':\n return Parser.#parseObject(this, schema);\n\n case undefined: {\n // 智能判断类型\n if ('properties' in schema) {\n return Parser.#parseObject(this, schema);\n }\n else if ('additionalProperties' in schema) {\n return Parser.#parseObject(this, schema);\n }\n else if ('items' in schema) {\n return Parser.#parseArray(this, schema as unknown as OpenAPILatest.ArraySchemaObject);\n }\n else {\n return Parser.#parseAsUnknown(schema, requiredBool);\n }\n }\n\n default:\n isNever(type);\n return Parser.#parseAsUnknown(schema, requiredBool);\n }\n }\n\n static parse(named: Named, schema: OpenApiLatest_Schema) {\n return new Parser(named, schema).parse();\n }\n\n static #parseInner(parent: Parser, schema: OpenApiLatest_Schema) {\n const result = new Parser(parent.named, schema).parse();\n parent.#mergeResultDeps(result);\n return result;\n }\n\n static #parseAsUnknown(schema: OpenApiLatest_Schema, required = false, spec?: { type?: string; zod?: string }): ParseResult {\n const comments = JsDoc.fromSchema(schema);\n\n return {\n comments,\n required,\n deps: [],\n type: spec?.type || 'unknown',\n zod: spec?.zod || `${ZOD_IMPORT_NAME}.unknown()`,\n };\n }\n\n static #parseArray(parent: Parser, schema: OpenAPILatest.ArraySchemaObject) {\n const comments = JsDoc.fromSchema(schema);\n const { minItems, maxItems, items } = schema;\n const result = Parser.#parseInner(parent, items);\n\n return {\n comments: {\n ...comments,\n minItems,\n maxItems,\n },\n required: false,\n deps: result.deps,\n type: `Array<${result.type}>`,\n zod: `${ZOD_IMPORT_NAME}.array(${result.zod})`,\n };\n }\n\n static #parseObject(parent: Parser, schema: OpenAPILatest.SchemaObject): ParseResult {\n const parser = new Parser(parent.named, schema);\n const required = isBoolean(schema.required) ? schema.required : false;\n const comments = JsDoc.fromSchema(schema);\n const explicitProps = 'properties' in schema ? schema.properties : undefined;\n\n // additionalProperties: true\n // additionalProperties: false\n // additionalProperties: {...}\n const genericProps = 'additionalProperties' in schema ? schema.additionalProperties : undefined;\n const explicitEntries = Object.entries(explicitProps || {});\n\n const noExplicitProps = explicitEntries.length === 0;\n const noGenericProps = isUndefined(genericProps) || genericProps === false || Object.keys(genericProps).length === 0;\n\n const typeList: string[] = [];\n const zodList: string[] = [];\n\n // 有显式属性\n if (!noExplicitProps) {\n const propTypeList: string[] = [];\n const propZodList: string[] = [];\n\n explicitEntries.forEach(([name, propSchema]) => {\n const { type, zod } = Parser.#parseObjectProp(parser, name, propSchema, isArray(schema.required) ? schema.required?.includes(name) : false);\n propTypeList.push(type);\n propZodList.push(zod);\n });\n\n typeList.push(withGroup(propTypeList, {\n sep: '\\n',\n wrap: ['{\\n', '\\n}'],\n always: true,\n }));\n zodList.push(withGroup(propZodList, {\n sep: '\\n',\n wrap: [`${ZOD_IMPORT_NAME}.object({\\n`, '\\n})'],\n always: true,\n }));\n }\n\n // 有泛型属性\n if (!noGenericProps) {\n const { type, zod } = Parser.#parseInner(parser, genericProps as OpenApiLatest_Schema);\n\n typeList.push(`Record<string, ${type}>`);\n zodList.push(`${ZOD_IMPORT_NAME}.record(${ZOD_IMPORT_NAME}.string(), ${zod})`);\n }\n\n // 无显式属性 && 无泛型属性\n if (typeList.length === 0) {\n return Parser.#parseAsUnknown(schema, required, {\n type: 'Record<string, unknown>',\n zod: `${ZOD_IMPORT_NAME}.record(${ZOD_IMPORT_NAME}.string(), ${ZOD_IMPORT_NAME}.unknown())`,\n });\n }\n\n parent.#mergeParserDeps(parser);\n\n return {\n comments,\n required: isBoolean(schema.required) ? schema.required : false,\n deps: parser.#depNames,\n type: withGroup(typeList, {\n sep: '&',\n }),\n zod: withGroup(zodList, {\n wrap: [`${ZOD_IMPORT_NAME}.intersection(`, ')'],\n }),\n };\n }\n\n static #parseObjectProp(parent: Parser, propName: string, propSchema: boolean | OpenAPILatest.SchemaObject | OpenAPILatest.ReferenceObject, propRequired1: boolean) {\n const { required: propRequired2, comments, type, zod } = isBoolean(propSchema) ? Parser.#parsePropBoolean(propSchema) : Parser.#parseInner(parent, propSchema);\n const jsDoc = new JsDoc();\n jsDoc.addComments(comments);\n const required = propRequired1 || propRequired2 || false;\n\n return {\n type: [jsDoc.print(), `${JSON.stringify(propName)}${requiredTypeStringify(required)}${type};`].filter(Boolean).join('\\n'),\n zod: `${JSON.stringify(propName)}: ${required ? zod : `${ZOD_IMPORT_NAME}.optional(${zod})`},`,\n };\n }\n\n static #parsePropBoolean(bool: boolean): ParseResult {\n return {\n comments: {},\n deps: [],\n required: true,\n type: bool ? 'any' : 'never',\n zod: bool ? `${ZOD_IMPORT_NAME}.any()` : `${ZOD_IMPORT_NAME}.never()`,\n };\n }\n}\n","export interface PathSlice {\n type: 'param' | 'segment';\n value: string;\n};\n\nexport class VarPath {\n #slices: PathSlice[] = [];\n props: string[] = [];\n constructor(readonly path: string) {\n this.#slices = path.split('/').map((s) => {\n const type = s.startsWith('{') && s.endsWith('}') ? 'param' : 'segment';\n const value = type === 'segment' ? s : s.slice(1, -1);\n\n if (type === 'param')\n this.props.push(value);\n\n return { type, value };\n });\n }\n\n toString(vars: Record<string, string>) {\n if (!this.props.length) {\n return JSON.stringify(this.path);\n }\n\n const path = this.#slices.map(({ type, value }) => {\n if (type === 'segment')\n return value;\n\n const varName = vars[value];\n\n if (varName === undefined) {\n throw new Error(`路径参数 ${value} 未定义`);\n }\n\n return `\\${${vars[value]}}`;\n }).join('/');\n return `\\`${path}\\``;\n }\n\n toPattern() {\n if (!this.props.length) {\n return JSON.stringify(this.path);\n }\n\n const main = this.#slices.map(({ type, value }) => {\n return type === 'segment' ? value : '[^/]+';\n }).join('\\\\/');\n return `/^${main}$/`;\n }\n}\n","import type { OpenAPILatest } from '../types/openapi';\nimport type { OpenApiLatest_Parameter } from './helpers';\nimport type { Named } from './Named';\nimport type { PrinterOptions } from './types';\nimport { AXIOS_PARAM_CONFIG_NAME, AXIOS_REQUEST_TYPE_NAME } from './const';\nimport { isRefParameter, requiredKeyStringify, toZodName } from './helpers';\nimport { Parser } from './Parser';\nimport { VarPath } from './VarPath';\n\nexport type ArgKind = 'path' | 'headers' | 'cookies' | 'params' | 'data' | 'config' | 'response';\n\nexport interface ArgProp {\n name: string;\n parameter: OpenAPILatest.ParameterObject;\n schema: OpenAPILatest.SchemaObject;\n}\n\nexport class Arg {\n parameters: OpenApiLatest_Parameter[] = [];\n originName: string = '';\n\n /**\n * 作为属性的名称\n */\n propName = '';\n /**\n * 作为参数的注释名称\n */\n docName = '';\n /**\n * 作为参数的变量名称\n */\n argName = '';\n /**\n * 作为 zod 的变量名称\n */\n zodName = '';\n /**\n * 是否必填\n */\n required = false;\n /**\n * 类型名称\n */\n typeName = '';\n /**\n * 类型值\n */\n typeValue = '';\n /**\n * zod 值\n */\n zodValue = '';\n comments: Record<string, unknown> = {};\n props: ArgProp[] = [];\n\n constructor(\n readonly kind: ArgKind,\n readonly operationName: string,\n readonly docNamed: Named,\n readonly argNamed: Named,\n readonly printOptions: PrinterOptions,\n /**\n * 是否单参数(如 data、config、response)\n */\n readonly isSingle: boolean = false,\n ) {\n this.originName = kind;\n this.propName = kind === 'path' ? 'url' : kind;\n this.docName = kind;\n this.argName = '';\n this.typeName = docNamed.nextTypeName(`${operationName}-${kind}`);\n this.zodName = docNamed.prepareVarName(toZodName(this.typeName));\n }\n\n varPath = new VarPath('');\n\n add(parameter?: OpenApiLatest_Parameter) {\n if (!parameter)\n return;\n\n this.parameters.push(parameter);\n }\n\n parse(): Arg | null {\n const fixedParameters = this.parameters.filter(p => !isRefParameter(p) && 'schema' in p && p.schema) as OpenAPILatest.ParameterObject[];\n const propLength = fixedParameters.length;\n const requiredNames: string[] = [];\n\n fixedParameters.forEach((parameter) => {\n const { required, schema, name } = parameter;\n\n if (!schema)\n return;\n\n if (required || parameter.in === 'path') {\n requiredNames.push(name);\n parameter.required = true;\n }\n\n this.props.push({\n parameter,\n name,\n schema: schema!,\n });\n });\n\n switch (propLength) {\n case 0: {\n switch (this.kind) {\n case 'path':\n this.required = true;\n this.typeValue = '';\n this.argName = this.argNamed.nextVarName(this.docName);\n return this;\n\n case 'config':\n this.typeValue = AXIOS_REQUEST_TYPE_NAME;\n this.argName = AXIOS_PARAM_CONFIG_NAME;\n this.comments = {\n [`param [${this.argName}]`]: `request ${this.propName}`,\n };\n return this;\n }\n return null;\n }\n\n case 1: {\n // prop0: type0\n const [firstArg] = this.props;\n const { parameter, schema } = firstArg;\n const result = Parser.parse(this.docNamed, schema);\n const isResponse = this.kind === 'response';\n const required = parameter.required || result.required || false;\n\n this.originName = firstArg.name;\n this.argName = this.argNamed.nextVarName(firstArg.name);\n this.required = required;\n this.typeValue = result.type;\n this.zodValue = result.zod;\n this.comments = isResponse\n ? {\n returns: parameter.description || schema.description || false,\n }\n : {\n [\n `param ${requiredKeyStringify(this.argName, required)}`]: parameter.description || schema.description\n || (this.kind === 'data' ? 'request data' : `request ${this.docName} ${JSON.stringify(firstArg.name)}`),\n };\n return this;\n }\n\n default: {\n // name: {prop0: type0, prop1: type1, ...}\n const rootSchema: OpenAPILatest.SchemaObject = {\n type: 'object',\n properties: this.props.reduce(\n (acc, { parameter, schema, name: originName }) => {\n acc[originName] = {\n ...schema,\n description: parameter.description || schema.description,\n deprecated: parameter.deprecated || schema.deprecated,\n };\n return acc;\n },\n {} as Record<string, OpenAPILatest.SchemaObject>,\n ),\n required: requiredNames,\n };\n const result = Parser.parse(this.docNamed, rootSchema);\n const required = requiredNames.length > 0;\n\n this.required = required;\n this.typeValue = result.type;\n this.zodValue = result.zod;\n this.argName = this.argNamed.nextVarName(this.docName);\n this.comments = this.kind === 'response'\n ? {\n returns: result.comments.description,\n }\n : {\n [`param ${requiredKeyStringify(this.docName, required)}`]: `request ${this.docName}`,\n };\n return this;\n }\n }\n }\n}\n","import type { Arg } from './Arg';\nimport { TYPE_FILE_EXPORT_NAME } from './const';\nimport { requiredTypeStringify } from './helpers';\n\nexport class Args {\n fixedArgs: Arg[];\n constructor(private args: (Arg | null)[]) {\n this.fixedArgs = this._sort();\n }\n\n private _sort() {\n const fixedArgs = this.args.filter(Boolean) as Arg[];\n return fixedArgs.sort((a, b) => Number(b.required) - Number(a.required));\n }\n\n toComments() {\n return this.fixedArgs.reduce(\n (acc, arg) => {\n return {\n ...acc,\n ...arg.comments,\n };\n },\n {} as Record<string, unknown>,\n );\n }\n\n printFormalParams() {\n return this.fixedArgs\n .filter(fixArg => fixArg.typeValue !== '')\n .map((fixArg) => {\n const typeValue = fixArg.kind === 'config' ? fixArg.typeValue : `${TYPE_FILE_EXPORT_NAME}.${fixArg.typeName}`;\n return `${fixArg.argName}${requiredTypeStringify(fixArg.required)}${typeValue}`;\n })\n .join(',');\n }\n\n filterValidateAble() {\n return this.fixedArgs\n .filter(fixArg => fixArg.typeValue !== '' && fixArg.kind !== 'config');\n }\n\n printSchemaTypes() {\n return this.filterValidateAble()\n .map(fixArg => `export type ${fixArg.typeName} = ${fixArg.typeValue};`);\n }\n\n printActualParams() {\n return this.fixedArgs\n .map((fixedArg) => {\n const { originName, argName: varName, propName, kind, props, varPath, isSingle } = fixedArg;\n\n switch (kind) {\n case 'config':\n return `...${varName}`;\n\n case 'path': {\n const singleProp = varPath.props.length === 1;\n const resolvedURL = varPath.toString(props.reduce((acc, cur) => {\n acc[cur.name] = singleProp ? varName : `${varName}[${JSON.stringify(cur.name)}]`;\n return acc;\n }, {} as Record<string, string>));\n return `url: ${resolvedURL}`;\n }\n\n default: {\n const value = props.length === 1 && !isSingle ? `{${JSON.stringify(originName)}: ${varName}}` : varName;\n return `${propName}: ${value}`;\n }\n }\n })\n .join(',\\n');\n }\n}\n","import type { OrderlyItem } from './helpers';\nimport { isArray } from '../utils/type-is';\nimport { sortingByDeps } from './helpers';\n\nconst contentTypes = [\n 'header',\n 'alert',\n 'info',\n 'import',\n 'block',\n 'footer',\n] as const;\nexport type ContentType = typeof contentTypes[number];\nexport interface OrderlyCode extends OrderlyItem {\n code: string;\n}\n\nexport class Content {\n parts = new Map<ContentType, string[]>();\n orderlyCodes: OrderlyCode[] = [];\n\n push(type: ContentType, code: string | string[]) {\n const part = this.parts.get(type) || [];\n part.push(...isArray(code) ? code : [code]);\n this.parts.set(type, part);\n }\n\n unshift(type: ContentType, code: string | string[]) {\n const part = this.parts.get(type) || [];\n part.unshift(...isArray(code) ? code : [code]);\n this.parts.set(type, part);\n }\n\n add(orderlyCode: OrderlyCode) {\n this.orderlyCodes.push(orderlyCode);\n }\n\n print() {\n this.unshift('block', sortingByDeps(this.orderlyCodes).map(i => i.code));\n this.orderlyCodes.length = 0;\n const parts = [...contentTypes].map(t => this.parts.get(t)).filter(Boolean) as string[][];\n return parts.map(p => p.join('\\n')).join('\\n\\n');\n }\n\n errors: string[] = [];\n pushError(message: string) {\n this.errors.push(message);\n }\n}\n","import { fixVarName, nextUniqueName } from '../utils/string';\nimport { INTERNAL_TYPES, INTERNAL_VARS, KEYWORD_VARS } from './const';\n\nexport class Named {\n // eslint-disable-next-line style/type-generic-spacing\n varNameCountMap = new Map<string /* var */, number /* count */>();\n // eslint-disable-next-line style/type-generic-spacing\n typeNameCountMap = new Map<string /* type */, number /* count */>();\n // eslint-disable-next-line style/type-generic-spacing\n refIdTypeMap = new Map<string /* refId */, string /* refType */>();\n\n constructor({ keywordVars, internalTypes, internalVars }: {\n keywordVars?: boolean;\n internalVars?: boolean;\n internalTypes?: boolean;\n } = {}) {\n if (keywordVars)\n KEYWORD_VARS.forEach(this.internalVarName.bind(this));\n\n if (internalVars)\n INTERNAL_VARS.forEach(this.internalVarName.bind(this));\n\n if (internalTypes)\n INTERNAL_TYPES.forEach(this.internalTypeName.bind(this));\n }\n\n /**\n * 注册内部变量\n * @param {string} varName\n */\n internalVarName(varName: string) {\n this.varNameCountMap.set(varName, 1);\n }\n\n /**\n * 注册内部类型\n * @param {string} typeName\n */\n internalTypeName(typeName: string) {\n this.typeNameCountMap.set(typeName, 1);\n }\n\n nextVarName(name: string) {\n return nextUniqueName(fixVarName(name), this.varNameCountMap);\n }\n\n prepareVars = new Map<string, string>();\n /**\n * 预设变量名,如果存在则返回,否则创建\n * @param {string} name\n */\n prepareVarName(name: string) {\n const next = this.prepareVars.get(name) || this.nextVarName(name);\n this.prepareVars.set(name, next);\n return next;\n }\n\n nextOperationId(method: string, url: string, operationId?: string) {\n operationId = operationId\n || fixVarName(\n [\n method,\n url\n .replace(/\\{.*?\\}/g, '')\n .split('/')\n .filter(Boolean),\n ].join('_'),\n );\n return nextUniqueName(operationId, this.varNameCountMap);\n }\n\n nextTypeName(typeName: string) {\n const fixedTypeName = fixVarName(typeName, true, 'Type');\n return nextUniqueName(fixedTypeName, this.typeNameCountMap);\n }\n\n nextRefType(refType: string, refId: string) {\n const uniqueTypeName = this.nextTypeName(refType);\n this.setRefType(refId, uniqueTypeName);\n return uniqueTypeName;\n }\n\n setRefType(refId: string, refType: string) {\n this.refIdTypeMap.set(refId, refType);\n }\n\n getRefType(refId: string) {\n return this.refIdTypeMap.get(refId);\n }\n}\n","import type { OpenAPILatest } from '../types/openapi';\nimport type {\n OpenApiLatest_Media,\n OpenApiLatest_Operation,\n OpenApiLatest_Parameter,\n OpenApiLatest_PathItem,\n OpenApiLatest_Request,\n OpenApiLatest_Response,\n OpenApiLatest_Schema,\n} from './helpers';\nimport type { PrinterConfigs, PrinterOptions, PrintResults } from './types';\nimport { isPackageExists } from 'local-pkg';\nimport { pkgName, pkgVersion } from '../const';\nimport { OpenAPIVersion } from '../types/openapi';\nimport { toImportPath, toRelative } from '../utils/path';\nimport { fixVarName } from '../utils/string';\nimport { isBoolean, isString, isUndefined } from '../utils/type-is';\nimport { Arg } from './Arg';\nimport { Args } from './Args';\nimport {\n AXIOS_IMPORT_FILE,\n AXIOS_IMPORT_NAME,\n AXIOS_PARAM_TRANSFORM_RESPONSE_NAME,\n AXIOS_REQUEST_TYPE_NAME,\n AXIOS_RESPONSE_NAME,\n DEFAULT_ENABLE_CONDITION,\n DEFAULT_RESPONSE_DATA_PROPS,\n ENABLE_MOCK_NAME,\n FAKER_IMPORT_FILE,\n FAKER_IMPORT_NAME,\n TYPE_FILE_EXPORT_NAME,\n ZOD_IMPORT_NAME,\n} from './const';\nimport { Content } from './Content';\nimport {\n isRefMedia,\n isRefOperation,\n isRefParameter,\n isRefPathItem,\n isRefRequest,\n isRefResponse,\n isRefSchema,\n toImportString,\n toZodName,\n} from './helpers';\nimport { JsDoc } from './JsDoc';\nimport { Named } from './Named';\nimport { Parser } from './Parser';\nimport { VarPath } from './VarPath';\n\nconst allowMethods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];\nconst parameterTypes = ['query', 'header', 'path', 'cookie'];\n\ntype RequestMediaMatch = (contentType: string, content: OpenApiLatest_Media) => boolean;\ntype ResponseMediaMatch = (\n contentType: string,\n content: OpenApiLatest_Media,\n response: OpenAPILatest.ResponseObject,\n) => boolean;\n\ntype ResponseMatch = (statusCode: string, response: OpenApiLatest_Response) => boolean;\n\ntype WithId<T> = T & {\n nodeId: string;\n namedId?: string;\n};\ntype SchemaInfo = WithId<{\n position: 'root' | 'anchor';\n schema: OpenApiLatest_Schema;\n typeName: string;\n nodeName: string;\n}>;\ntype RequestBodyInfo = WithId<{ requestBody: OpenApiLatest_Request }>;\ntype ParameterInfo = WithId<{ parameter: OpenApiLatest_Parameter }>;\ntype ResponseInfo = WithId<{ response: OpenApiLatest_Response }>;\ntype PathItemInfo = WithId<{ pathItem: OpenApiLatest_PathItem }>;\n\nexport class Printer {\n named = new Named({ internalVars: true, internalTypes: true });\n\n #mainContent = new Content();\n #typeContent = new Content();\n #zodContent = new Content();\n #mockContent = new Content();\n\n private configs: PrinterConfigs = {};\n\n constructor(\n private readonly document: OpenAPILatest.Document,\n private options?: PrinterOptions,\n ) {\n const { openapi } = document;\n\n if (!openapi)\n throw new Error('未找到 openapi 版本号');\n if (!openapi.startsWith(OpenAPIVersion.V3_1)) {\n throw new Error(`当前仅支持 openapi ${OpenAPIVersion.V3_1},当前版本为 ${openapi}`);\n }\n\n this.registerComponents();\n }\n\n schemas: Record<string /** nodeId */, SchemaInfo> = {};\n requestBodies: Record<string /** nodeId */, RequestBodyInfo> = {};\n parameters: Record<string /** nodeId */, ParameterInfo> = {};\n responses: Record<string /** nodeId */, ResponseInfo> = {};\n pathItems: Record<string /** nodeId */, PathItemInfo> = {};\n\n #pathZodNames = new Set<string>();\n #respZodNames = new Set<string>();\n\n #parseRefComponent<T>(\n {\n kind,\n name,\n obj,\n }: {\n kind: keyof OpenAPILatest.ComponentsObject;\n name: string;\n obj: { $ref: string } | { $id?: string };\n },\n processor: (nodeId: string, namedId?: string) => unknown,\n ) {\n const nodeId = `#/components/${kind}/${name}`;\n const refId = '$ref' in obj ? obj.$ref : '';\n const namedId = '$ref' in obj ? '' : obj.$id;\n\n if (refId === nodeId) {\n throw new Error(`${kind}/${name} 引用了自身`);\n }\n\n processor(nodeId, namedId);\n }\n\n registerComponents() {\n const {\n schemas = {},\n requestBodies = {},\n parameters = {},\n responses = {},\n pathItems = {},\n } = this.document.components || {};\n\n for (const [name, schema] of Object.entries(schemas)) {\n this.#parseRefComponent(\n {\n kind: 'schemas',\n name,\n obj: schema,\n },\n (nodeId, namedId) => {\n if (this.schemas[nodeId]) {\n throw new Error(`重复的 schema 引用 id:${nodeId}`);\n }\n\n const typeName = this.named.nextRefType(name, nodeId);\n this.schemas[nodeId] = {\n position: 'root',\n typeName,\n schema,\n nodeId,\n namedId,\n nodeName: name,\n };\n this.#tryRegisterAnchors({ namedId, nodeId, typeName }, schema);\n\n if (namedId) {\n this.named.setRefType(namedId, typeName);\n }\n },\n );\n }\n\n for (const [name, requestBody] of Object.entries(requestBodies)) {\n this.#parseRefComponent(\n {\n kind: 'requestBodies',\n name,\n obj: requestBody,\n },\n (nodeId, namedId) => {\n if (this.requestBodies[nodeId]) {\n throw new Error(`重复的 requestBody 引用 id:${nodeId}`);\n }\n\n this.requestBodies[nodeId] = { nodeId, namedId, requestBody };\n },\n );\n }\n\n for (const [name, parameter] of Object.entries(parameters)) {\n this.#parseRefComponent(\n {\n kind: 'parameters',\n name,\n obj: parameter,\n },\n (nodeId, namedId) => {\n if (this.parameters[nodeId]) {\n throw new Error(`重复的 parameter 引用 id:${nodeId}`);\n }\n\n this.parameters[nodeId] = { nodeId, namedId, parameter };\n },\n );\n }\n\n for (const [name, response] of Object.entries(responses)) {\n this.#parseRefComponent(\n {\n kind: 'responses',\n name,\n obj: response,\n },\n (nodeId, namedId) => {\n if (this.responses[nodeId]) {\n throw new Error(`重复的 response 引用 id:${nodeId}`);\n }\n\n this.responses[nodeId] = { nodeId, namedId, response };\n },\n );\n }\n\n for (const [name, pathItem] of Object.entries(pathItems)) {\n this.#parseRefComponent(\n {\n kind: 'pathItems',\n name,\n obj: pathItem,\n },\n (nodeId, namedId) => {\n if (this.pathItems[nodeId]) {\n throw new Error(`重复的 pathItem 引用 id:${nodeId}`);\n }\n\n this.pathItems[nodeId] = { nodeId, namedId, pathItem };\n },\n );\n }\n }\n\n #tryRegisterAnchors(info: { nodeId: string; namedId?: string; typeName: string }, schema: OpenApiLatest_Schema) {\n const { nodeId, namedId, typeName } = info;\n\n if (isRefSchema(schema))\n return;\n\n if (schema.$anchor) {\n const anchorId = `${nodeId}#${schema.$anchor}`;\n\n if (this.schemas[anchorId]) {\n throw new Error(`重复的 anchor 引用 id:${anchorId}`);\n }\n\n const anchorNamedId = namedId && `${namedId}#${schema.$anchor}`;\n const anchorTypeName = this.named.nextTypeName(`${typeName}-${schema.$anchor}`);\n\n this.schemas[anchorId] = {\n position: 'anchor',\n nodeId: anchorId,\n namedId: anchorNamedId,\n typeName: anchorTypeName,\n schema,\n nodeName: anchorId,\n };\n\n this.named.setRefType(anchorId, anchorTypeName);\n anchorNamedId && this.named.setRefType(anchorNamedId, anchorTypeName);\n }\n\n if ('items' in schema && schema.items) {\n this.#tryRegisterAnchors(info, schema.items);\n }\n else if ('properties' in schema && schema.properties) {\n for (const [prop, property] of Object.entries(schema.properties)) {\n this.#tryRegisterAnchors(info, property);\n }\n }\n }\n\n print(configs?: PrinterConfigs): PrintResults {\n Object.assign(this.configs, configs);\n const {\n hideHeaders,\n hideFooters,\n hideAlert,\n hideInfo,\n hideSchemas,\n hideImports,\n hidePaths,\n } = this.configs;\n\n !hideInfo && this.#printInfo();\n !hideAlert && this.#printAlert();\n !hideSchemas && this.#printSchemas();\n !hidePaths && this.#printPaths();\n !hideHeaders && this.#printHeader();\n !hideFooters && this.#printFooter();\n // 一定要放在最后,因为在 printSchemas 和 printPaths 阶段有更新 zodName\n !hideImports && this.#printImports();\n\n return {\n main: {\n lang: 'ts',\n code: this.#mainContent.print(),\n errors: this.#mainContent.errors,\n },\n type: {\n lang: 'ts',\n code: this.#typeContent.print(),\n errors: this.#typeContent.errors,\n },\n zod: {\n lang: 'ts',\n code: this.#zodContent.print(),\n errors: this.#zodContent.errors,\n },\n mock: {\n lang: 'ts',\n code: this.#mockContent.print(),\n errors: this.#mockContent.errors,\n },\n };\n }\n\n #printAlert() {\n const alert = [\n `/**`,\n ` * 由 ${pkgName}@${pkgVersion} 生成,建议忽略此文件的格式校验`,\n ` */`,\n ];\n\n this.#mainContent.push('alert', alert);\n this.#typeContent.push('alert', alert);\n this.#zodContent.push('alert', alert);\n this.#mockContent.push('alert', alert);\n }\n\n #printInfo() {\n const { contact, description, license, summary, termsOfService, title, version } = this.document.info;\n const { externalDocs } = this.document;\n const { name, email, url } = contact || {};\n\n const jsDoc = new JsDoc();\n const { document } = this.configs;\n document && jsDoc.addComments({ document });\n\n const extDoc = JsDoc.printExte