import path from 'node:path' import { camelCase, pascalCase } from '@internals/utils' import { usePluginManager } from '@kubb/core/hooks' import type { KubbFile } from '@kubb/fabric-core/types' import type { Operation } from '@kubb/oas' import type { OperationSchemas } from '@kubb/plugin-oas' import { createReactGenerator } from '@kubb/plugin-oas/generators' import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks' import { getBanner, getFooter } from '@kubb/plugin-oas/utils' import { pluginTsName } from '@kubb/plugin-ts' import { pluginZodName } from '@kubb/plugin-zod' import { File } from '@kubb/react-fabric' import { ClassClient } from '../components/ClassClient' import { WrapperClient } from '../components/WrapperClient' import type { PluginClient } from '../types' type OperationData = { operation: Operation name: string typeSchemas: OperationSchemas zodSchemas: OperationSchemas | undefined typeFile: KubbFile.File zodFile: KubbFile.File } type Controller = { name: string file: KubbFile.File operations: Array } export const classClientGenerator = createReactGenerator({ name: 'classClient', Operations({ operations, generator, plugin, config }) { const { options, key: pluginKey } = plugin const pluginManager = usePluginManager() const oas = useOas() const { getName, getFile, getGroup, getSchemas } = useOperationManager(generator) function buildOperationData(operation: Operation): OperationData { const type = { file: getFile(operation, { pluginKey: [pluginTsName] }), schemas: getSchemas(operation, { pluginKey: [pluginTsName], type: 'type' }), } const zod = { file: getFile(operation, { pluginKey: [pluginZodName] }), schemas: getSchemas(operation, { pluginKey: [pluginZodName], type: 'function' }), } return { operation, name: getName(operation, { type: 'function' }), typeSchemas: type.schemas, zodSchemas: zod.schemas, typeFile: type.file, zodFile: zod.file, } } // Group operations by tag const controllers = operations.reduce( (acc, operation) => { const group = getGroup(operation) const groupName = group?.tag ? (options.group?.name?.({ group: camelCase(group.tag) }) ?? pascalCase(group.tag)) : 'Client' if (!group?.tag && !options.group) { // If no grouping, put all operations in a single class const name = 'ApiClient' const file = pluginManager.getFile({ name, extname: '.ts', pluginKey, }) const operationData = buildOperationData(operation) const previousFile = acc.find((item) => item.file.path === file.path) if (previousFile) { previousFile.operations.push(operationData) } else { acc.push({ name, file, operations: [operationData] }) } } else if (group?.tag) { // Group by tag const name = groupName const file = pluginManager.getFile({ name, extname: '.ts', pluginKey, options: { group }, }) const operationData = buildOperationData(operation) const previousFile = acc.find((item) => item.file.path === file.path) if (previousFile) { previousFile.operations.push(operationData) } else { acc.push({ name, file, operations: [operationData] }) } } return acc }, [] as Array, ) function collectTypeImports(ops: Array) { const typeImportsByFile = new Map>() const typeFilesByPath = new Map() ops.forEach((op) => { const { typeSchemas, typeFile } = op if (!typeImportsByFile.has(typeFile.path)) { typeImportsByFile.set(typeFile.path, new Set()) } const typeImports = typeImportsByFile.get(typeFile.path)! if (typeSchemas.request?.name) typeImports.add(typeSchemas.request.name) if (typeSchemas.response?.name) typeImports.add(typeSchemas.response.name) if (typeSchemas.pathParams?.name) typeImports.add(typeSchemas.pathParams.name) if (typeSchemas.queryParams?.name) typeImports.add(typeSchemas.queryParams.name) if (typeSchemas.headerParams?.name) typeImports.add(typeSchemas.headerParams.name) typeSchemas.statusCodes?.forEach((item) => { if (item?.name) typeImports.add(item.name) }) typeFilesByPath.set(typeFile.path, typeFile) }) return { typeImportsByFile, typeFilesByPath } } function collectZodImports(ops: Array) { const zodImportsByFile = new Map>() const zodFilesByPath = new Map() ops.forEach((op) => { const { zodSchemas, zodFile } = op if (!zodImportsByFile.has(zodFile.path)) { zodImportsByFile.set(zodFile.path, new Set()) } const zodImports = zodImportsByFile.get(zodFile.path)! if (zodSchemas?.response?.name) zodImports.add(zodSchemas.response.name) if (zodSchemas?.request?.name) zodImports.add(zodSchemas.request.name) zodFilesByPath.set(zodFile.path, zodFile) }) return { zodImportsByFile, zodFilesByPath } } const files = controllers.map(({ name, file, operations: ops }) => { const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops) const { zodImportsByFile, zodFilesByPath } = options.parser === 'zod' ? collectZodImports(ops) : { zodImportsByFile: new Map>(), zodFilesByPath: new Map() } const hasFormData = ops.some((op) => op.operation.getContentType() === 'multipart/form-data') return ( {options.importPath ? ( <> ) : ( <> )} {hasFormData && } {Array.from(typeImportsByFile.entries()).map(([filePath, imports]) => { const typeFile = typeFilesByPath.get(filePath) if (!typeFile) { return null } const importNames = Array.from(imports).filter(Boolean) if (importNames.length === 0) { return null } return })} {options.parser === 'zod' && Array.from(zodImportsByFile.entries()).map(([filePath, imports]) => { const zodFile = zodFilesByPath.get(filePath) if (!zodFile) { return null } const importNames = Array.from(imports).filter(Boolean) if (importNames.length === 0) { return null } return })} ) }) if (options.wrapper) { const wrapperFile = pluginManager.getFile({ name: options.wrapper.className, extname: '.ts', pluginKey, }) files.push( {options.importPath ? ( ) : ( )} {controllers.map(({ name, file }) => ( ))} name)} /> , ) } return files }, })