openapi-mock-gen
Version:
Generate mock definitions with random fake data based on OpenAPI spec.
1 lines • 143 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../lib/shared/constants.ts","../lib/shared/prompts.ts","../lib/shared/utils.ts","../lib/shared/templates.ts","../lib/shared/writer.ts","../lib/core/config.ts","../lib/core/specs.ts","../lib/core/mock-generator.ts","../lib/core/openapi-parser.ts","../lib/core/endpoint-organizor.ts","../lib/adapters/msw/constants.ts","../lib/adapters/msw/templates.ts","../lib/adapters/msw/writers.ts","../lib/adapters/msw/index.ts","../lib/adapters/index.ts","../lib/main.ts","../lib/cli.ts"],"sourcesContent":["export const EXPORT_LANGUAGE = {\n TS: 'typescript',\n JS: 'javascript',\n} as const;\nexport const DEFAULT_CONFIG = {\n outputDir: './.mocks',\n baseUrl: 'http://localhost:3000',\n arrayLength: 5,\n language: EXPORT_LANGUAGE.JS,\n useExample: true,\n dynamic: true,\n};\nexport const TS_EXTENSION = '.ts';\nexport const JS_EXTENSION = '.js';\nexport const CONFIG_FILE_NAME = 'mock.config';\nexport const MANIFEST_FILE_NAME = 'manifest.json';\nexport const DEFAULT_MIN_NUMBER = 0;\nexport const DEFAULT_MAX_NUMBER = 9999999;\nexport const DEFAULT_MULTIPLE_OF = 1;\nexport const DEFAULT_SEED = 123;\n\nexport const DEFAULT_API_DIR_NAME = 'api';\nexport const OPENAPI_TYPES_FILE_NAME = 'openapi-types.d.ts';\nexport const DEFAULT_TYPES_FILE_NAME = 'types';\n\nexport const ADAPTERS = {\n MSW: 'msw',\n};\n\n// Raw template strings\nexport const GENERATED_COMMENT_FLAG = '@generated by openapi-mock-gen';\n\nexport const GENERATED_COMMENT = `/**\n * ${GENERATED_COMMENT_FLAG}\n * If you want to make changes, please remove this entire comment and edit the file directly.\n */`;\n\nexport const DISABLE_LINTING = '/* eslint-disable */';\n\nexport const DISABLE_TS_CHECK = '/* tslint:disable */\\n// @ts-nocheck';\n\nexport const DISABLE_ALL_CHECK = `\n${DISABLE_LINTING}\n${DISABLE_TS_CHECK}`;\n\nexport const HTTP_METHODS_TYPE = `\ntype HttpMethods = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head';\n`;\n\nexport const FAKER_MAP_TYPE = `\ntype FakerMap = Record<string, string | (() => unknown)>;\n`;\n\nexport const ENDPOINT_CONFIG_TYPE = `\ninterface EndpointConfig {\n arrayLength: number;\n useExample: boolean;\n dynamic: boolean;\n fakerMap?: FakerMap;\n}`;\n\nexport const CONFIG_TYPE = `\ninterface Config extends EndpointConfig {\n specPath: string;\n outputDir: string;\n baseUrl: string;\n language: 'typescript' | 'javascript';\n endpoints?: Record<string, { [key in HttpMethods]?: Partial<EndpointConfig> }>;\n}`;\n\nexport const FAKER_SEED = 'faker.seed({seed});';\n\nexport const ESM_IMPORT = \"import {module} from '{modulePath}'\";\nexport const CJS_IMPORT = \"const {module} = require('{modulePath}')\";\n\nexport const ESM_EXPORT = 'export default {exportName}';\nexport const CJS_EXPORT = 'module.exports = {exportName}';\n\nexport const ESM_EXPORT_NAMED = 'export { {exportName} }';\nexport const CJS_EXPORT_NAMED = 'module.exports = { {exportName} }';\n\nexport const CONFIG_BODY = `\nconst config{configType} = {\n specPath: '{specPath}',\n outputDir: '{outputDir}',\n baseUrl: '{baseUrl}',\n language: '{language}',\n\n // Global settings\n arrayLength: {arrayLength},\n useExample: {useExample},\n dynamic: {dynamic},\n\n // Use this to customize how specific fields are generated.\n // The key can be a string or regex. The value can be a fixed value, a faker expression string, or a function.\n // Functions are executed in a sandboxed Node.js environment, so you can access native APIs like Math, Date, etc, plus the faker instance.\n // Other dependencies such as self-defined/third-party modules, etc, are not allowed.\n // fakerMap: {\n // // Eg. by key, using a faker expression string\n // id: 'faker.string.uuid()',\n // // Eg. by key, using a function with faker (requires importing faker in your config)\n // name: () => faker.person.fullName(),\n // // Eg. by regex, using a fixed value\n // '(?:^|_)image|img|picture|photo(?:_|$)': 'https://picsum.photos/200',\n // // Eg. using a custom function with native APIs\n // user: () => (Math.random() > 0.5 ? 'John Doe' : 'Jane Doe'),\n // },\n\n // If you wish to customize the config for a specific endpoint, you can do so here.\n // The config for a specific endpoint will override the global config.\n // endpoints: {\n // '/users': {\n // 'get': {\n // arrayLength: 10,\n // useExample: false,\n // fakerMap: {\n // // This will override the global fakerMap for this endpoint\n // name: () => 'John Doe',\n // },\n // }\n // }\n // }\n};`;\n\nexport const MOCK_DATA_BODY = `\nconst mockData = (){mockDataType} => ({\n {mockData}\n});`;\n\nexport const MANIFEST_BODY = `\n{\n \"manifest\": {manifestData}\n}`;\n","import Enquirer from 'enquirer';\nimport ora, { Color, Ora } from 'ora';\n\nimport { GroupedEndpoints, ApiInfoByEndpoints, PromptConfig } from './types';\nimport { EXPORT_LANGUAGE } from './constants';\n\n// @ts-expect-error Enquirer does have a MultiSelect type, but it's not properly exported.\nconst { MultiSelect, Input, prompt } = Enquirer;\n\nconst ALL = 'All';\n\nasync function selectApiGroups(groupedEndpoints: GroupedEndpoints): Promise<string[]> {\n const allChoices = Object.keys(groupedEndpoints);\n const multiSelect = new MultiSelect({\n name: 'apiGroups',\n message: 'Select the API groups you want to mock (press space to select, enter to confirm)',\n choices: [ALL, ...allChoices],\n validate(value: string[]) {\n if (value.length === 0) return 'Please select at least one API group.';\n return true;\n },\n });\n\n return multiSelect.run();\n}\n\nasync function selectIndividualEndpoints(choices: string[]): Promise<string[]> {\n const multiSelect = new MultiSelect({\n name: 'apiEndpoints',\n message: 'Select the API endpoints you want to mock (press space to select, enter to confirm)',\n initial: [ALL],\n choices: [ALL, ...new Set(choices)],\n validate(value: string[]) {\n if (value.length === 0) return 'Please select at least one API endpoint.';\n return true;\n },\n });\n return multiSelect.run();\n}\n\nexport async function promptForEndpoints(groupedEndpoints: GroupedEndpoints): Promise<ApiInfoByEndpoints[]> {\n const groupNames = Object.keys(groupedEndpoints);\n let selectedGroups = await selectApiGroups(groupedEndpoints);\n\n if (selectedGroups.includes(ALL) && selectedGroups.length === 1) {\n selectedGroups = groupNames;\n }\n\n const availableEndpoints = selectedGroups.flatMap((group) => groupedEndpoints[group] || []);\n const availableEndpointPaths = availableEndpoints.map(\n (endpoint) => `${endpoint.method.toUpperCase()} ${endpoint.path}`\n );\n\n const selectedEndpointPaths = await selectIndividualEndpoints(availableEndpointPaths);\n\n if (selectedEndpointPaths.includes(ALL) && selectedEndpointPaths.length === 1) {\n return availableEndpoints;\n }\n\n return availableEndpoints.filter((endpoint) =>\n selectedEndpointPaths.includes(`${endpoint.method.toUpperCase()} ${endpoint.path}`)\n );\n}\n\nexport async function inputSpecPath(): Promise<string> {\n const input = new Input({\n name: 'specPath',\n message: 'Please enter the URL or local path to your OpenAPI specification:',\n validate(value: string) {\n if (!value) return 'The spec path cannot be empty.';\n return true;\n },\n });\n return input.run();\n}\n\nexport async function promptForBaseUrl(): Promise<string> {\n const input = new Input({\n name: 'baseUrl',\n message: 'Please enter the base URL for your API:',\n initial: 'http://localhost:3000',\n });\n return input.run();\n}\n\nexport async function promptForGlobalConfig(): Promise<PromptConfig> {\n const promtSequence = [\n {\n type: 'confirm',\n name: 'useTypeScript',\n message: 'Do you want to generate files in TypeScript?',\n initial: true,\n },\n {\n type: 'input',\n name: 'arrayLength',\n message: 'Default array length for mock data:',\n initial: '5',\n validate(value: string) {\n if (!value) return 'The array length cannot be empty.';\n if (Number.isNaN(Number(value))) return 'The array length must be a number.';\n return true;\n },\n },\n {\n type: 'confirm',\n name: 'useExample',\n message: 'Use \"example\" if provided in spec?',\n initial: 'true',\n },\n {\n type: 'confirm',\n name: 'dynamic',\n message: 'Generate dynamic data for each request?',\n initial: 'true',\n },\n ];\n\n const { useTypeScript, ...rest }: Omit<PromptConfig, 'language'> & { useTypeScript: boolean } =\n await prompt(promtSequence);\n\n return {\n language: useTypeScript ? EXPORT_LANGUAGE.TS : EXPORT_LANGUAGE.JS,\n ...rest,\n };\n}\n\nlet spinner: Ora;\n\nexport const getSpinner = (text?: string, color?: Color) => {\n if (!spinner) {\n spinner = ora();\n }\n\n if (text) {\n spinner.text = text;\n }\n if (color) {\n spinner.color = color;\n }\n\n return spinner;\n};\n","import fs from 'fs';\nimport path from 'path';\nimport vm from 'vm';\n\nimport pc from 'picocolors';\nimport { faker } from '@faker-js/faker';\nimport { OpenAPIV3 } from 'openapi-types';\n\nimport { JS_EXTENSION, TS_EXTENSION, CONFIG_FILE_NAME, OPENAPI_TYPES_FILE_NAME, EXPORT_LANGUAGE } from './constants';\nimport { getSpinner } from './prompts';\nimport {\n Config,\n Language,\n MockGeneratorInfo,\n SchemaWithNullablePaths,\n CollectedErrorObject,\n HttpMethods,\n} from './types';\n\nexport const slashToKebabCase = (str: string) => str.split('/').filter(Boolean).join('-');\n\nexport const toCamelCase = (str: string) =>\n str.toLowerCase().replace(/[^a-zA-Z0-9]+(.)?/g, (_, chr) => (chr ? chr.toUpperCase() : ''));\n\nexport const replacePathParamsWithBy = (path: string) => path.replace(/{(\\w+)}/g, 'By-$1');\n\nexport const getExpressLikePath = (path: string) => path.replace(/{(\\w+)}/g, ':$1');\n\nexport const interpolateString = (str: string, data: Record<string, string | number | boolean>) =>\n str.replace(/{(\\w+)}/g, (_, key) => String(data[key] ?? ''));\n\nexport const executeCode = (code: string, sandbox: Record<string, unknown> = { faker }) => {\n try {\n const script = new vm.Script(`(${code})`);\n return script.runInNewContext(sandbox);\n } catch (error) {\n console.error(error);\n console.error(\n 'An error occurred while generating the static mock data, please note that external dependencies are not supported except for faker.'\n );\n console.error('Falling back to the dynamic mock data generation instead.');\n\n return code;\n }\n};\n\nexport async function getIsESM() {\n try {\n const packageJsonPath = path.join(process.cwd(), 'package.json');\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n const isTsConfig = fs.existsSync(path.join(process.cwd(), 'tsconfig.json'));\n return packageJson.type === 'module' || isTsConfig;\n } catch {\n // If package.json doesn't exist or is invalid, default to CommonJS\n return false;\n }\n}\n\nexport const handleCleanGeneration = async (outputDir: string) => {\n const spinner = getSpinner();\n\n const mockDir = path.join(process.cwd(), outputDir);\n if (!fs.existsSync(mockDir)) {\n spinner.fail(`${mockDir} not found`);\n return;\n }\n\n fs.rmSync(mockDir, { recursive: true });\n};\n\nexport const handleGenerateOpenApiTypes = async (config: Config): Promise<void> => {\n const { specPath, outputDir } = config;\n const spinner = getSpinner('Generating OpenAPI types with openapi-typescript...', 'yellow').start();\n\n try {\n const { default: openapiTS, astToString } = await import('openapi-typescript');\n const ast = await openapiTS(specPath);\n const code = astToString(ast);\n const outPutPath = path.join(process.cwd(), outputDir);\n if (!fs.existsSync(outPutPath)) {\n fs.mkdirSync(outPutPath, { recursive: true });\n }\n\n fs.writeFileSync(path.join(outPutPath, OPENAPI_TYPES_FILE_NAME), code);\n\n spinner.succeed(pc.bold('OpenAPI types generated successfully'));\n } catch (error) {\n console.error(error);\n spinner.fail('Something went wrong while generating OpenAPI types using openapi-typescript.');\n throw error;\n }\n};\n\nexport const getExtension = (language: Language) => (language === EXPORT_LANGUAGE.TS ? TS_EXTENSION : JS_EXTENSION);\n\nexport const fileNames = (language: Language) => ({\n getConfigFileName: (configFileName: string | undefined = CONFIG_FILE_NAME) =>\n `${configFileName}${getExtension(language)}`,\n getMockFileName: (path: string) => `${slashToKebabCase(path)}${getExtension(language)}`,\n});\n\nexport const collectedErrors = new Map<`${HttpMethods}-${string}`, CollectedErrorObject[]>();\n\nexport const validateExampleAgainstSchema = (\n info: Omit<MockGeneratorInfo, 'schema'> & { schema: SchemaWithNullablePaths | OpenAPIV3.SchemaObject },\n key?: string\n) => {\n const { schema, path: endpointPath, method: endpointMethod } = info;\n\n const { type: schemaType } = schema;\n\n const getExampleType = (value: unknown) => {\n if (Array.isArray(value)) return 'array';\n return typeof value;\n };\n\n if (!schemaType) {\n return;\n }\n\n const checkExampleType = (example: unknown) => {\n let typeMismatch = false;\n const exampleType = getExampleType(example);\n\n switch (schemaType) {\n case 'string':\n if (exampleType !== 'string') typeMismatch = true;\n break;\n case 'number':\n case 'integer':\n if (exampleType !== 'number') typeMismatch = true;\n break;\n case 'boolean':\n if (exampleType !== 'boolean') typeMismatch = true;\n break;\n case 'array':\n if (exampleType !== 'array') typeMismatch = true;\n break;\n case 'object':\n if (exampleType !== 'object') typeMismatch = true;\n break;\n default:\n typeMismatch = true;\n break;\n }\n\n return { typeMismatch, exampleType };\n };\n\n const example = 'exampleObject' in schema ? schema.exampleObject : schema.example;\n if (example === undefined) return;\n\n const { typeMismatch, exampleType } = checkExampleType(example);\n if (!typeMismatch) return;\n\n const collectedErrorsArray = collectedErrors.get(`${endpointMethod}-${endpointPath}`);\n\n if (!collectedErrorsArray) {\n collectedErrors.set(`${endpointMethod}-${endpointPath}`, [\n {\n method: endpointMethod,\n path: endpointPath,\n key: key ?? 'root',\n schemaType,\n exampleType,\n },\n ]);\n } else {\n collectedErrorsArray.push({\n method: endpointMethod,\n path: endpointPath,\n key: key ?? 'root',\n schemaType,\n exampleType,\n });\n }\n};\n\nexport const handlePrintMismatchedErrors = async () => {\n const spinner = getSpinner();\n\n const errors = Object.fromEntries(collectedErrors.entries());\n\n const pathKeys = Object.keys(errors).sort();\n if (pathKeys.length === 0) {\n return;\n }\n\n spinner.fail(pc.redBright(pc.bold(`We've found the following mismatched exmaples in the schema: `)));\n pathKeys.forEach((pathKey) => {\n const errorInfo = errors[pathKey];\n const { method, path } = errorInfo[0];\n\n errorInfo.forEach((error) => {\n const { key, schemaType, exampleType } = error;\n\n console.error(\n `Found ${pc.cyan(pc.bold(key))} in (${method.toUpperCase()}) - ${pc.cyan(pc.bold(path))} with schema type ${pc.bold(pc.redBright(schemaType))} but received example in type: ${pc.bold(pc.redBright(exampleType))}`\n );\n });\n });\n};\n","import { interpolateString } from '@/lib/shared/utils';\n\nimport { Language, ModuleSystem, Config } from './types';\nimport {\n DEFAULT_SEED,\n EXPORT_LANGUAGE,\n OPENAPI_TYPES_FILE_NAME,\n FAKER_SEED,\n ESM_IMPORT,\n CJS_IMPORT,\n ESM_EXPORT,\n CJS_EXPORT,\n ESM_EXPORT_NAMED,\n CJS_EXPORT_NAMED,\n GENERATED_COMMENT,\n DISABLE_LINTING,\n DISABLE_ALL_CHECK,\n HTTP_METHODS_TYPE,\n FAKER_MAP_TYPE,\n ENDPOINT_CONFIG_TYPE,\n CONFIG_TYPE,\n MOCK_DATA_BODY,\n MANIFEST_BODY,\n CONFIG_BODY,\n} from './constants';\n\nexport interface TemplateContext {\n language: Language;\n moduleSystem: ModuleSystem;\n}\n\nexport const createTemplate = (template: string) => ({\n render: (data: Record<string, string | number | boolean>): string => {\n try {\n return interpolateString(template, data);\n } catch (error) {\n throw new Error(`Template rendering failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n },\n});\n\nexport const MODULE_IMPORT_TEMPLATE = {\n cjs: createTemplate(CJS_IMPORT),\n esm: createTemplate(ESM_IMPORT),\n};\n\nexport const MODULE_EXPORT_TEMPLATE = {\n cjs: createTemplate(CJS_EXPORT),\n cjsNamed: createTemplate(CJS_EXPORT_NAMED),\n esm: createTemplate(ESM_EXPORT),\n esmNamed: createTemplate(ESM_EXPORT_NAMED),\n};\n\nconst fakerImport = (context: TemplateContext): string => {\n const { moduleSystem } = context;\n const template = moduleSystem === 'esm' ? MODULE_IMPORT_TEMPLATE.esm : MODULE_IMPORT_TEMPLATE.cjs;\n\n return template.render({\n module: '{ faker }',\n modulePath: '@faker-js/faker',\n });\n};\n\nconst openapiTypesImport = (context: TemplateContext): string => {\n const { moduleSystem } = context;\n const template = moduleSystem === 'esm' ? MODULE_IMPORT_TEMPLATE.esm : MODULE_IMPORT_TEMPLATE.cjs;\n\n return template.render({\n module: 'type { paths }',\n modulePath: `../${OPENAPI_TYPES_FILE_NAME}`,\n });\n};\n\n// Config file\nconst configHeader = (context: TemplateContext): string => {\n const parts = [GENERATED_COMMENT];\n\n if (context.language === EXPORT_LANGUAGE.TS) {\n parts.push(DISABLE_LINTING);\n parts.push(HTTP_METHODS_TYPE, FAKER_MAP_TYPE, ENDPOINT_CONFIG_TYPE, CONFIG_TYPE);\n } else {\n parts.push(DISABLE_ALL_CHECK);\n }\n\n return parts.join('\\n\\n');\n};\n\nconst configFooter = (context: TemplateContext): string =>\n MODULE_EXPORT_TEMPLATE[context.moduleSystem].render({ exportName: 'config' });\n\n// Mock data file\nconst mockDataHeader = (usesFaker: boolean, shouldImportPaths: boolean, context: TemplateContext): string => `\n${GENERATED_COMMENT}\n\n${context.language === EXPORT_LANGUAGE.TS ? DISABLE_LINTING : DISABLE_ALL_CHECK}\n\n${usesFaker ? fakerImport(context) : ''}\n\n${context.language === EXPORT_LANGUAGE.TS && shouldImportPaths ? openapiTypesImport(context) : ''}\n\n${usesFaker ? createTemplate(FAKER_SEED).render({ seed: DEFAULT_SEED }) : ''}\n`;\n\nconst mockDataFooter = (context: TemplateContext): string =>\n MODULE_EXPORT_TEMPLATE[context.moduleSystem].render({ exportName: 'mockData' });\n\nexport class TemplateGenerator {\n private readonly context: TemplateContext;\n\n constructor(language: Language, isESM: boolean) {\n this.context = {\n language,\n moduleSystem: isESM ? 'esm' : 'cjs',\n };\n }\n\n generateConfig(data: Omit<Config, 'fakerMap' | 'endpoints'>): string {\n const header = configHeader(this.context);\n const configType = this.context.language === EXPORT_LANGUAGE.TS ? ': Config' : '';\n const body = createTemplate(CONFIG_BODY).render({ ...data, configType });\n const footer = configFooter(this.context);\n\n return [header, body, footer].join('\\n\\n');\n }\n\n generateMockData(usesFaker: boolean, mockData: string, mockDataType?: string): string {\n const shouldImportPaths = mockDataType?.includes('paths') || false;\n\n const header = mockDataHeader(usesFaker, shouldImportPaths, this.context);\n const body = createTemplate(MOCK_DATA_BODY).render({\n mockData: mockData,\n mockDataType: this.context.language === EXPORT_LANGUAGE.TS && mockDataType ? `: ${mockDataType}` : '',\n });\n const footer = mockDataFooter(this.context);\n\n return [header, body, footer].join('\\n\\n');\n }\n\n generateManifest(manifestData: string): string {\n return createTemplate(MANIFEST_BODY).render({ manifestData });\n }\n\n getContext(): Readonly<TemplateContext> {\n return { ...this.context };\n }\n}\n","import fs from 'fs';\nimport path from 'path';\n\nimport pc from 'picocolors';\nimport prettier from 'prettier';\n\nimport { fileNames } from './utils';\nimport { getSpinner } from './prompts';\nimport { OrganizedApiData, Config, GeneratedMocks, Language, PrettifyParser } from './types';\nimport { DEFAULT_API_DIR_NAME, EXPORT_LANGUAGE, MANIFEST_FILE_NAME, GENERATED_COMMENT_FLAG } from './constants';\nimport { TemplateGenerator } from './templates';\n\nexport const shouldOverwriteFile = (filePath: string) =>\n !fs.existsSync(filePath) || fs.readFileSync(filePath, 'utf-8').includes(GENERATED_COMMENT_FLAG);\n\nexport async function writeFileWithPrettify(filePath: string, content: string, parser: PrettifyParser = 'typescript') {\n const directory = path.dirname(filePath);\n fs.mkdirSync(directory, { recursive: true });\n\n try {\n const formattedContent = await prettier.format(content, { parser });\n fs.writeFileSync(filePath, formattedContent);\n } catch (error) {\n console.error(error);\n fs.writeFileSync(filePath, content);\n }\n}\n\nexport function generateMockDataFileContent(\n mockCodeArray: string[] | object[],\n templateGenerator: TemplateGenerator,\n endpoint: OrganizedApiData,\n language: Language\n): string {\n const isDynamic = mockCodeArray.every((mockCode) => typeof mockCode === 'string');\n const { path: apiPath, method, responses } = endpoint;\n\n const mockDataProperties: string[] = [];\n const mockDataTypeProperties: string[] = [];\n\n mockCodeArray.forEach((_, i) => {\n const { code, response } = responses[i];\n const data = isDynamic ? mockCodeArray[i] : JSON.stringify(mockCodeArray[i], null, 2);\n mockDataProperties.push(`'${code}': ${data}`);\n\n if (language === EXPORT_LANGUAGE.TS) {\n const hasContent = response?.['application/json'];\n if (hasContent) {\n const typeString = `paths['${apiPath}']['${method.toLowerCase()}']['responses']['${code}']['content']['application/json']`;\n mockDataTypeProperties.push(`'${code}': ${typeString}`);\n } else {\n mockDataTypeProperties.push(`'${code}': null`);\n }\n }\n });\n\n const mockDataString = mockDataProperties.join(',\\n');\n const mockDataTypeString = language === EXPORT_LANGUAGE.TS ? `{${mockDataTypeProperties.join(',')}}` : undefined;\n const usesFaker = mockDataString.includes('faker.');\n\n return templateGenerator.generateMockData(usesFaker, mockDataString, mockDataTypeString);\n}\n\nexport async function writeMockDataFiles({\n config,\n endpoints,\n generatedMocks,\n templateGenerator,\n}: {\n config: Config;\n endpoints: OrganizedApiData[];\n generatedMocks: GeneratedMocks;\n templateGenerator: TemplateGenerator;\n}) {\n const spinner = getSpinner();\n const { outputDir, language } = config;\n\n const writePromises = endpoints.map((endpoint, i) => {\n const mockContent = generateMockDataFileContent(generatedMocks[i], templateGenerator, endpoint, language);\n const filePath = path.join(outputDir, DEFAULT_API_DIR_NAME, fileNames(language).getMockFileName(endpoint.path));\n\n if (!shouldOverwriteFile(filePath)) {\n return Promise.resolve();\n }\n\n return writeFileWithPrettify(filePath, mockContent);\n });\n\n await Promise.all(writePromises);\n spinner.succeed(pc.bold(`Generated ${endpoints.length} mock data files.`));\n}\n\nexport async function writeManifestFile({\n config,\n organizedApiData,\n templateGenerator,\n}: {\n config: Config;\n organizedApiData: OrganizedApiData[];\n templateGenerator: TemplateGenerator;\n}) {\n const spinner = getSpinner();\n const { outputDir, language } = config;\n const { getMockFileName } = fileNames(language);\n\n const newManifestEntries = organizedApiData.map(\n ({ method, path: apiPath, operationId, summary, description, responses }) => ({\n method: method.toUpperCase(),\n path: apiPath,\n operationId,\n summary,\n description,\n mockFile: getMockFileName(apiPath),\n nullablePaths: responses.flatMap((r) => r.response?.['application/json']?.['x-nullable-paths'] ?? []),\n })\n );\n\n const manifestPath = path.join(outputDir, MANIFEST_FILE_NAME);\n\n let finalManifestData = newManifestEntries;\n\n if (fs.existsSync(manifestPath)) {\n try {\n const existingManifestContent = fs.readFileSync(manifestPath, 'utf-8');\n const existingManifest = JSON.parse(existingManifestContent);\n const existingData = existingManifest.manifest;\n if (!Array.isArray(existingData)) throw new Error();\n\n const newEntriesMap = new Map(\n newManifestEntries.map(({ method, path }) => [`${method}-${path}`, { method, path }])\n );\n\n const oldEntriesToKeep = existingData.filter(\n ({ path, method }) => path && method && !newEntriesMap.has(`${method}-${path}`)\n );\n\n finalManifestData = [...oldEntriesToKeep, ...newManifestEntries];\n } catch {\n spinner.fail(`The existed ${MANIFEST_FILE_NAME} is corrupted, falling back to overwriting the old file content.`);\n }\n }\n\n const manifestContent = templateGenerator.generateManifest(JSON.stringify(finalManifestData, null, 2));\n await writeFileWithPrettify(manifestPath, manifestContent, 'json');\n}\n","import fs from 'fs';\nimport path from 'path';\n\nimport pc from 'picocolors';\nimport { createJiti } from 'jiti';\n\nimport { DEFAULT_CONFIG, CONFIG_FILE_NAME, JS_EXTENSION, TS_EXTENSION } from '@/lib/shared/constants';\nimport type { Config, InitCLIOptions } from '@/lib/shared/types';\nimport { TemplateGenerator } from '@/lib/shared/templates';\nimport { getIsESM, fileNames } from '@/lib/shared/utils';\nimport { writeFileWithPrettify } from '@/lib/shared/writer';\nimport { inputSpecPath, promptForBaseUrl, promptForGlobalConfig, getSpinner } from '@/lib/shared/prompts';\n\nconst jiti = createJiti(import.meta.url);\n\nexport async function loadConfigFromFile(): Promise<Config> {\n const spinner = getSpinner();\n spinner.info(pc.bold('Loading config file...'));\n\n const tsConfigPath = path.join(process.cwd(), CONFIG_FILE_NAME + TS_EXTENSION);\n const jsConfigPath = path.join(process.cwd(), CONFIG_FILE_NAME + JS_EXTENSION);\n\n let configPath = '';\n\n try {\n if (fs.existsSync(tsConfigPath)) {\n configPath = tsConfigPath;\n } else if (fs.existsSync(jsConfigPath)) {\n configPath = jsConfigPath;\n } else {\n throw new Error(\n `Config file not found at root directory, please run \"openapi-mock-gen init\" to create a new config file.`\n );\n }\n\n const configModule: Config = await jiti.import(configPath, { default: true });\n\n return {\n ...DEFAULT_CONFIG,\n ...(configModule ?? {}),\n };\n } catch (error) {\n spinner.fail(`Error loading config file at ${configPath}`);\n throw error;\n }\n}\n\nexport async function initializeConfig(cliOptions: InitCLIOptions): Promise<Config> {\n const spinner = getSpinner();\n\n const specPath = cliOptions.specPath ?? (await inputSpecPath());\n const baseUrl = cliOptions.baseUrl ?? (await promptForBaseUrl());\n const outputDir = cliOptions.outputDir ?? DEFAULT_CONFIG.outputDir;\n\n spinner.succeed(pc.bold('Prompting for a new config…'));\n\n const { language, ...rest } = await promptForGlobalConfig();\n const config = { language, specPath, baseUrl, outputDir, ...rest };\n\n const isESM = await getIsESM();\n const templateGenerator = new TemplateGenerator(language, isESM);\n\n const configFileName = fileNames(language).getConfigFileName();\n const configPath = path.join(process.cwd(), configFileName);\n\n const configContent = templateGenerator.generateConfig(config);\n await writeFileWithPrettify(configPath, configContent);\n spinner.succeed(pc.bold(`${configFileName} generated successfully.`));\n\n return {\n ...config,\n fakerMap: {},\n endpoints: {},\n };\n}\n","import pc from 'picocolors';\nimport { OpenAPIV3 } from 'openapi-types';\nimport SwaggerParser from '@apidevtools/swagger-parser';\n\nimport { Config } from '@/lib/shared/types';\nimport { getSpinner } from '@/lib/shared/prompts';\n\nasync function bundleSpec(specPathOrUrl: string): Promise<OpenAPIV3.Document> {\n const doc = await SwaggerParser.bundle(specPathOrUrl);\n\n if (!('openapi' in doc) || !doc.openapi.startsWith('3')) {\n throw new Error('Invalid OpenAPI version. Only OpenAPI v3 is supported.');\n }\n\n return doc as OpenAPIV3.Document;\n}\n\nexport async function loadSpec(config: Config): Promise<OpenAPIV3.Document> {\n const { specPath } = config;\n const spinner = getSpinner(`Loading spec from: ${specPath}`, 'yellow').start();\n const document = await bundleSpec(specPath);\n\n spinner.succeed(pc.bold('Spec loaded successfully.'));\n\n return document;\n}\n","import merge from 'lodash.merge';\nimport { OpenAPIV3 } from 'openapi-types';\n\nimport { executeCode, interpolateString, validateExampleAgainstSchema } from '@/lib/shared/utils';\nimport type {\n EndpointConfig,\n FakerMap,\n MockGeneratorInfo,\n OrganizedApiData,\n Config,\n GeneratedMocks,\n} from '@/lib/shared/types';\nimport {\n DEFAULT_CONFIG,\n DEFAULT_MIN_NUMBER,\n DEFAULT_MAX_NUMBER,\n DEFAULT_MULTIPLE_OF,\n DEFAULT_SEED,\n} from '@/lib/shared/constants';\n\nconst FAKER_HELPERS: Record<string, string> = {\n // Date and Time\n date: 'faker.date.past().toISOString().substring(0, 10)',\n dateTime: 'faker.date.past()',\n time: 'new Date().toISOString().substring(11, 16)',\n\n // Internet\n email: 'faker.internet.email()',\n hostname: 'faker.internet.domainName()',\n ipv4: 'faker.internet.ip()',\n ipv6: 'faker.internet.ipv6()',\n url: 'faker.internet.url()',\n token: 'faker.internet.jwt()',\n\n // Location\n city: 'faker.location.city()',\n country: 'faker.location.country()',\n latitude: 'faker.location.latitude()',\n longitude: 'faker.location.longitude()',\n state: 'faker.location.state()',\n street: 'faker.location.streetAddress()',\n zip: 'faker.location.zipCode()',\n\n // Names and IDs\n name: 'faker.person.fullName()',\n uuid: 'faker.string.uuid()',\n\n // Numbers\n integer: 'faker.number.int()',\n number: 'faker.number.int({ min: {min}, max: {max}, multipleOf: {multipleOf} })',\n float: 'faker.number.float({ fractionDigits: {fractionDigits} })',\n\n // Strings\n alpha: 'faker.string.alpha({ length: { min: {minLength}, max: {maxLength} } })',\n alphaMax: 'faker.string.alpha({ length: { max: {maxLength} } })',\n alphaMin: 'faker.string.alpha({ length: { min: {minLength} } })',\n string: 'faker.lorem.words()',\n\n // Text\n paragraph: 'faker.lorem.paragraph()',\n sentence: 'faker.lorem.sentence()',\n\n // Utilities\n arrayElement: 'faker.helpers.arrayElement({arrayElement})',\n fromRegExp: 'faker.helpers.fromRegExp({pattern})',\n boolean: 'faker.datatype.boolean()',\n image: 'faker.image.urlLoremFlickr()',\n phone: 'faker.phone.number()',\n avatar: 'faker.image.avatar()',\n};\n\n// see: https://json-schema.org/understanding-json-schema/reference/type#built-in-formats\nconst JSON_SCHEMA_STANDARD_FORMATS: Record<string, string> = {\n date: FAKER_HELPERS.date,\n time: FAKER_HELPERS.time,\n 'date-time': FAKER_HELPERS.dateTime,\n\n email: FAKER_HELPERS.email,\n 'idn-email': FAKER_HELPERS.email,\n\n hostname: FAKER_HELPERS.hostname,\n 'idn-hostname': FAKER_HELPERS.hostname,\n\n ipv4: FAKER_HELPERS.ipv4,\n ipv6: FAKER_HELPERS.ipv6,\n\n uuid: FAKER_HELPERS.uuid,\n uri: FAKER_HELPERS.url,\n iri: FAKER_HELPERS.url,\n 'uri-reference': FAKER_HELPERS.url,\n 'iri-reference': FAKER_HELPERS.url,\n 'uri-template': FAKER_HELPERS.url,\n\n regex: FAKER_HELPERS.fromRegExp,\n};\n\nconst HEURISTIC_STRING_KEY_MAP: Record<string, string> = {\n // General identifiers\n '(?:^|_)id$': FAKER_HELPERS.uuid,\n '(?:^|_)uuid(?:_|$)': FAKER_HELPERS.uuid,\n '(?:^|_)token(?:_|$)': FAKER_HELPERS.token,\n\n // Timestamps\n '.+_at$': FAKER_HELPERS.dateTime,\n '(?:^|_)timestamp(?:_|$)': FAKER_HELPERS.dateTime,\n\n // locations\n '(?:^|_)street(?:_|$)': FAKER_HELPERS.street,\n '(?:^|_)city(?:_|$)': FAKER_HELPERS.city,\n '(?:^|_)state(?:_|$)': FAKER_HELPERS.state,\n '(?:^|_)zip(?:_|$)': FAKER_HELPERS.zip,\n '(?:^|_)country(?:_|$)': FAKER_HELPERS.country,\n '^postal_code$': FAKER_HELPERS.zip,\n '(?:^|_)latitude(?:_|$)': FAKER_HELPERS.latitude,\n '(?:^|_)longitude(?:_|$)': FAKER_HELPERS.longitude,\n\n // phone / contact\n '(?:^|_)phone(?:_|$)': FAKER_HELPERS.phone,\n '(?:^|_)mobile(?:_|$)': FAKER_HELPERS.phone,\n\n // personal info\n '(?:^|_)email(?:_|$)': FAKER_HELPERS.email,\n '.*name$': FAKER_HELPERS.name,\n\n // urls\n '(?:^|_)ur[li]$': FAKER_HELPERS.url,\n '\\\\b(profile|user)_(image|img|photo|picture)\\\\b|(?:^|_)avatar(?:_|$)': FAKER_HELPERS.avatar,\n '(?:^|_)(photo|image|picture|img)(?:_|$)': FAKER_HELPERS.image,\n\n // content / text\n '(?:^|_)title(?:_|$)': FAKER_HELPERS.sentence,\n '(?:^|_)description(?:_|$)': FAKER_HELPERS.paragraph,\n '(?:^|_)content(?:_|$)': FAKER_HELPERS.paragraph,\n '(?:^|_)text(?:_|$)': FAKER_HELPERS.paragraph,\n '(?:^|_)paragraph(?:_|$)': FAKER_HELPERS.paragraph,\n '(?:^|_)comments?(?:_|$)': FAKER_HELPERS.sentence,\n '(?:^|_)message(?:_|$)': FAKER_HELPERS.sentence,\n '(?:^|_)summary(?:_|$)': FAKER_HELPERS.paragraph,\n};\n\nconst moderateScore = interpolateString(FAKER_HELPERS.number, { min: 1, max: 100, multipleOf: DEFAULT_MULTIPLE_OF });\nconst moderateNumber = interpolateString(FAKER_HELPERS.number, { min: 1, max: 1000, multipleOf: DEFAULT_MULTIPLE_OF });\nconst moderatePrice = interpolateString(FAKER_HELPERS.number, { min: 1, max: 100000, multipleOf: DEFAULT_MULTIPLE_OF });\nconst moderateRating = interpolateString(FAKER_HELPERS.number, { min: 1, max: 5, multipleOf: DEFAULT_MULTIPLE_OF });\nconst moderateFloat = interpolateString(FAKER_HELPERS.float, { fractionDigits: 1 });\n\nconst lcg = `(() => {\n const seed = (Math.random() * 9301 + 49297) % 233280;\n const random = seed / 233280;\n return {min} + Math.floor(random * {max});\n})()`;\n\nconst HEURISTIC_NUMBER_KEY_MAP: Record<string, string> = {\n // Linear Congruential Generator (LCG) formulas for stateless random unique(nearly) numbers\n '^id$': interpolateString(lcg, { min: DEFAULT_MIN_NUMBER, max: DEFAULT_MAX_NUMBER, seed: DEFAULT_SEED }),\n '.+_id$': interpolateString(lcg, { min: DEFAULT_MIN_NUMBER, max: DEFAULT_MAX_NUMBER, seed: DEFAULT_SEED }),\n\n // age / time\n '(?:^|_)age(?:_|$)': interpolateString(FAKER_HELPERS.number, { min: 20, max: 80, multipleOf: DEFAULT_MULTIPLE_OF }),\n '(?:^|_)year(?:_|$)': interpolateString(FAKER_HELPERS.number, {\n min: 1900,\n max: new Date().getFullYear(),\n multipleOf: DEFAULT_MULTIPLE_OF,\n }),\n '(?:^|_)month(?:_|$)': interpolateString(FAKER_HELPERS.number, { min: 1, max: 12, multipleOf: DEFAULT_MULTIPLE_OF }),\n '(?:^|_)day(?:_|$)': interpolateString(FAKER_HELPERS.number, { min: 1, max: 31, multipleOf: DEFAULT_MULTIPLE_OF }),\n\n // quatities/counts\n '(?:^|_)counts?(?:_|$)': moderateNumber,\n '(?:^|_)quantity(?:_|$)': moderateNumber,\n '(?:^|_)amount(?:_|$)': moderateNumber,\n '(?:^|_)total(?:_|$)': moderateNumber,\n\n // financial\n '(?:^|_)price(?:_|$)': moderatePrice,\n '(?:^|_)discounts?(?:_|$)': moderateFloat,\n '(?:^|_)tax(?:_|$)': moderatePrice,\n '(?:^|_)fee(?:_|$)': moderatePrice,\n\n // dimensions\n '(?:^|_)size(?:_|$)': moderateNumber,\n '(?:^|_)length(?:_|$)': moderateNumber,\n '(?:^|_)width(?:_|$)': moderateNumber,\n '(?:^|_)height(?:_|$)': moderateNumber,\n '(?:^|_)weight(?:_|$)': moderateNumber,\n\n // ratings\n '(?:^|_)ratings?(?:_|$)': moderateRating,\n '(?:^|_)stars(?:_|$)': moderateRating,\n '(?:^|_)scores?(?:_|$)': moderateScore,\n};\n\nconst transformNumberBasedOnFormat = (schema: OpenAPIV3.NonArraySchemaObject, key?: string) => {\n const { minimum, maximum, multipleOf, exclusiveMinimum, exclusiveMaximum } = schema;\n\n if (key) {\n const heuristicKey = Object.keys(HEURISTIC_NUMBER_KEY_MAP).find((rx) => new RegExp(rx).test(key));\n if (heuristicKey) {\n return HEURISTIC_NUMBER_KEY_MAP[heuristicKey];\n }\n }\n\n const getMinimum = () => {\n const min = minimum ?? DEFAULT_MIN_NUMBER;\n if (exclusiveMinimum) return typeof exclusiveMinimum === 'number' ? exclusiveMinimum + 1 : min + 1;\n return min;\n };\n\n const getMaximum = () => {\n const max = maximum ?? DEFAULT_MAX_NUMBER;\n if (exclusiveMaximum) return typeof exclusiveMaximum === 'number' ? exclusiveMaximum - 1 : max - 1;\n return max;\n };\n\n const getMultipleOf = () => {\n if (multipleOf) return multipleOf;\n return DEFAULT_MULTIPLE_OF;\n };\n\n const min = getMinimum();\n const max = getMaximum();\n\n return interpolateString(FAKER_HELPERS.number, {\n // prevent non-sense min max values\n min: min > max ? max : min,\n max: max < min ? min : max,\n multipleOf: getMultipleOf(),\n });\n};\n\nconst transformStringBasedOnFormat = (schema: OpenAPIV3.NonArraySchemaObject, key?: string) => {\n const { format, pattern, minLength, maxLength } = schema;\n\n if (format && JSON_SCHEMA_STANDARD_FORMATS[format]) {\n return JSON_SCHEMA_STANDARD_FORMATS[format];\n }\n\n if (pattern) {\n try {\n // check for invalid patterns\n new RegExp(pattern);\n return interpolateString(FAKER_HELPERS.fromRegExp, { pattern: `/${pattern}/` });\n } catch {\n console.error(`Invalid pattern regex pattern in your openapi schema found: ${pattern}`);\n return FAKER_HELPERS.string;\n }\n }\n\n if (key) {\n const heuristicKey = Object.keys(HEURISTIC_STRING_KEY_MAP).find((rx) => new RegExp(rx).test(key));\n if (heuristicKey) {\n return HEURISTIC_STRING_KEY_MAP[heuristicKey];\n }\n }\n\n if (minLength !== undefined || maxLength !== undefined) {\n if (minLength !== undefined && maxLength !== undefined) {\n return interpolateString(FAKER_HELPERS.alpha, {\n minLength: minLength > maxLength ? maxLength : minLength,\n maxLength: maxLength < minLength ? minLength : maxLength,\n });\n } else if (minLength) {\n return interpolateString(FAKER_HELPERS.alphaMin, { minLength });\n } else if (maxLength) {\n return interpolateString(FAKER_HELPERS.alphaMax, { maxLength });\n }\n }\n\n return FAKER_HELPERS.string;\n};\n\nconst handleObject = (\n info: Omit<MockGeneratorInfo, 'schema'> & { schema: OpenAPIV3.NonArraySchemaObject },\n isInsideArray?: boolean\n): string => {\n const { schema, ...context } = info;\n const { properties, additionalProperties } = schema;\n\n if (!properties) {\n if (typeof additionalProperties === 'object') {\n const value = generateMock({ schema: additionalProperties, ...context }, undefined, isInsideArray);\n return `{ [${FAKER_HELPERS.string}]: ${value} }`;\n }\n\n // provide example in case no properties are defined\n if (schema.example) {\n return JSON.stringify(schema.example);\n }\n }\n\n const propertiesEntries = Object.entries(properties ?? {}).map(\n ([key, value]) => `'${key}': ${generateMock({ schema: value, ...context }, key, isInsideArray)}`\n );\n\n const additionalPropertiesEntries = [];\n if (additionalProperties === true) {\n additionalPropertiesEntries.push(`[${FAKER_HELPERS.string}]: ${FAKER_HELPERS.string}`);\n } else if (typeof additionalProperties === 'object') {\n const value = generateMock({ schema: additionalProperties, ...context }, undefined, isInsideArray);\n additionalPropertiesEntries.push(`[${FAKER_HELPERS.string}]: ${value}`);\n }\n\n const allEntries = [...propertiesEntries, ...additionalPropertiesEntries];\n return `{${allEntries.join(',\\n')}}`;\n};\n\nconst handleArray = (\n info: Omit<MockGeneratorInfo, 'schema'> & { schema: OpenAPIV3.ArraySchemaObject },\n key?: string\n): string => {\n const { schema, ...context } = info;\n const endpointConfig: EndpointConfig = context.config.endpoints?.[context.path]?.[context.method] ?? {};\n const arrayLength = endpointConfig.arrayLength ?? context.config.arrayLength ?? DEFAULT_CONFIG.arrayLength;\n\n // provide example in case no items are defined\n if (!schema.items && schema.example) {\n return JSON.stringify(schema.example);\n }\n\n return `Array.from({ length: ${arrayLength} }, () => (${generateMock({ schema: schema.items, ...context }, key, true)}))`;\n};\n\nconst handleFakerMapping = (key: string, fakerMap: FakerMap): string | null => {\n if (!fakerMap) return null;\n\n const resolveValue = (value: string | (() => unknown)) => {\n if (typeof value === 'function') return `(${value.toString()})()`;\n\n if (typeof value === 'string' && value.startsWith('faker.')) {\n return value;\n }\n\n return JSON.stringify(value);\n };\n\n if (fakerMap[key]) {\n return resolveValue(fakerMap[key]);\n }\n\n for (const rx in fakerMap) {\n try {\n if (new RegExp(rx).test(key)) {\n return resolveValue(fakerMap[rx]);\n }\n } catch {\n console.warn(`Invalid regex pattern found: ${rx}`);\n }\n }\n\n return null;\n};\n\nconst handleUndefinedType = (info: Omit<MockGeneratorInfo, 'schema'> & { schema: OpenAPIV3.SchemaObject }) => {\n const { schema, ...context } = info;\n const { additionalProperties } = schema;\n\n if ('properties' in schema || (additionalProperties && typeof additionalProperties === 'object')) {\n return generateMock({ schema: { ...schema, type: 'object' }, ...context });\n } else if ('items' in schema) {\n return generateMock({ schema: { ...schema, type: 'array' }, ...context });\n } else if (\n 'minimum' in schema ||\n 'maximum' in schema ||\n 'multipleOf' in schema ||\n 'exclusiveMinimum' in schema ||\n 'exclusiveMaximum' in schema\n ) {\n return generateMock({ schema: { ...schema, type: 'number' }, ...context });\n }\n\n return generateMock({ schema: { ...schema, type: 'string' }, ...context });\n};\n\nexport function generateMock(\n info: MockGeneratorInfo,\n key?: string,\n isInsideArray = false // a flag to prevent example being used inside the array\n): string {\n const { schema, ...context } = info;\n const { config, path: endpointPath, method: endpointMethod } = context;\n const { fakerMap, endpoints, useExample: globalUseExample } = config;\n\n const endpointConfig: EndpointConfig = endpoints?.[endpointPath]?.[endpointMethod] ?? {};\n const useExample = endpointConfig.useExample ?? globalUseExample;\n const mergedFakerMap = merge({}, fakerMap, endpointConfig.fakerMap);\n\n if (!schema || '$ref' in schema || 'x-circular-ref' in schema) return 'null';\n\n if (key) {\n const mapped = handleFakerMapping(key, mergedFakerMap);\n if (mapped) return mapped;\n }\n\n if (useExample) {\n validateExampleAgainstSchema({ schema, ...context }, key);\n\n // if mediaType Object examples is provided, use it directly\n if ('exampleObject' in schema && schema.exampleObject) {\n return JSON.stringify(schema.exampleObject);\n }\n\n // case when example is provided in the schemaObject level\n if ('example' in schema && !isInsideArray) {\n return JSON.stringify(schema.example);\n }\n }\n\n if (schema.enum) {\n return interpolateString(FAKER_HELPERS.arrayElement, { arrayElement: JSON.stringify(schema.enum) });\n }\n\n if (schema.allOf) {\n const { allOf, ...rest } = schema;\n return generateMock({ schema: merge({}, ...allOf, rest), ...context }, key, isInsideArray);\n }\n\n if (schema.oneOf || schema.anyOf) {\n const schemaObjects = schema.oneOf || schema.anyOf || [];\n const arrayElement = schemaObjects\n .map((schemaObject) => generateMock({ schema: schemaObject, ...context }, key, isInsideArray))\n .join(',');\n\n return interpolateString(FAKER_HELPERS.arrayElement, {\n arrayElement: `[${arrayElement}]`,\n });\n }\n\n switch (schema.type) {\n case 'string':\n return transformStringBasedOnFormat(schema, key);\n case 'number':\n case 'integer':\n return transformNumberBasedOnFormat(schema, key);\n case 'boolean':\n return FAKER_HELPERS.boolean;\n case 'object':\n return handleObject({ schema, ...context }, isInsideArray);\n case 'array':\n return handleArray({ schema, ...context }, key);\n case undefined:\n return handleUndefinedType({ schema, ...context });\n default:\n return 'null';\n }\n}\n\nexport const generateMocks = (organizedApiData: OrganizedApiData[], config: Config): GeneratedMocks => {\n const { dynamic } = config;\n\n return organizedApiData.map((endpoint) => {\n const { path, method, responses } = endpoint;\n\n return responses.map((res) => {\n const schema = res.response?.['application/json'];\n const info: MockGeneratorInfo = { schema, config, path, method };\n\n const mockCode = generateMock(info);\n\n return dynamic ? mockCode : executeCode(mockCode);\n });\n });\n};\n","import { OpenAPIV3 } from 'openapi-types';\n\nimport { ApiInfoByEndpoints, OrganizedApiData, SchemaWithNullablePaths } from '@/lib/shared/types';\n\nexport const extractRelevantFields = (paths: OpenAPIV3.PathsObject): ApiInfoByEndpoints[] =>\n Object.entries(paths).flatMap(([pathName, pathItem]) => {\n if (!pathItem) return [];\n\n return Object.values(OpenAPIV3.HttpMethods).reduce((acc: ApiInfoByEndpoints[], method) => {\n const operation = pathItem[method];\n\n if (operation) {\n acc.push({\n path: pathName,\n method,\n tags: operation.tags ?? [],\n operationId: operation.operationId ?? '',\n summary: operation.summary ?? '',\n description: operation.description,\n parameters: operation.parameters,\n responses: operation.responses,\n });\n }\n\n return acc;\n }, []);\n });\n\nconst getNullablePaths = (schema: OpenAPIV3.SchemaObject): string[] => {\n const paths: string[] = [];\n\n const traverse = (subSchema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, path: string) => {\n if (!subSchema || '$ref' in subSchema || 'x-circular-ref' in subSchema) {\n return;\n }\n\n if (subSchema.nullable && path) {\n paths.push(path);\n }\n\n if (subSchema.allOf) subSchema.allOf.forEach((s) => traverse(s, path));\n if (subSchema.oneOf) subSchema.oneOf.forEach((s) => traverse(s, path));\n if (subSchema.anyOf) subSchema.anyOf.forEach((s) => traverse(s, path));\n\n if (subSchema.type === 'object' && subSchema.properties) {\n for (const [key, propSchema] of Object.entries(subSchema.properties)) {\n const newPath = path ? `${path}.${key}` : key;\n traverse(propSchema, newPath);\n }\n }\n\n if (subSchema.type === 'array' && subSchema.items) {\n traverse(subSchema.items, path);\n }\n };\n\n traverse(schema, '');\n return [...new Set(paths)];\n};\n\nconst transformExample = (\n example: OpenAPIV3.ExampleObject | OpenAPIV3.ReferenceObject | undefined,\n doc: OpenAPIV3.Document,\n resolvedRefs?: Set<string>\n) => {\n if (!example) return {};\n\n // resolve reference object first if encountered\n if ('$ref' in example) {\n if (resolvedRefs?.has(example.$ref)) {\n console.error('Circular reference detected:', example.$ref);\n return { 'x-circular-ref': true };\n }\n\n const scopedResolvedRefs = new Set(resolvedRefs);\n scopedResolvedRefs.add(example.$ref);\n const name = example.$ref.split('/').pop() ?? '';\n return transformExample(doc.components?.examples?.[name], doc, scopedResolvedRefs);\n }\n\n // For now, we do not resolve externalValue, we just provide it as is\n return example.value ?? example.externalValue;\n};\n\nconst transformSchema = (\n schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,\n doc: OpenAPIV3.Document,\n resolvedRefs?: Set<string>\n): OpenAPIV3.SchemaObject | Record<string, unknown> => {\n if (!schema) return {};\n\n // resolve reference object first if encountered\n if ('$ref' in schema) {\n if (resolvedRefs?.has(schema.$ref)) {\n console.error('Circular reference detected:', schema.$ref);\n return { 'x-circular-ref': true };\n }\n\n const scopedResolvedRefs = new Set(resolvedRefs);\n scopedResolvedRefs.add(schema.$ref);\n const name = schema.$ref.split('/').pop() ?? '';\n return transformSchema(doc.components?.schemas?.[name], doc, scopedResolvedRefs);\n }\n\n if (schema.type === 'array') {\n return { ...schema, items: schema.items ? transformSchema(schema.items, doc, resolvedRefs) : undefined };\n }\n\n if (schema.type === 'object') {\n return {\n ...schema,\n properties: schema.properties\n ? Object.fromEntries(\n Object.entries(schema.properties).map(([k, v]) => [k, transformSchema(v, doc, resolvedRefs)])\n )\n : undefined,\n };\n }\n\n if (schema.allOf) return { ...schema, allOf: schema.allOf.map((s) => transformSchema(s, doc, resolvedRefs)) };\n if (schema.oneOf) return { ...schema, oneOf: schema.oneOf.map((s) => transformSchema(s, doc, resolvedRefs)) };\n if (schema.anyOf) return { ...schema, anyOf: schema.anyOf.map((s) => transformSchema(s, doc, resolvedRefs)) };\n\n return schema;\n};\n\nconst resolveResponse = (\n response: OpenAPIV3.ResponseObject | OpenAPIV3.ReferenceObject,\n doc: OpenAPIV3.Document\n): Record<string, SchemaWithNullablePaths> => {\n // resolve reference object first if encountered\n if ('$ref' in response) {\n const name = response.$ref.split('/').pop() ?? '';\n const realResponse = doc.components?.responses?.[name];\n return realResponse ? resolveResponse(realResponse, doc) : {};\n }\n\n const { co