mock-fried 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.3.1",
7
+ "version": "1.4.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,14 +1,10 @@
1
1
  import { type OpenAPISchema } from '../../utils/mock/providers/index.js';
2
- import { type OpenAPISpec } from '../../utils/cache-manager.js';
3
- /**
4
- * OpenAPI 스펙 파일 로드
5
- */
6
- export declare function loadOpenAPISpec(specPath: string): OpenAPISpec;
7
2
  /**
8
3
  * 스키마 참조 해결
9
4
  */
10
5
  export declare function resolveSchemaRef(schema: OpenAPISchema, schemas?: Record<string, Record<string, unknown>>): OpenAPISchema;
11
6
  /**
12
7
  * OpenAPI Backend 인스턴스 가져오기 (캐싱 포함)
8
+ * Swagger 2.0과 OpenAPI 3.x 모두 지원
13
9
  */
14
10
  export declare function getOpenAPIBackend(specPath: string): Promise<any>;
@@ -1,5 +1,3 @@
1
- import { readFileSync } from "node:fs";
2
- import yaml from "js-yaml";
3
1
  import {
4
2
  generateMockFromSchema,
5
3
  hashString
@@ -10,12 +8,17 @@ import {
10
8
  } from "../../utils/mock/providers/index.js";
11
9
  import { cacheManager } from "../../utils/cache-manager.js";
12
10
  import { getOrCreateCursorManager, getOrCreatePageManager } from "../../utils/pagination-factory.js";
13
- export function loadOpenAPISpec(specPath) {
14
- const content = readFileSync(specPath, "utf-8");
15
- if (specPath.endsWith(".yaml") || specPath.endsWith(".yml")) {
16
- return yaml.load(content);
11
+ import { loadSpec } from "../../utils/spec-loader.js";
12
+ async function getCachedSpecLoader(specPath) {
13
+ const cache = cacheManager.specMode;
14
+ if (cache.specLoader && cache.specPath === specPath) {
15
+ return cache.specLoader;
17
16
  }
18
- return JSON.parse(content);
17
+ const result = await loadSpec(specPath);
18
+ cache.specLoader = result;
19
+ cache.specPath = specPath;
20
+ console.log(`[mock-fried] Loaded ${result.version} spec: ${specPath}`);
21
+ return result;
19
22
  }
20
23
  export function resolveSchemaRef(schema, schemas) {
21
24
  if (!schema.$ref || !schemas) return schema;
@@ -29,13 +32,14 @@ export async function getOpenAPIBackend(specPath) {
29
32
  return cache.apiInstance;
30
33
  }
31
34
  const { OpenAPIBackend } = await import("openapi-backend");
32
- const definition = loadOpenAPISpec(specPath);
33
- cache.spec = definition;
35
+ const { spec, openapi3Spec } = await getCachedSpecLoader(specPath);
36
+ cache.spec = spec;
34
37
  const apiInstance = new OpenAPIBackend({
38
+ // OpenAPI 3.0 스펙 전달 (Swagger 2.0도 변환되어 사용)
35
39
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- definition,
37
- quick: false,
38
- // $ref 역참조를 위해 false로 설정
40
+ definition: openapi3Spec,
41
+ quick: true,
42
+ // 이미 OpenAPI 3.0으로 변환됨
39
43
  validate: false
40
44
  // Mock 서버에서는 request validation 비활성화
41
45
  });
@@ -2,11 +2,11 @@ import { defineEventHandler, createError } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
3
  import { consola } from "consola";
4
4
  import { createRequire } from "node:module";
5
- import { readFileSync, existsSync, statSync } from "node:fs";
6
- import yaml from "js-yaml";
5
+ import { existsSync, statSync } from "node:fs";
7
6
  import { getClientPackage } from "../utils/client-parser.js";
8
7
  import { findProtoFiles, getProtoTypeName } from "../utils/proto-utils.js";
9
8
  import { cacheManager } from "../utils/cache-manager.js";
9
+ import { loadSpec, getSchemaDefinitions, mergeParameters } from "../utils/spec-loader.js";
10
10
  const logger = consola.withTag("mock-fried");
11
11
  let _require = null;
12
12
  function getRequire() {
@@ -98,62 +98,25 @@ function parseClientPackageSchema(config) {
98
98
  export function clearSchemaCache() {
99
99
  cacheManager.clearSchema();
100
100
  }
101
- function parseOpenApiSpec(specPath) {
101
+ async function parseOpenApiSpec(specPath) {
102
102
  if (!existsSync(specPath)) {
103
103
  return void 0;
104
104
  }
105
105
  try {
106
- const content = readFileSync(specPath, "utf-8");
107
- let spec;
108
- if (specPath.endsWith(".yaml") || specPath.endsWith(".yml")) {
109
- spec = yaml.load(content);
110
- } else {
111
- spec = JSON.parse(content);
112
- }
106
+ const { spec, version } = await loadSpec(specPath);
113
107
  const info = spec.info || {};
114
108
  const paths = spec.paths || {};
115
109
  const pathItems = [];
116
110
  for (const [path, pathItem] of Object.entries(paths)) {
117
- const pathLevelParams = [];
118
- if (Array.isArray(pathItem.parameters)) {
119
- for (const param of pathItem.parameters) {
120
- const p = param;
121
- pathLevelParams.push({
122
- name: p.name,
123
- in: p.in,
124
- required: p.required,
125
- description: p.description,
126
- schema: p.schema
127
- });
128
- }
129
- }
130
- for (const [method, operation] of Object.entries(pathItem)) {
111
+ const pathObj = pathItem;
112
+ const pathLevelParams = extractParameters(pathObj.parameters);
113
+ for (const [method, operation] of Object.entries(pathObj)) {
131
114
  if (["get", "post", "put", "delete", "patch"].includes(method.toLowerCase())) {
132
115
  const op = operation;
133
- const operationParams = [];
134
- if (Array.isArray(op.parameters)) {
135
- for (const param of op.parameters) {
136
- const p = param;
137
- operationParams.push({
138
- name: p.name,
139
- in: p.in,
140
- required: p.required,
141
- description: p.description,
142
- schema: p.schema
143
- });
144
- }
145
- }
146
- const mergedParams = [...pathLevelParams];
147
- for (const opParam of operationParams) {
148
- const existingIndex = mergedParams.findIndex(
149
- (p) => p.name === opParam.name && p.in === opParam.in
150
- );
151
- if (existingIndex >= 0) {
152
- mergedParams[existingIndex] = opParam;
153
- } else {
154
- mergedParams.push(opParam);
155
- }
156
- }
116
+ const operationParams = extractParameters(op.parameters);
117
+ const mergedParams = mergeParameters(pathLevelParams, operationParams);
118
+ const requestBody = extractRequestBodyRef(op.requestBody);
119
+ const responses = extractResponsesRef(op.responses);
157
120
  pathItems.push({
158
121
  path,
159
122
  method: method.toUpperCase(),
@@ -162,25 +125,96 @@ function parseOpenApiSpec(specPath) {
162
125
  description: op.description,
163
126
  tags: op.tags,
164
127
  parameters: mergedParams.length > 0 ? mergedParams : void 0,
165
- requestBody: op.requestBody,
166
- responses: op.responses
128
+ requestBody,
129
+ responses
167
130
  });
168
131
  }
169
132
  }
170
133
  }
134
+ const schemas = getSchemaDefinitions(spec);
135
+ const schemaCount = Object.keys(schemas).length;
171
136
  return {
172
137
  info: {
173
138
  title: info.title || "Unknown API",
174
139
  version: info.version || "1.0.0",
175
140
  description: info.description
176
141
  },
177
- paths: pathItems
142
+ paths: pathItems,
143
+ _meta: {
144
+ source: "spec-file",
145
+ specVersion: version,
146
+ // 'swagger2' | 'openapi3'
147
+ schemaCount,
148
+ endpointCount: pathItems.length
149
+ }
178
150
  };
179
151
  } catch (error) {
180
152
  logger.error("Failed to parse OpenAPI spec:", specPath, error);
181
153
  return void 0;
182
154
  }
183
155
  }
156
+ function extractParameters(params) {
157
+ if (!Array.isArray(params)) return [];
158
+ return params.map((param) => {
159
+ const p = param;
160
+ return {
161
+ name: p.name,
162
+ in: p.in,
163
+ required: p.required,
164
+ description: p.description,
165
+ schema: p.schema
166
+ };
167
+ });
168
+ }
169
+ function extractRequestBodyRef(requestBody) {
170
+ if (!requestBody) return void 0;
171
+ const content = requestBody.content;
172
+ if (content?.["application/json"]?.schema) {
173
+ const schema = content["application/json"].schema;
174
+ return {
175
+ content: {
176
+ "application/json": {
177
+ schema: schema.$ref ? { $ref: schema.$ref } : { type: "object" }
178
+ }
179
+ }
180
+ };
181
+ }
182
+ return void 0;
183
+ }
184
+ function extractResponsesRef(responses) {
185
+ if (!responses) return void 0;
186
+ const result = {};
187
+ for (const [code, response] of Object.entries(responses)) {
188
+ const res = response;
189
+ const content = res.content;
190
+ if (content?.["application/json"]?.schema) {
191
+ const schema = content["application/json"].schema;
192
+ result[code] = {
193
+ description: res.description,
194
+ content: {
195
+ "application/json": {
196
+ schema: schema.$ref ? { $ref: schema.$ref } : { type: schema.type || "object" }
197
+ }
198
+ }
199
+ };
200
+ } else if (res.schema) {
201
+ const schema = res.schema;
202
+ result[code] = {
203
+ description: res.description,
204
+ content: {
205
+ "application/json": {
206
+ schema: schema.$ref ? { $ref: schema.$ref } : { type: schema.type || "object" }
207
+ }
208
+ }
209
+ };
210
+ } else {
211
+ result[code] = {
212
+ description: res.description
213
+ };
214
+ }
215
+ }
216
+ return result;
217
+ }
184
218
  async function parseProtoSpec(protoPath) {
185
219
  if (!existsSync(protoPath)) {
186
220
  return void 0;
@@ -301,7 +335,7 @@ export default defineEventHandler(async () => {
301
335
  }
302
336
  };
303
337
  } else if (mockConfig.openapiPath) {
304
- const openapi = parseOpenApiSpec(mockConfig.openapiPath);
338
+ const openapi = await parseOpenApiSpec(mockConfig.openapiPath);
305
339
  if (openapi) {
306
340
  schema.openapi = openapi;
307
341
  }
@@ -7,6 +7,7 @@ import type { SchemaMockGenerator } from './mock/index.js';
7
7
  import type { CursorPaginationManager, PagePaginationManager } from './mock/pagination/index.js';
8
8
  import type * as protoLoader from '@grpc/proto-loader';
9
9
  import type * as grpc from '@grpc/grpc-js';
10
+ import type { SpecLoaderResult } from './spec-loader.js';
10
11
  /**
11
12
  * OpenAPI 스펙 인터페이스
12
13
  */
@@ -33,6 +34,8 @@ interface SpecModeCache {
33
34
  apiInstance: any;
34
35
  specPath: string | null;
35
36
  spec: OpenAPISpec | null;
37
+ /** swagger-parser로 로드된 스펙 결과 (Swagger 2.0 지원) */
38
+ specLoader: SpecLoaderResult | null;
36
39
  cursorManager: CursorPaginationManager<Record<string, unknown>> | null;
37
40
  pageManager: PagePaginationManager<Record<string, unknown>> | null;
38
41
  backwardParam: string;
@@ -3,6 +3,7 @@ class MockCacheManager {
3
3
  apiInstance: null,
4
4
  specPath: null,
5
5
  spec: null,
6
+ specLoader: null,
6
7
  cursorManager: null,
7
8
  pageManager: null,
8
9
  backwardParam: "isBackward"
@@ -47,6 +48,7 @@ class MockCacheManager {
47
48
  this._specMode.apiInstance = null;
48
49
  this._specMode.specPath = null;
49
50
  this._specMode.spec = null;
51
+ this._specMode.specLoader = null;
50
52
  this._specMode.cursorManager = null;
51
53
  this._specMode.pageManager = null;
52
54
  this._specMode.backwardParam = "isBackward";
@@ -0,0 +1,65 @@
1
+ import type { OpenAPIV2, OpenAPIV3 } from 'openapi-types';
2
+ export type ParsedSpec = OpenAPIV2.Document | OpenAPIV3.Document;
3
+ export type SpecVersion = 'swagger2' | 'openapi3';
4
+ export interface SpecLoaderResult {
5
+ /** 원본 스펙 (검증됨) */
6
+ spec: ParsedSpec;
7
+ /** 스펙 버전 */
8
+ version: SpecVersion;
9
+ /** $ref가 해석된 스펙 */
10
+ dereferenced: ParsedSpec;
11
+ /** OpenAPI 3.x로 변환된 스펙 (openapi-backend용) */
12
+ openapi3Spec: OpenAPIV3.Document;
13
+ }
14
+ /**
15
+ * OpenAPI/Swagger 스펙 파일 로드 및 파싱
16
+ *
17
+ * - $ref 해석 (dereference)
18
+ * - 버전 자동 감지
19
+ * - Swagger 2.0 → OpenAPI 3.0 변환
20
+ * - 검증
21
+ *
22
+ * @param specPath 스펙 파일 경로 (YAML/JSON)
23
+ */
24
+ export declare function loadSpec(specPath: string): Promise<SpecLoaderResult>;
25
+ /**
26
+ * Swagger 2.0 여부 확인
27
+ */
28
+ export declare function isSwagger2(spec: ParsedSpec): spec is OpenAPIV2.Document;
29
+ /**
30
+ * OpenAPI 3.x 여부 확인
31
+ */
32
+ export declare function isOpenApi3(spec: ParsedSpec): spec is OpenAPIV3.Document;
33
+ /**
34
+ * 스키마 정의 위치 가져오기 (버전별)
35
+ *
36
+ * - Swagger 2.0: definitions
37
+ * - OpenAPI 3.x: components/schemas
38
+ */
39
+ export declare function getSchemaDefinitions(spec: ParsedSpec): Record<string, unknown>;
40
+ /**
41
+ * 응답 스키마 추출 (버전별)
42
+ *
43
+ * 200 → 201 → 204 → default 순서로 찾음
44
+ *
45
+ * @param responses 응답 정의 객체
46
+ * @param spec 스펙 (버전 감지용)
47
+ */
48
+ export declare function getResponseSchema(responses: OpenAPIV2.ResponsesObject | OpenAPIV3.ResponsesObject | undefined, spec: ParsedSpec): unknown | undefined;
49
+ /**
50
+ * 파라미터 스키마 추출 (버전별)
51
+ *
52
+ * - Swagger 2.0: type 직접 또는 schema
53
+ * - OpenAPI 3.x: schema
54
+ */
55
+ export declare function getParameterSchema(param: OpenAPIV2.ParameterObject | OpenAPIV3.ParameterObject, spec: ParsedSpec): unknown;
56
+ /**
57
+ * path-level + operation-level 파라미터 병합
58
+ *
59
+ * operation-level이 path-level을 override (같은 name + in 조합)
60
+ */
61
+ export declare function mergeParameters(pathParams: (OpenAPIV2.ParameterObject | OpenAPIV3.ParameterObject)[] | undefined, operationParams: (OpenAPIV2.ParameterObject | OpenAPIV3.ParameterObject)[] | undefined): (OpenAPIV2.ParameterObject | OpenAPIV3.ParameterObject)[];
62
+ /**
63
+ * Swagger 2.0의 body 파라미터를 requestBody처럼 추출
64
+ */
65
+ export declare function getRequestBodySchema(params: (OpenAPIV2.ParameterObject | OpenAPIV3.ParameterObject)[] | undefined, spec: ParsedSpec): unknown | undefined;
@@ -0,0 +1,100 @@
1
+ import SwaggerParser from "@apidevtools/swagger-parser";
2
+ export async function loadSpec(specPath) {
3
+ const spec = await SwaggerParser.parse(specPath);
4
+ const version = isSwagger2(spec) ? "swagger2" : "openapi3";
5
+ let openapi3Spec;
6
+ if (version === "swagger2") {
7
+ const { convertFile } = await import("swagger2openapi");
8
+ const result = await convertFile(specPath, {
9
+ patch: true,
10
+ // 자동 패치 적용
11
+ warnOnly: true
12
+ // 경고만 하고 계속 진행
13
+ });
14
+ openapi3Spec = result.openapi;
15
+ } else {
16
+ openapi3Spec = spec;
17
+ }
18
+ const dereferenced = await SwaggerParser.dereference(specPath);
19
+ return { spec, version, dereferenced, openapi3Spec };
20
+ }
21
+ export function isSwagger2(spec) {
22
+ return "swagger" in spec && spec.swagger === "2.0";
23
+ }
24
+ export function isOpenApi3(spec) {
25
+ return "openapi" in spec && (spec.openapi?.startsWith("3.") ?? false);
26
+ }
27
+ export function getSchemaDefinitions(spec) {
28
+ if (isSwagger2(spec)) {
29
+ return spec.definitions ?? {};
30
+ }
31
+ return spec.components?.schemas ?? {};
32
+ }
33
+ export function getResponseSchema(responses, spec) {
34
+ if (!responses) return void 0;
35
+ const successCodes = ["200", "201", "204", "default"];
36
+ for (const code of successCodes) {
37
+ const response = responses[code];
38
+ if (!response) continue;
39
+ if ("$ref" in response) continue;
40
+ if (isSwagger2(spec)) {
41
+ const swagger2Response = response;
42
+ if (swagger2Response.schema) {
43
+ return swagger2Response.schema;
44
+ }
45
+ } else {
46
+ const oas3Response = response;
47
+ const content = oas3Response.content;
48
+ if (content?.["application/json"]?.schema) {
49
+ return content["application/json"].schema;
50
+ }
51
+ if (content) {
52
+ const firstContentType = Object.keys(content)[0];
53
+ if (firstContentType && content[firstContentType]?.schema) {
54
+ return content[firstContentType].schema;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ return void 0;
60
+ }
61
+ export function getParameterSchema(param, spec) {
62
+ if (isSwagger2(spec)) {
63
+ const swagger2Param = param;
64
+ if ("schema" in swagger2Param && swagger2Param.schema) {
65
+ return swagger2Param.schema;
66
+ }
67
+ return {
68
+ type: swagger2Param.type,
69
+ format: swagger2Param.format,
70
+ enum: swagger2Param.enum,
71
+ minimum: swagger2Param.minimum,
72
+ maximum: swagger2Param.maximum,
73
+ default: swagger2Param.default
74
+ };
75
+ }
76
+ const oas3Param = param;
77
+ return oas3Param.schema;
78
+ }
79
+ export function mergeParameters(pathParams, operationParams) {
80
+ const merged = /* @__PURE__ */ new Map();
81
+ for (const param of pathParams ?? []) {
82
+ const key = `${param.name}:${param.in}`;
83
+ merged.set(key, param);
84
+ }
85
+ for (const param of operationParams ?? []) {
86
+ const key = `${param.name}:${param.in}`;
87
+ merged.set(key, param);
88
+ }
89
+ return Array.from(merged.values());
90
+ }
91
+ export function getRequestBodySchema(params, spec) {
92
+ if (!params) return void 0;
93
+ if (isSwagger2(spec)) {
94
+ const bodyParam = params.find(
95
+ (p) => p.in === "body"
96
+ );
97
+ return bodyParam?.schema;
98
+ }
99
+ return void 0;
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mock-fried",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "Nuxt3 Mock API Module - OpenAPI & Protobuf RPC Mock Server",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,13 +37,15 @@
37
37
  "prepack": "nuxt-module-build build",
38
38
  "dev": "yarn dev:prepare && nuxi dev playground-openapi",
39
39
  "dev:openapi": "yarn dev:prepare && nuxi dev playground-openapi",
40
+ "dev:swagger": "yarn dev:prepare && nuxi dev playground-swagger",
40
41
  "dev:openapi-client": "yarn dev:prepare && nuxi dev playground-openapi-client",
41
42
  "dev:proto": "yarn dev:prepare:proto && nuxi dev playground-proto",
42
43
  "dev:build": "nuxi build playground-openapi",
44
+ "dev:build:swagger": "nuxi build playground-swagger",
43
45
  "dev:build:openapi-client": "nuxi build playground-openapi-client",
44
46
  "dev:build:proto": "nuxi build playground-proto",
45
47
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare",
46
- "dev:prepare:playground": "yarn dev:prepare && nuxi prepare playground-openapi && nuxi prepare playground-openapi-client && nuxi prepare playground-proto",
48
+ "dev:prepare:playground": "yarn dev:prepare && nuxi prepare playground-openapi && nuxi prepare playground-swagger && nuxi prepare playground-openapi-client && nuxi prepare playground-proto",
47
49
  "dev:prepare:proto": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground-proto",
48
50
  "release": "yarn lint && yarn test && yarn prepack && changelogen --release && yarn npm publish && git push --follow-tags",
49
51
  "lint": "eslint .",
@@ -56,33 +58,41 @@
56
58
  "test:unit": "vitest run --exclude 'test/e2e/**'",
57
59
  "test:e2e": "vitest run test/e2e/",
58
60
  "test:e2e:openapi": "vitest run test/e2e/playground-openapi.e2e.test.ts",
61
+ "test:e2e:swagger": "vitest run test/e2e/playground-swagger.e2e.test.ts",
59
62
  "test:e2e:openapi-client": "vitest run test/e2e/playground-openapi-client.e2e.test.ts",
60
63
  "test:e2e:proto": "vitest run test/e2e/playground-proto.e2e.test.ts test/e2e/playground-proto-advanced.e2e.test.ts",
61
64
  "test:e2e:proto-advanced": "vitest run test/e2e/playground-proto-advanced.e2e.test.ts",
62
65
  "test:types": "vue-tsc --noEmit"
63
66
  },
67
+ "peerDependencies": {
68
+ "@nuxt/kit": ">=3.0.0"
69
+ },
64
70
  "dependencies": {
71
+ "@apidevtools/swagger-parser": "^12.1.0",
65
72
  "@grpc/grpc-js": "^1.12.0",
66
73
  "@grpc/proto-loader": "^0.7.13",
67
- "@nuxt/kit": "^4.2.2",
68
74
  "consola": "^3.4.2",
69
75
  "js-yaml": "^4.1.0",
70
76
  "openapi-backend": "^5.10.6",
71
77
  "pathe": "^2.0.2",
78
+ "swagger2openapi": "^7.0.8",
72
79
  "ufo": "^1.6.1"
73
80
  },
74
81
  "devDependencies": {
75
82
  "@nuxt/devtools": "^3.1.1",
76
83
  "@nuxt/eslint-config": "^1.12.1",
84
+ "@nuxt/kit": "^4.2.2",
77
85
  "@nuxt/module-builder": "^1.0.2",
78
86
  "@nuxt/schema": "^4.2.2",
79
87
  "@nuxt/test-utils": "^3.21.0",
80
88
  "@types/js-yaml": "^4.0.9",
81
89
  "@types/node": "latest",
90
+ "@types/swagger2openapi": "^7",
82
91
  "@vitest/coverage-v8": "^4.0.16",
83
92
  "changelogen": "^0.6.2",
84
93
  "eslint": "^9.39.2",
85
94
  "nuxt": "^4.2.2",
95
+ "openapi-types": "^12.1.3",
86
96
  "prettier": "^3.5.3",
87
97
  "typescript": "~5.9.3",
88
98
  "vitest": "^4.0.16",