vite-plugin-ai-mock-generator 1.0.0
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.js +663 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +653 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// src/storage.ts
|
|
5
|
+
var MockStorage = class {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.storageDir = options.dir || "mock-data";
|
|
8
|
+
this.persist = options.persist !== false;
|
|
9
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
10
|
+
if (this.persist && !fs.existsSync(this.storageDir)) {
|
|
11
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
this.load();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 生成存储 key
|
|
17
|
+
*/
|
|
18
|
+
getKey(endpoint, method) {
|
|
19
|
+
return `${method}:${endpoint}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 生成文件名
|
|
23
|
+
*/
|
|
24
|
+
getFileName(endpoint, method) {
|
|
25
|
+
const sanitized = endpoint.replace(/^\//, "").replace(/\//g, "_").replace(/:/g, "_");
|
|
26
|
+
return `${method}_${sanitized}.json`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 获取数据
|
|
30
|
+
*/
|
|
31
|
+
get(endpoint, method = "GET") {
|
|
32
|
+
const key = this.getKey(endpoint, method);
|
|
33
|
+
const store = this.cache.get(key);
|
|
34
|
+
return store?.data;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 设置数据
|
|
38
|
+
*/
|
|
39
|
+
set(endpoint, method, data, metadata) {
|
|
40
|
+
const key = this.getKey(endpoint, method);
|
|
41
|
+
const store = {
|
|
42
|
+
endpoint,
|
|
43
|
+
method,
|
|
44
|
+
data,
|
|
45
|
+
metadata: {
|
|
46
|
+
count: Array.isArray(data) ? data.length : 1,
|
|
47
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
type: typeof data,
|
|
50
|
+
...metadata
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
this.cache.set(key, store);
|
|
54
|
+
if (this.persist) {
|
|
55
|
+
this.save(endpoint, method, store);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 删除数据
|
|
60
|
+
*/
|
|
61
|
+
delete(endpoint, method = "GET") {
|
|
62
|
+
const key = this.getKey(endpoint, method);
|
|
63
|
+
this.cache.delete(key);
|
|
64
|
+
if (this.persist) {
|
|
65
|
+
const fileName = this.getFileName(endpoint, method);
|
|
66
|
+
const filePath = path.join(this.storageDir, fileName);
|
|
67
|
+
if (fs.existsSync(filePath)) {
|
|
68
|
+
fs.unlinkSync(filePath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 清空所有数据
|
|
74
|
+
*/
|
|
75
|
+
clear() {
|
|
76
|
+
this.cache.clear();
|
|
77
|
+
if (this.persist && fs.existsSync(this.storageDir)) {
|
|
78
|
+
const files = fs.readdirSync(this.storageDir);
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
fs.unlinkSync(path.join(this.storageDir, file));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 获取所有端点
|
|
86
|
+
*/
|
|
87
|
+
getAll() {
|
|
88
|
+
return Array.from(this.cache.values());
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 保存到文件
|
|
92
|
+
*/
|
|
93
|
+
save(endpoint, method, store) {
|
|
94
|
+
const fileName = this.getFileName(endpoint, method);
|
|
95
|
+
const filePath = path.join(this.storageDir, fileName);
|
|
96
|
+
fs.writeFileSync(filePath, JSON.stringify(store, null, 2), "utf-8");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 从文件加载
|
|
100
|
+
*/
|
|
101
|
+
load() {
|
|
102
|
+
if (!this.persist || !fs.existsSync(this.storageDir)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const files = fs.readdirSync(this.storageDir);
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
if (!file.endsWith(".json")) continue;
|
|
108
|
+
try {
|
|
109
|
+
const filePath = path.join(this.storageDir, file);
|
|
110
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
111
|
+
const store = JSON.parse(content);
|
|
112
|
+
const key = this.getKey(store.endpoint, store.method);
|
|
113
|
+
this.cache.set(key, store);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn(`Failed to load mock data from ${file}:`, error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 导出所有数据
|
|
121
|
+
*/
|
|
122
|
+
export(outputPath) {
|
|
123
|
+
const allData = this.getAll();
|
|
124
|
+
fs.writeFileSync(outputPath, JSON.stringify(allData, null, 2), "utf-8");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 导入数据
|
|
128
|
+
*/
|
|
129
|
+
import(inputPath) {
|
|
130
|
+
const content = fs.readFileSync(inputPath, "utf-8");
|
|
131
|
+
const allData = JSON.parse(content);
|
|
132
|
+
for (const store of allData) {
|
|
133
|
+
const key = this.getKey(store.endpoint, store.method);
|
|
134
|
+
this.cache.set(key, store);
|
|
135
|
+
if (this.persist) {
|
|
136
|
+
this.save(store.endpoint, store.method, store);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/generator.ts
|
|
143
|
+
var MockDataGenerator = class {
|
|
144
|
+
constructor(options) {
|
|
145
|
+
this.apiKey = options.apiKey;
|
|
146
|
+
this.apiUrl = options.apiUrl;
|
|
147
|
+
this.model = options.model;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 生成 Mock 数据
|
|
151
|
+
*/
|
|
152
|
+
async generate(context) {
|
|
153
|
+
const { type, count, locale, quality } = context;
|
|
154
|
+
const prompt = this.buildPrompt(type, count, locale, quality);
|
|
155
|
+
const response = await this.callAI(prompt);
|
|
156
|
+
return this.parseResponse(response, type.isArray);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 构建 AI Prompt
|
|
160
|
+
*/
|
|
161
|
+
buildPrompt(type, count, locale, quality) {
|
|
162
|
+
const properties = type.properties.map((p) => this.formatProperty(p)).join("\n");
|
|
163
|
+
return `
|
|
164
|
+
\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 Mock \u6570\u636E\u751F\u6210\u5668\u3002\u8BF7\u6839\u636E\u4EE5\u4E0B\u7C7B\u578B\u5B9A\u4E49\u751F\u6210\u771F\u5B9E\u3001\u5408\u7406\u7684\u6D4B\u8BD5\u6570\u636E\u3002
|
|
165
|
+
|
|
166
|
+
\u7C7B\u578B\u540D\u79F0: ${type.name}
|
|
167
|
+
\u6570\u636E\u8BED\u8A00: ${locale === "zh-CN" ? "\u4E2D\u6587" : "\u82F1\u6587"}
|
|
168
|
+
\u6570\u636E\u6570\u91CF: ${count}
|
|
169
|
+
\u8D28\u91CF\u8981\u6C42: ${quality === "high" ? "\u9AD8\u8D28\u91CF\uFF08\u771F\u5B9E\u4E1A\u52A1\u6570\u636E\uFF09" : quality === "fast" ? "\u5FEB\u901F\u751F\u6210" : "\u5E73\u8861\u8D28\u91CF\u548C\u901F\u5EA6"}
|
|
170
|
+
|
|
171
|
+
\u7C7B\u578B\u5B9A\u4E49:
|
|
172
|
+
${properties}
|
|
173
|
+
|
|
174
|
+
\u8981\u6C42:
|
|
175
|
+
1. \u751F\u6210 ${count} \u6761\u6570\u636E
|
|
176
|
+
2. \u6570\u636E\u8981\u7B26\u5408\u4E1A\u52A1\u903B\u8F91\u548C\u771F\u5B9E\u573A\u666F
|
|
177
|
+
3. \u5B57\u6BB5\u503C\u8981\u5408\u7406\uFF08\u5982\u4EF7\u683C\u4E0D\u80FD\u4E3A\u8D1F\u6570\uFF0C\u5E74\u9F84\u5728\u5408\u7406\u8303\u56F4\uFF09
|
|
178
|
+
4. \u65E5\u671F\u683C\u5F0F\u4F7F\u7528 ISO 8601
|
|
179
|
+
5. \u8FD4\u56DE JSON \u6570\u7EC4\u683C\u5F0F\uFF0C\u4E0D\u8981\u5305\u542B\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57
|
|
180
|
+
6. \u7406\u89E3\u5B57\u6BB5\u8BED\u4E49\uFF0C\u751F\u6210\u771F\u5B9E\u6570\u636E\uFF08\u5982 userName \u751F\u6210\u771F\u5B9E\u59D3\u540D\uFF09
|
|
181
|
+
|
|
182
|
+
\u793A\u4F8B\u683C\u5F0F:
|
|
183
|
+
[
|
|
184
|
+
{
|
|
185
|
+
"id": 1,
|
|
186
|
+
"name": "\u5F20\u4E09",
|
|
187
|
+
...
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
\u8BF7\u751F\u6210\u6570\u636E:
|
|
192
|
+
`.trim();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 格式化属性
|
|
196
|
+
*/
|
|
197
|
+
formatProperty(prop) {
|
|
198
|
+
let line = `- ${prop.name}: ${prop.type}`;
|
|
199
|
+
if (prop.comment) {
|
|
200
|
+
line += ` // ${prop.comment}`;
|
|
201
|
+
}
|
|
202
|
+
if (prop.constraints) {
|
|
203
|
+
const constraints = [];
|
|
204
|
+
if (prop.constraints.min !== void 0) {
|
|
205
|
+
constraints.push(`min: ${prop.constraints.min}`);
|
|
206
|
+
}
|
|
207
|
+
if (prop.constraints.max !== void 0) {
|
|
208
|
+
constraints.push(`max: ${prop.constraints.max}`);
|
|
209
|
+
}
|
|
210
|
+
if (prop.constraints.unique) {
|
|
211
|
+
constraints.push("unique");
|
|
212
|
+
}
|
|
213
|
+
if (prop.constraints.enum) {
|
|
214
|
+
constraints.push(`enum: [${prop.constraints.enum.join(", ")}]`);
|
|
215
|
+
}
|
|
216
|
+
if (constraints.length > 0) {
|
|
217
|
+
line += ` (${constraints.join(", ")})`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return line;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 调用 AI API
|
|
224
|
+
*/
|
|
225
|
+
async callAI(prompt) {
|
|
226
|
+
try {
|
|
227
|
+
const response = await fetch(`${this.apiUrl}/chat/completions`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: {
|
|
230
|
+
"Content-Type": "application/json",
|
|
231
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
232
|
+
},
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
model: this.model,
|
|
235
|
+
messages: [
|
|
236
|
+
{
|
|
237
|
+
role: "user",
|
|
238
|
+
content: prompt
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
temperature: 0.7
|
|
242
|
+
})
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw new Error(`AI API error: ${response.statusText}`);
|
|
246
|
+
}
|
|
247
|
+
const data = await response.json();
|
|
248
|
+
return data.choices[0].message.content;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error("Failed to call AI API:", error);
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 解析 AI 响应
|
|
256
|
+
*/
|
|
257
|
+
parseResponse(response, isArray = true) {
|
|
258
|
+
try {
|
|
259
|
+
let cleanedResponse = response.trim();
|
|
260
|
+
cleanedResponse = cleanedResponse.replace(/^```json\s*/i, "");
|
|
261
|
+
cleanedResponse = cleanedResponse.replace(/^```\s*/, "");
|
|
262
|
+
cleanedResponse = cleanedResponse.replace(/\s*```$/, "");
|
|
263
|
+
const jsonMatch = cleanedResponse.match(/(\[[\s\S]*\]|\{[\s\S]*\})/);
|
|
264
|
+
if (!jsonMatch) {
|
|
265
|
+
throw new Error("No JSON array or object found in response");
|
|
266
|
+
}
|
|
267
|
+
const data = JSON.parse(jsonMatch[0]);
|
|
268
|
+
return isArray ? data : data[0];
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error("Failed to parse AI response:", error);
|
|
271
|
+
console.error("Response:", response);
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 生成基础数据(不使用 AI)
|
|
277
|
+
*/
|
|
278
|
+
generateBasic(type, count) {
|
|
279
|
+
const data = [];
|
|
280
|
+
for (let i = 0; i < count; i++) {
|
|
281
|
+
const item = {};
|
|
282
|
+
for (const prop of type.properties) {
|
|
283
|
+
item[prop.name] = this.generateBasicValue(prop, i);
|
|
284
|
+
}
|
|
285
|
+
data.push(item);
|
|
286
|
+
}
|
|
287
|
+
return data;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 生成基础值
|
|
291
|
+
*/
|
|
292
|
+
generateBasicValue(prop, index) {
|
|
293
|
+
const { type, constraints } = prop;
|
|
294
|
+
if (constraints?.enum) {
|
|
295
|
+
return constraints.enum[index % constraints.enum.length];
|
|
296
|
+
}
|
|
297
|
+
switch (type) {
|
|
298
|
+
case "number":
|
|
299
|
+
const min = constraints?.min ?? 0;
|
|
300
|
+
const max = constraints?.max ?? 100;
|
|
301
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
302
|
+
case "string":
|
|
303
|
+
return `${prop.name}_${index + 1}`;
|
|
304
|
+
case "boolean":
|
|
305
|
+
return Math.random() > 0.5;
|
|
306
|
+
case "Date":
|
|
307
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
308
|
+
default:
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/utils.ts
|
|
315
|
+
function delay(ms) {
|
|
316
|
+
const delayTime = Array.isArray(ms) ? Math.random() * (ms[1] - ms[0]) + ms[0] : ms;
|
|
317
|
+
return new Promise((resolve) => setTimeout(resolve, delayTime));
|
|
318
|
+
}
|
|
319
|
+
function matchPathParams(pattern, path2) {
|
|
320
|
+
const patternParts = pattern.split("/");
|
|
321
|
+
const pathParts = path2.split("/");
|
|
322
|
+
if (patternParts.length !== pathParts.length) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const params = {};
|
|
326
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
327
|
+
const patternPart = patternParts[i];
|
|
328
|
+
const pathPart = pathParts[i];
|
|
329
|
+
if (patternPart.startsWith(":")) {
|
|
330
|
+
const paramName = patternPart.slice(1);
|
|
331
|
+
params[paramName] = pathPart;
|
|
332
|
+
} else if (patternPart !== pathPart) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return params;
|
|
337
|
+
}
|
|
338
|
+
function parseQueryParams(url) {
|
|
339
|
+
const queryString = url.split("?")[1];
|
|
340
|
+
if (!queryString) return {};
|
|
341
|
+
const params = {};
|
|
342
|
+
const pairs = queryString.split("&");
|
|
343
|
+
for (const pair of pairs) {
|
|
344
|
+
const [key, value] = pair.split("=");
|
|
345
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
|
|
346
|
+
}
|
|
347
|
+
return params;
|
|
348
|
+
}
|
|
349
|
+
function applyFilters(data, filters) {
|
|
350
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
351
|
+
return data;
|
|
352
|
+
}
|
|
353
|
+
return data.filter((item) => {
|
|
354
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
355
|
+
if (item[key] !== value) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function applySorting(data, sort) {
|
|
363
|
+
if (!sort) return data;
|
|
364
|
+
const sortConfig = typeof sort === "string" ? { field: sort, order: "asc" } : sort;
|
|
365
|
+
return [...data].sort((a, b) => {
|
|
366
|
+
const aValue = a[sortConfig.field];
|
|
367
|
+
const bValue = b[sortConfig.field];
|
|
368
|
+
if (aValue < bValue) return sortConfig.order === "asc" ? -1 : 1;
|
|
369
|
+
if (aValue > bValue) return sortConfig.order === "asc" ? 1 : -1;
|
|
370
|
+
return 0;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
function applyPagination(data, page = 1, pageSize = 20) {
|
|
374
|
+
const total = data.length;
|
|
375
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
376
|
+
const start = (page - 1) * pageSize;
|
|
377
|
+
const end = start + pageSize;
|
|
378
|
+
return {
|
|
379
|
+
data: data.slice(start, end),
|
|
380
|
+
pagination: {
|
|
381
|
+
page,
|
|
382
|
+
pageSize,
|
|
383
|
+
total,
|
|
384
|
+
totalPages
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function formatResponse(data, success = true, message) {
|
|
389
|
+
return {
|
|
390
|
+
code: success ? 200 : 500,
|
|
391
|
+
data,
|
|
392
|
+
message: message || (success ? "Success" : "Error")
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/server.ts
|
|
397
|
+
var MockServer = class {
|
|
398
|
+
constructor(storage, options) {
|
|
399
|
+
this.storage = storage;
|
|
400
|
+
this.endpoints = options.endpoints || [];
|
|
401
|
+
this.options = options;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 配置服务器中间件
|
|
405
|
+
*/
|
|
406
|
+
configureServer(server) {
|
|
407
|
+
server.middlewares.use(async (req, res, next) => {
|
|
408
|
+
const url = req.url || "";
|
|
409
|
+
const method = req.method;
|
|
410
|
+
const endpoint = this.matchEndpoint(url, method);
|
|
411
|
+
if (!endpoint) {
|
|
412
|
+
return next();
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const params = this.parseRequest(req, url);
|
|
416
|
+
const data = await this.getMockData(endpoint, params);
|
|
417
|
+
if (this.options.server?.delay) {
|
|
418
|
+
await delay(this.options.server.delay);
|
|
419
|
+
}
|
|
420
|
+
res.setHeader("Content-Type", "application/json");
|
|
421
|
+
if (this.options.server?.cors) {
|
|
422
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
423
|
+
res.setHeader("Access-Control-Allow-Methods", "*");
|
|
424
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
425
|
+
}
|
|
426
|
+
res.statusCode = 200;
|
|
427
|
+
res.end(JSON.stringify(data));
|
|
428
|
+
if (this.options.output?.logs) {
|
|
429
|
+
console.log(`[Mock] ${method} ${url} \u2192 ${data ? "OK" : "Empty"}`);
|
|
430
|
+
}
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.error(`[Mock] Error handling ${method} ${url}:`, error);
|
|
433
|
+
res.statusCode = 500;
|
|
434
|
+
res.end(
|
|
435
|
+
JSON.stringify({
|
|
436
|
+
code: 500,
|
|
437
|
+
message: error.message
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
console.log("\n\u{1F3AD} Mock \u670D\u52A1\u5668\u5DF2\u542F\u52A8");
|
|
443
|
+
console.log(`\u{1F4CD} \u5DF2\u6CE8\u518C ${this.endpoints.length} \u4E2A\u7AEF\u70B9
|
|
444
|
+
`);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 匹配端点
|
|
448
|
+
*/
|
|
449
|
+
matchEndpoint(url, method) {
|
|
450
|
+
const path2 = url.split("?")[0];
|
|
451
|
+
const prefix = this.options.server?.prefix || "";
|
|
452
|
+
const cleanPath = prefix ? path2.replace(new RegExp(`^${prefix}`), "") : path2;
|
|
453
|
+
for (const endpoint of this.endpoints) {
|
|
454
|
+
if (endpoint.enabled === false) continue;
|
|
455
|
+
if (endpoint.method !== method) continue;
|
|
456
|
+
if (endpoint.path === cleanPath) {
|
|
457
|
+
return endpoint;
|
|
458
|
+
}
|
|
459
|
+
if (matchPathParams(endpoint.path, cleanPath)) {
|
|
460
|
+
return endpoint;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* 解析请求
|
|
467
|
+
*/
|
|
468
|
+
parseRequest(req, url) {
|
|
469
|
+
const query = parseQueryParams(url);
|
|
470
|
+
const path2 = url.split("?")[0];
|
|
471
|
+
const endpoint = this.matchEndpoint(path2, req.method);
|
|
472
|
+
const pathParams = endpoint ? matchPathParams(endpoint.path, path2.split("?")[0]) : null;
|
|
473
|
+
return {
|
|
474
|
+
query,
|
|
475
|
+
params: pathParams || {},
|
|
476
|
+
body: req.body,
|
|
477
|
+
headers: req.headers
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* 获取 Mock 数据
|
|
482
|
+
*/
|
|
483
|
+
async getMockData(endpoint, params) {
|
|
484
|
+
let data = this.storage.get(endpoint.path, endpoint.method);
|
|
485
|
+
if (!data) {
|
|
486
|
+
console.warn(
|
|
487
|
+
`[Mock] No data found for ${endpoint.method} ${endpoint.path}`
|
|
488
|
+
);
|
|
489
|
+
return formatResponse([], false, "No mock data available");
|
|
490
|
+
}
|
|
491
|
+
if (Array.isArray(data)) {
|
|
492
|
+
if (params.query.filter) {
|
|
493
|
+
data = applyFilters(data, JSON.parse(params.query.filter));
|
|
494
|
+
}
|
|
495
|
+
if (params.query.sort) {
|
|
496
|
+
data = applySorting(data, params.query.sort);
|
|
497
|
+
}
|
|
498
|
+
if (params.query.page || params.query.pageSize) {
|
|
499
|
+
const page = parseInt(params.query.page) || 1;
|
|
500
|
+
const pageSize = parseInt(params.query.pageSize) || 20;
|
|
501
|
+
const result = applyPagination(data, page, pageSize);
|
|
502
|
+
return formatResponse({
|
|
503
|
+
list: result.data,
|
|
504
|
+
pagination: result.pagination
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (endpoint.custom) {
|
|
509
|
+
data = await endpoint.custom(data, params);
|
|
510
|
+
}
|
|
511
|
+
return formatResponse(data);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* 添加端点
|
|
515
|
+
*/
|
|
516
|
+
addEndpoint(endpoint) {
|
|
517
|
+
this.endpoints.push(endpoint);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* 移除端点
|
|
521
|
+
*/
|
|
522
|
+
removeEndpoint(path2, method) {
|
|
523
|
+
this.endpoints = this.endpoints.filter(
|
|
524
|
+
(e) => !(e.path === path2 && e.method === method)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* 获取所有端点
|
|
529
|
+
*/
|
|
530
|
+
getEndpoints() {
|
|
531
|
+
return this.endpoints;
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// src/index.ts
|
|
536
|
+
function vitePluginAIMockGenerator(options = {}) {
|
|
537
|
+
const {
|
|
538
|
+
apiKey = process.env.OPENAI_API_KEY || "",
|
|
539
|
+
apiUrl = process.env.OPENAI_API_URL || "https://api.openai.com/v1",
|
|
540
|
+
model = process.env.OPENAI_MODEL || "gpt-4",
|
|
541
|
+
enabled = true,
|
|
542
|
+
autoGenerate = false,
|
|
543
|
+
generation = {
|
|
544
|
+
locale: "zh-CN",
|
|
545
|
+
count: 20,
|
|
546
|
+
quality: "balanced"
|
|
547
|
+
},
|
|
548
|
+
storage: storageOptions = {
|
|
549
|
+
dir: "mock-data",
|
|
550
|
+
persist: true,
|
|
551
|
+
cache: true
|
|
552
|
+
},
|
|
553
|
+
output = {
|
|
554
|
+
console: true,
|
|
555
|
+
logs: false
|
|
556
|
+
}
|
|
557
|
+
} = options;
|
|
558
|
+
if (!enabled) {
|
|
559
|
+
return {
|
|
560
|
+
name: "vite-plugin-ai-mock-generator"
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const storage = new MockStorage(storageOptions);
|
|
564
|
+
const generator = new MockDataGenerator({ apiKey, apiUrl, model });
|
|
565
|
+
const server = new MockServer(storage, options);
|
|
566
|
+
return {
|
|
567
|
+
name: "vite-plugin-ai-mock-generator",
|
|
568
|
+
enforce: "pre",
|
|
569
|
+
configResolved(config) {
|
|
570
|
+
if (output.console) {
|
|
571
|
+
console.log("\n\u{1F916} AI Mock Generator \u5DF2\u542F\u52A8");
|
|
572
|
+
console.log(`\u{1F4C2} \u5B58\u50A8\u76EE\u5F55: ${storageOptions.dir}`);
|
|
573
|
+
console.log(`\u{1F30D} \u6570\u636E\u8BED\u8A00: ${generation.locale}`);
|
|
574
|
+
console.log(`\u{1F4CA} \u9ED8\u8BA4\u6570\u91CF: ${generation.count}`);
|
|
575
|
+
console.log(`\u{1F511} API Key: ${apiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
|
|
576
|
+
console.log(
|
|
577
|
+
`\u{1F4CD} \u7AEF\u70B9\u6570\u91CF: ${options.endpoints?.length || 0}`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
async buildStart() {
|
|
582
|
+
if (autoGenerate && options.endpoints) {
|
|
583
|
+
if (output.console) {
|
|
584
|
+
console.log("\n\u{1F504} \u5F00\u59CB\u81EA\u52A8\u751F\u6210 Mock \u6570\u636E...\n");
|
|
585
|
+
}
|
|
586
|
+
for (const endpoint of options.endpoints) {
|
|
587
|
+
const existingData = storage.get(endpoint.path, endpoint.method);
|
|
588
|
+
if (existingData) {
|
|
589
|
+
if (output.console) {
|
|
590
|
+
console.log(
|
|
591
|
+
`\u23ED\uFE0F \u8DF3\u8FC7 ${endpoint.method} ${endpoint.path} (\u5DF2\u6709\u6570\u636E)`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
if (output.console) {
|
|
598
|
+
console.log(`\u{1F3B2} \u751F\u6210 ${endpoint.method} ${endpoint.path}...`);
|
|
599
|
+
}
|
|
600
|
+
const typeDefinition = parseTypeDefinition(endpoint.response);
|
|
601
|
+
const count = endpoint.count || generation.count || 20;
|
|
602
|
+
let data;
|
|
603
|
+
if (generation.quality === "fast") {
|
|
604
|
+
data = generator.generateBasic(typeDefinition, count);
|
|
605
|
+
} else {
|
|
606
|
+
data = await generator.generate({
|
|
607
|
+
type: typeDefinition,
|
|
608
|
+
count,
|
|
609
|
+
locale: generation.locale || "zh-CN",
|
|
610
|
+
quality: generation.quality || "balanced"
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
storage.set(endpoint.path, endpoint.method, data, {
|
|
614
|
+
type: endpoint.response
|
|
615
|
+
});
|
|
616
|
+
if (output.console) {
|
|
617
|
+
console.log(
|
|
618
|
+
`\u2705 \u5DF2\u751F\u6210 ${count} \u6761\u6570\u636E: ${endpoint.method} ${endpoint.path}`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
console.error(
|
|
623
|
+
`\u274C \u751F\u6210\u5931\u8D25 ${endpoint.method} ${endpoint.path}:`,
|
|
624
|
+
error.message
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (output.console) {
|
|
629
|
+
console.log("\n\u2728 Mock \u6570\u636E\u751F\u6210\u5B8C\u6210\n");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
configureServer(viteServer) {
|
|
634
|
+
server.configureServer(viteServer);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function parseTypeDefinition(typeStr) {
|
|
639
|
+
if (typeof typeStr === "object") {
|
|
640
|
+
return typeStr;
|
|
641
|
+
}
|
|
642
|
+
const isArray = typeStr.endsWith("[]");
|
|
643
|
+
const typeName = isArray ? typeStr.slice(0, -2) : typeStr;
|
|
644
|
+
return {
|
|
645
|
+
name: typeName,
|
|
646
|
+
properties: [],
|
|
647
|
+
isArray
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export { MockDataGenerator, MockServer, MockStorage, vitePluginAIMockGenerator };
|
|
652
|
+
//# sourceMappingURL=index.mjs.map
|
|
653
|
+
//# sourceMappingURL=index.mjs.map
|