openapi-ts-mock-generator
Version:
typescript mock data generator based openapi
1 lines • 13.8 kB
Source Map (JSON)
{"version":3,"sources":["../../src/core/config.ts","../../src/utils/file-utils.ts","../../src/generators/handler-generator.ts"],"sourcesContent":["import { Faker, ko } from \"@faker-js/faker\"\nimport { Options } from \"./types\"\n\n/**\n * 기본 옵션 설정\n */\nexport const defaultOptions: Options = {\n path: \"\",\n arrayMinLength: 1,\n arrayMaxLength: 3,\n includeCodes: undefined,\n baseDir: \"./\",\n specialPath: undefined,\n handlerUrl: \"*\",\n fakerLocale: \"ko\",\n generateTarget: \"api,schema\",\n clear: false,\n // TypeScriptCodeOptions\n isStatic: false,\n isOptional: false,\n}\n\n/**\n * 배열 길이 관련 상수\n */\nexport const ARRAY_MIN_LENGTH = 1\nexport const ARRAY_MAX_LENGTH = 3\n\n/**\n * 문자열 길이 관련 상수\n */\nexport const MIN_STRING_LENGTH = 3\nexport const MAX_STRING_LENGTH = 20\n\n/**\n * 정수 범위 관련 상수\n */\nexport const MIN_INTEGER = 1\nexport const MAX_INTEGER = 100000\n\n/**\n * 소수 범위 관련 상수\n */\nexport const MIN_NUMBER = 0\nexport const MAX_NUMBER = 100\n\n/**\n * 단어 길이 관련 상수\n */\nexport const MIN_WORD_LENGTH = 0\nexport const MAX_WORD_LENGTH = 3\n\n/**\n * Faker 시드값 (일관된 결과를 위함)\n */\nconst FAKER_SEED = 1\n\n/**\n * 전역 Faker 인스턴스\n * 한국어 로케일을 기본으로 설정하고 시드를 고정하여 일관된 결과 제공\n */\nexport const faker = new Faker({\n locale: [ko],\n})\nfaker.seed(FAKER_SEED)\n\n/**\n * 생성된 파일의 상단에 추가되는 주석\n */\nexport const GEN_COMMENT =\n \"/* Do not edit this file. */\\n/* This file generated by openapi-ts-mock-generator. */\\n\\n\"\n","/**\n * 파일 시스템 관련 유틸리티 함수들\n */\n\nimport { existsSync, mkdirSync, writeFileSync, rmSync, readdirSync, readFileSync } from \"fs\"\nimport * as path from \"path\"\n\n/**\n * 디렉토리가 존재하지 않으면 생성\n */\nexport const ensureDir = (dirPath: string): void => {\n if (!existsSync(dirPath)) {\n mkdirSync(dirPath, { recursive: true })\n }\n}\n\n/**\n * 디렉토리를 비우고 정리\n */\nexport const clearDirectory = (dirPath: string): void => {\n if (existsSync(dirPath)) {\n readdirSync(dirPath).forEach((file) => {\n rmSync(path.join(dirPath, file))\n })\n }\n}\n\n/**\n * 안전하게 파일 쓰기 (디렉토리 자동 생성)\n */\nexport const safeWriteFile = (filePath: string, content: string): void => {\n const dir = path.dirname(filePath)\n ensureDir(dir)\n writeFileSync(filePath, content)\n}\n\n/**\n * JSON 파일 읽기 (파일이 없으면 기본값 반환)\n */\nexport const readJsonFile = <T>(filePath: string, defaultValue: T): T => {\n if (!existsSync(filePath)) {\n return defaultValue\n }\n\n try {\n const content = readFileSync(filePath, \"utf-8\")\n return JSON.parse(content)\n } catch (error) {\n console.warn(`Failed to read JSON file ${filePath}:`, error)\n return defaultValue\n }\n}\n\n/**\n * 파일 경로가 URL인지 로컬 파일인지 확인하여 절대 경로 반환\n */\nexport const resolveFilePath = (inputPath: string, baseDir?: string): string => {\n if (inputPath.startsWith(\"http\")) {\n return inputPath\n }\n\n if (baseDir) {\n return path.join(baseDir, inputPath)\n }\n\n return inputPath\n}\n\n/**\n * 여러 파일명 조합하여 고유한 파일명 생성\n */\nexport const createUniqueFileName = (baseName: string, extension: string): string => {\n const timestamp = Date.now()\n return `${baseName}-${timestamp}.${extension}`\n}\n","/**\n * MSW 핸들러 코드 생성 로직\n * MSW에서 사용할 HTTP 핸들러들을 생성\n */\n\nimport { GEN_COMMENT, Options, PathNormalizedType } from \"../core\"\nimport { ensureDir, clearDirectory, safeWriteFile } from \"../utils\"\nimport { camelCase, pascalCase } from \"change-case-all\"\nimport * as path from \"path\"\n\n/**\n * MSW 핸들러 코드를 생성하고 파일로 출력\n */\nexport const generateHandlers = (paths: PathNormalizedType[], options: Options): void => {\n const firstTags = Array.from(new Set(paths.map((path) => path.tags[0])))\n // create records with tag as key\n const handlersPerTag = firstTags.reduce((acc, tag) => {\n acc[tag] = []\n return acc\n }, {} as Record<string, string[]>)\n\n paths.forEach((path) => {\n const handler = generateSingleHandler(path, options)\n handlersPerTag[path.tags[0]].push(handler)\n })\n\n writeHandlerFiles(handlersPerTag, options)\n}\n\n/**\n * 단일 경로에 대한 핸들러 생성\n */\nconst generateSingleHandler = (path: PathNormalizedType, options: Options): string => {\n const codeBaseArray = [` http.${path.method}(\\`\\${handlerUrl}${path.pathname}\\`, () => {`]\n\n if (path.responses.length === 1) {\n // single response\n const res = path.responses[0]\n if (res.schema?.type === \"ref\") {\n const schemaName = pascalCase(res.schema.value.$ref.replace(\"#/components/schemas/\", \"\"))\n codeBaseArray.push(` // Schema is ${schemaName}`)\n }\n const outputResName = `get${pascalCase(path.operationId)}${res.statusCode}`\n codeBaseArray.push(` return HttpResponse.json(${outputResName}(), {`)\n codeBaseArray.push(` status: ${res.statusCode},`)\n codeBaseArray.push(` })`)\n } else if (path.responses.length > 1) {\n // multiple responses\n // random select response\n codeBaseArray.push(` const responses = [`)\n path.responses.forEach((res) => {\n const schemaName =\n res.schema?.type === \"ref\"\n ? pascalCase(res.schema.value.$ref.replace(\"#/components/schemas/\", \"\"))\n : \"\"\n const schemaComment = schemaName ? ` // Schema is ${schemaName}` : \"\"\n const outputResName = `get${pascalCase(path.operationId)}${res.statusCode}`\n codeBaseArray.push(\n ` [${outputResName}(), { status: ${res.statusCode} }],${schemaComment}`\n )\n })\n codeBaseArray.push(` ]`)\n codeBaseArray.push(` const randomIndex = Math.floor(Math.random() * responses.length)`)\n codeBaseArray.push(` return HttpResponse.json(...responses[randomIndex])`)\n } else {\n // empty responses\n codeBaseArray.push(` return HttpResponse.json()`)\n }\n\n codeBaseArray.push(` }),`)\n return codeBaseArray.join(\"\\n\")\n}\n\n/**\n * 핸들러 파일들을 실제로 디스크에 작성\n */\nconst writeHandlerFiles = (handlersPerTag: Record<string, string[]>, options: Options): void => {\n const directory = path.join(options.baseDir, \"handlers\")\n ensureDir(directory)\n if (options.clear) {\n clearDirectory(directory)\n }\n\n Object.entries(handlersPerTag).forEach(([tag, handlers]) => {\n const content = generateHandlerFileContent(tag, handlers, options)\n\n const fileName = path.join(directory, `${tag}.ts`)\n safeWriteFile(fileName, content)\n console.log(`Generated Handler ${fileName}`)\n })\n\n // make mockHandlers.ts for merge all handlers\n const mockHandlersContent = generateMockHandlersFile(handlersPerTag, options)\n const fileName = path.join(options.baseDir ?? \"\", \"mockHandlers.ts\")\n safeWriteFile(fileName, mockHandlersContent)\n console.log(`Generated mock handlers ${fileName}`)\n}\n\n/**\n * 핸들러 파일의 내용 생성\n */\nconst generateHandlerFileContent = (tag: string, handlers: string[], options: Options): string => {\n const importMSW = `import { http, HttpResponse } from 'msw'`\n const responseNames = handlers\n .reduce((acc, handler) => {\n const matched = handler.match(/get[A-Z]\\w+/g)\n if (matched === null) return acc\n return [...acc, ...matched]\n }, [] as string[])\n .join(\", \")\n const importResponses =\n responseNames.length > 0 ? `import { ${responseNames} } from \"../response\"\\n` : \"\"\n\n const handlerUrl = `const handlerUrl = \"${options.handlerUrl}\"`\n const handlerName = camelCase(tag)\n\n const mockHandlers = [\n `${importMSW}`,\n `${importResponses}`,\n `${handlerUrl}`,\n ``,\n `export const ${handlerName}Handlers = [`,\n `${handlers.join(\"\\n\\n\")}`,\n `]`,\n ].join(\"\\n\")\n\n return GEN_COMMENT + mockHandlers\n}\n\n/**\n * 모든 핸들러를 통합하는 mockHandlers 파일 생성\n */\nconst generateMockHandlersFile = (\n handlersPerTag: Record<string, string[]>,\n options: Options\n): string => {\n const handlersImport = Object.keys(handlersPerTag)\n .map((tag) => {\n const handlerName = `${camelCase(tag)}Handlers`\n return `import { ${handlerName} } from \"./handlers/${tag}\"`\n })\n .join(\"\\n\")\n\n const handlersArrayItem = Object.keys(handlersPerTag)\n .map((tag) => {\n const handlerName = `${camelCase(tag)}Handlers`\n return ` ...${handlerName},`\n })\n .join(\"\\n\")\n\n const mockHandlers = [\n `${handlersImport}`,\n ``,\n `export const handlers = [`,\n `${handlersArrayItem}`,\n `]`,\n ].join(\"\\n\")\n\n return GEN_COMMENT + mockHandlers\n}\n\n/**\n * 특정 태그의 핸들러만 생성\n */\nexport const generateHandlersForTag = (\n paths: PathNormalizedType[],\n tag: string,\n options: Options\n): string[] => {\n return paths\n .filter((path) => path.tags.includes(tag))\n .map((path) => generateSingleHandler(path, options))\n}\n\n/**\n * 핸들러에서 사용되는 응답 함수명들 추출\n */\nexport const extractResponseNames = (handlers: string[]): string[] => {\n return handlers.reduce((acc, handler) => {\n const matched = handler.match(/get[A-Z]\\w+/g)\n if (matched === null) return acc\n return [...acc, ...matched]\n }, [] as string[])\n}\n\n/**\n * 경로 정보에서 핸들러 함수명 생성\n */\nexport const generateHandlerName = (tag: string): string => {\n return `${camelCase(tag)}Handlers`\n}\n"],"mappings":"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","names":["path","_a","fileName"]}