whistle.mockbubu 2.0.0 → 2.1.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/README.md +38 -0
- package/index.js +3 -3
- package/lib/config/const.js +138 -0
- package/lib/config/rule-collector.js +81 -0
- package/lib/constants.js +62 -0
- package/lib/core/memory-buffer/index.js +207 -0
- package/lib/core/memory-buffer/shared-instance.js +15 -0
- package/lib/core/plugin-mode-manager.js +74 -0
- package/lib/core/resRulesServer.js +14 -0
- package/lib/core/rulesServer.js +31 -0
- package/lib/core/server-entry/capture-handler.js +191 -0
- package/lib/core/server-entry/request-interceptor.js +82 -0
- package/lib/core/server-entry/response-handler.js +147 -0
- package/lib/core/server-entry/server.js +230 -0
- package/lib/storage/group-manager.js +627 -0
- package/lib/storage/storage-adapter.js +712 -0
- package/lib/storage/storage-v3.js +1418 -0
- package/lib/uiServer/index.js +61 -24
- package/lib/uiServer/router/export/import-export-router.js +459 -0
- package/lib/uiServer/router/files/api-list-router.js +150 -0
- package/lib/uiServer/router/files/batch-operations-router.js +185 -0
- package/lib/uiServer/router/files/file-config-router.js +118 -0
- package/lib/uiServer/router/files/file-crud-router.js +212 -0
- package/lib/uiServer/router/files/file-save-router.js +146 -0
- package/lib/uiServer/router/files/version-router.js +260 -0
- package/lib/uiServer/router/global/plugin-control.js +135 -0
- package/lib/uiServer/router/global/system-info-router.js +386 -0
- package/lib/uiServer/router/{group-router.js → groups/group-router.js} +21 -20
- package/lib/uiServer/router/index.js +38 -1521
- package/lib/uiServer/utils/router-helpers.js +100 -0
- package/lib/uiServer/utils/util.js +172 -0
- package/lib/uiServer/{validator.js → utils/validator.js} +11 -6
- package/lib/utils/archive-utils.js +788 -0
- package/lib/utils/error-handler.js +173 -0
- package/lib/utils/logger.js +79 -0
- package/lib/utils/path-utils.js +147 -0
- package/lib/utils/performance.js +265 -0
- package/lib/utils/utils.js +541 -0
- package/package.json +2 -2
- package/public/js/app.js +3732 -1929
- package/public/js/app.js.map +1 -1
- package/public/js/chunk-vendors.js +5098 -3965
- package/public/js/chunk-vendors.js.map +1 -1
- package/rules.txt +1 -1
- package/CHANGELOG_GROUP_FEATURE.md +0 -468
- package/CHANGELOG_P0_FIXES.md +0 -412
- package/CHANGELOG_P1_OPTIMIZATIONS.md +0 -292
- package/CLAUDE.md +0 -436
- package/GROUP_FEATURE_DESIGN.md +0 -520
- package/lib/const.js +0 -47
- package/lib/group-manager.js +0 -491
- package/lib/resRulesServer.js +0 -9
- package/lib/server.js +0 -249
- package/lib/uiServer/router/version-router.js +0 -205
- package/lib/uiServer/util.js +0 -153
- package/lib/utils.js +0 -409
package/lib/utils.js
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
const zlib = require('zlib')
|
|
2
|
-
const { RuleValueMap } = require('./const')
|
|
3
|
-
// const URL = require('url')
|
|
4
|
-
const setProperty = (storage, property, val) => {
|
|
5
|
-
if (storage.hasProperty(property)) {
|
|
6
|
-
const oldVal = storage.getProperty(property)
|
|
7
|
-
|
|
8
|
-
if (typeof oldVal === 'object') {
|
|
9
|
-
storage.setProperty(property, Object.assign(oldVal, val))
|
|
10
|
-
} else {
|
|
11
|
-
storage.setProperty(property, val)
|
|
12
|
-
}
|
|
13
|
-
} else {
|
|
14
|
-
storage.setProperty(property, val)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const removePropertyAttr = (storage, property, attrName) => {
|
|
19
|
-
if (storage.hasProperty(property)) {
|
|
20
|
-
const oldVal = storage.getProperty(property)
|
|
21
|
-
|
|
22
|
-
if (typeof oldVal === 'object') {
|
|
23
|
-
delete oldVal[attrName]
|
|
24
|
-
storage.setProperty(property, oldVal)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const getPropertyAttr = (storage, property, attrName) => {
|
|
29
|
-
const props = storage.getProperty(property)
|
|
30
|
-
|
|
31
|
-
return props ? props[attrName] : undefined
|
|
32
|
-
}
|
|
33
|
-
const getProperty = (storage, property) => {
|
|
34
|
-
return storage.getProperty(property)
|
|
35
|
-
}
|
|
36
|
-
const removeProperty = (storage, property) => {
|
|
37
|
-
return storage.removeProperty(property)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const writeFile = ({ storage, filename, body, properties, encoding, groupId }) => {
|
|
41
|
-
switch (encoding) {
|
|
42
|
-
case 'gzip':
|
|
43
|
-
body = zlib.gunzipSync(body)
|
|
44
|
-
break
|
|
45
|
-
case 'deflate':
|
|
46
|
-
body = zlib.inflateSync(body)
|
|
47
|
-
break
|
|
48
|
-
case 'br':
|
|
49
|
-
body = zlib.brotliDecompressSync(body)
|
|
50
|
-
break
|
|
51
|
-
default:
|
|
52
|
-
break
|
|
53
|
-
}
|
|
54
|
-
setProperty(storage, filename, properties || {})
|
|
55
|
-
|
|
56
|
-
const type = Object.prototype.toString.call(body)
|
|
57
|
-
let content
|
|
58
|
-
switch (type) {
|
|
59
|
-
case '[object String]':
|
|
60
|
-
content = body
|
|
61
|
-
break
|
|
62
|
-
case '[object Object]':
|
|
63
|
-
content = JSON.stringify(body)
|
|
64
|
-
break
|
|
65
|
-
case '[object Uint8Array]':
|
|
66
|
-
content = body.toString()
|
|
67
|
-
break
|
|
68
|
-
default:
|
|
69
|
-
content = body
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 使用组目录结构: groupId/filename
|
|
73
|
-
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
74
|
-
storage.writeFile(filePath, content)
|
|
75
|
-
setApiListUpdated(storage, true)
|
|
76
|
-
// switch (type) {
|
|
77
|
-
// case '[object String]':
|
|
78
|
-
// storage.writeFile(filename, body)
|
|
79
|
-
// break
|
|
80
|
-
// case '[object Object]':
|
|
81
|
-
// storage.writeFile(filename, JSON.stringify(body))
|
|
82
|
-
// break
|
|
83
|
-
// case '[object Uint8Array]':
|
|
84
|
-
// storage.writeFile(filename, body.toString())
|
|
85
|
-
// break
|
|
86
|
-
// default:
|
|
87
|
-
// storage.writeFile(filename, body) // TODO:
|
|
88
|
-
// }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ⚠️ 临时保留:为向后兼容暂时保留(将在 Phase 2 完成后移除)
|
|
92
|
-
const getApiList = (storage) => {
|
|
93
|
-
return storage.getFileList()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ✅ 新增:按组获取文件列表
|
|
97
|
-
const getGroupFileList = (storage, groupId) => {
|
|
98
|
-
const allFiles = storage.getFileList()
|
|
99
|
-
const prefix = `${groupId}/`
|
|
100
|
-
return allFiles
|
|
101
|
-
.filter(item => item.name.startsWith(prefix))
|
|
102
|
-
.map(item => ({
|
|
103
|
-
...item,
|
|
104
|
-
name: item.name.replace(prefix, ''), // 移除前缀,返回原始文件名
|
|
105
|
-
}))
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const updateFile = (storage, filename, body, groupId) => {
|
|
109
|
-
if (!filename) {
|
|
110
|
-
throw new Error('文件名不能为空')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
114
|
-
const sessionCache = storage.readFile(filePath)
|
|
115
|
-
if (sessionCache) {
|
|
116
|
-
const session = JSON.parse(sessionCache)
|
|
117
|
-
session.res.body = JSON.stringify(body)
|
|
118
|
-
storage.writeFile(filePath, JSON.stringify(session))
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const readFile = (storage, filename, groupId) => {
|
|
123
|
-
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
124
|
-
const sessionCache = storage.readFile(filePath)
|
|
125
|
-
if (sessionCache) {
|
|
126
|
-
const resCache = JSON.parse(sessionCache)?.res
|
|
127
|
-
return resCache.body
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 读取完整的 session 数据(包含 req 和 res)
|
|
132
|
-
const readSession = (storage, filename, groupId) => {
|
|
133
|
-
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
134
|
-
const sessionCache = storage.readFile(filePath)
|
|
135
|
-
if (sessionCache) {
|
|
136
|
-
return JSON.parse(sessionCache)
|
|
137
|
-
}
|
|
138
|
-
return null
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const removeFile = (storage, name, groupId) => {
|
|
142
|
-
const filePath = groupId ? `${groupId}/${name}` : name
|
|
143
|
-
console.log(`[mockbubu] ❌ removeFile 被调用,彻底删除文件: ${filePath}`)
|
|
144
|
-
storage.removeFile(filePath)
|
|
145
|
-
// 注意:不再删除 properties,因为元数据现在由 groupManager 管理
|
|
146
|
-
}
|
|
147
|
-
// 获取mock文件名
|
|
148
|
-
const getFilename = (originalReq) => {
|
|
149
|
-
const { url, ruleValue, pattern } = originalReq
|
|
150
|
-
const u = new URL(url)
|
|
151
|
-
let filename = url
|
|
152
|
-
|
|
153
|
-
switch (ruleValue) {
|
|
154
|
-
case RuleValueMap.href:
|
|
155
|
-
filename = u.href
|
|
156
|
-
break
|
|
157
|
-
case RuleValueMap.pathname:
|
|
158
|
-
filename = u.origin + u.pathname
|
|
159
|
-
break
|
|
160
|
-
case RuleValueMap.pattern:
|
|
161
|
-
filename = pattern
|
|
162
|
-
break
|
|
163
|
-
default:
|
|
164
|
-
filename = u.origin + u.pathname
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return filename
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const getApiListUpdated = (storage) => {
|
|
171
|
-
return !!storage.getProperty('api-list-updated')
|
|
172
|
-
}
|
|
173
|
-
const setApiListUpdated = (storage, updated) => {
|
|
174
|
-
return storage.setProperty('api-list-updated', updated)
|
|
175
|
-
}
|
|
176
|
-
const getRule = (originalReq) => {
|
|
177
|
-
const { pattern, ruleValue } = originalReq
|
|
178
|
-
|
|
179
|
-
return `${pattern} mockbubu://${ruleValue}`
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const isJsonReq = (headers) => {
|
|
183
|
-
return (
|
|
184
|
-
(headers.accept && headers.accept.includes('application/json')) ||
|
|
185
|
-
headers['sec-fetch-dest'] === 'empty' ||
|
|
186
|
-
headers['Sec-Fetch-Dest'] === 'empty'
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function getVersionName(name) {
|
|
191
|
-
return 'version.' + name
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function getVersionMetaName(name) {
|
|
195
|
-
return 'versionMeta.' + name
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const MAX_VERSION_COUNT = 10 // 最多保留10个版本
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* mock版本管理
|
|
202
|
-
*/
|
|
203
|
-
|
|
204
|
-
// 新增版本
|
|
205
|
-
const addNewVersion = ({ storage, filename, versionName, content, description = '' }) => {
|
|
206
|
-
const name = getVersionName(versionName)
|
|
207
|
-
const metaName = getVersionMetaName(versionName)
|
|
208
|
-
|
|
209
|
-
// 检查版本数量限制
|
|
210
|
-
const versions = getVersions({ storage, filename })
|
|
211
|
-
if (versions.length >= MAX_VERSION_COUNT) {
|
|
212
|
-
throw new Error(`版本数量已达上限(${MAX_VERSION_COUNT}个),请删除旧版本后再创建`)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 保存版本内容和元数据
|
|
216
|
-
const now = Date.now()
|
|
217
|
-
setProperty(storage, filename, {
|
|
218
|
-
[name]: content,
|
|
219
|
-
[metaName]: {
|
|
220
|
-
description,
|
|
221
|
-
createTime: now,
|
|
222
|
-
updateTime: now,
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
}
|
|
226
|
-
// 更新版本文件内容
|
|
227
|
-
const updateVersionContent = ({ storage, filename, versionName, content }) => {
|
|
228
|
-
const name = getVersionName(versionName)
|
|
229
|
-
const metaName = getVersionMetaName(versionName)
|
|
230
|
-
|
|
231
|
-
// 更新内容和修改时间
|
|
232
|
-
const meta = getPropertyAttr(storage, filename, metaName) || {}
|
|
233
|
-
setProperty(storage, filename, {
|
|
234
|
-
[name]: content,
|
|
235
|
-
[metaName]: {
|
|
236
|
-
...meta,
|
|
237
|
-
updateTime: Date.now(),
|
|
238
|
-
},
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
// 获取版本文件内容
|
|
242
|
-
const getVersionContent = ({ storage, filename, versionName }) => {
|
|
243
|
-
const name = getVersionName(versionName)
|
|
244
|
-
|
|
245
|
-
return getPropertyAttr(storage, filename, name)
|
|
246
|
-
}
|
|
247
|
-
// 删除版本
|
|
248
|
-
const deleteVersion = ({ storage, filename, versionName }) => {
|
|
249
|
-
const name = getVersionName(versionName)
|
|
250
|
-
const metaName = getVersionMetaName(versionName)
|
|
251
|
-
|
|
252
|
-
removePropertyAttr(storage, filename, name)
|
|
253
|
-
removePropertyAttr(storage, filename, metaName)
|
|
254
|
-
}
|
|
255
|
-
// 更新版本名称
|
|
256
|
-
const updateVersionName = ({ storage, filename, versionName, newVersion }) => {
|
|
257
|
-
const content = getVersionContent({ storage, filename, versionName })
|
|
258
|
-
const oldMetaName = getVersionMetaName(versionName)
|
|
259
|
-
const meta = getPropertyAttr(storage, filename, oldMetaName) || {}
|
|
260
|
-
|
|
261
|
-
// 删除旧版本
|
|
262
|
-
deleteVersion({ storage, filename, versionName })
|
|
263
|
-
|
|
264
|
-
// 创建新版本(保留元数据,但不增加版本数量限制检查)
|
|
265
|
-
const name = getVersionName(newVersion)
|
|
266
|
-
const metaName = getVersionMetaName(newVersion)
|
|
267
|
-
setProperty(storage, filename, {
|
|
268
|
-
[name]: content,
|
|
269
|
-
[metaName]: {
|
|
270
|
-
...meta,
|
|
271
|
-
updateTime: Date.now(),
|
|
272
|
-
},
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 更新版本元信息(名称和描述)
|
|
277
|
-
const updateVersionMeta = ({ storage, filename, versionName, newVersionName, description }) => {
|
|
278
|
-
const content = getVersionContent({ storage, filename, versionName })
|
|
279
|
-
if (!content) {
|
|
280
|
-
throw new Error('版本不存在')
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const oldMetaName = getVersionMetaName(versionName)
|
|
284
|
-
const meta = getPropertyAttr(storage, filename, oldMetaName) || {}
|
|
285
|
-
|
|
286
|
-
// 如果版本名变了,删除旧版本
|
|
287
|
-
if (versionName !== newVersionName) {
|
|
288
|
-
deleteVersion({ storage, filename, versionName })
|
|
289
|
-
} else {
|
|
290
|
-
// 只删除旧的元数据
|
|
291
|
-
removePropertyAttr(storage, filename, oldMetaName)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// 保存新的版本和元数据
|
|
295
|
-
const newName = getVersionName(newVersionName)
|
|
296
|
-
const newMetaName = getVersionMetaName(newVersionName)
|
|
297
|
-
setProperty(storage, filename, {
|
|
298
|
-
[newName]: content,
|
|
299
|
-
[newMetaName]: {
|
|
300
|
-
...meta,
|
|
301
|
-
description,
|
|
302
|
-
updateTime: Date.now(),
|
|
303
|
-
},
|
|
304
|
-
})
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const getVersions = ({ storage, filename }) => {
|
|
308
|
-
const props = getProperty(storage, filename)
|
|
309
|
-
if (!props) {
|
|
310
|
-
return []
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const list = []
|
|
314
|
-
|
|
315
|
-
Object.keys(props).forEach((key) => {
|
|
316
|
-
const matchs = key.match(/^version\.(.+)/)
|
|
317
|
-
|
|
318
|
-
if (matchs && matchs[1]) {
|
|
319
|
-
const versionName = matchs[1]
|
|
320
|
-
const metaName = getVersionMetaName(versionName)
|
|
321
|
-
const meta = props[metaName] || {}
|
|
322
|
-
|
|
323
|
-
list.push({
|
|
324
|
-
filename: versionName,
|
|
325
|
-
content: props[key],
|
|
326
|
-
description: meta.description || '',
|
|
327
|
-
createTime: meta.createTime || 0,
|
|
328
|
-
updateTime: meta.updateTime || 0,
|
|
329
|
-
})
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
// 按修改时间倒序排列(最新修改的在前)
|
|
334
|
-
return list.sort((a, b) => b.updateTime - a.updateTime)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const withTryCatch = (fn) => {
|
|
338
|
-
return async (...args) => {
|
|
339
|
-
try {
|
|
340
|
-
return await fn(...args)
|
|
341
|
-
} catch (e) {
|
|
342
|
-
// 仅在开发环境输出错误日志
|
|
343
|
-
if (process.env.NODE_ENV === 'development') {
|
|
344
|
-
console.error('[mockbubu] 全局捕获异常:', e)
|
|
345
|
-
}
|
|
346
|
-
throw e
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
exports.addNewVersion = addNewVersion
|
|
352
|
-
exports.updateVersionContent = updateVersionContent
|
|
353
|
-
exports.deleteVersion = deleteVersion
|
|
354
|
-
exports.updateVersionName = updateVersionName
|
|
355
|
-
exports.updateVersionMeta = updateVersionMeta
|
|
356
|
-
exports.getVersions = getVersions
|
|
357
|
-
exports.setProperty = setProperty
|
|
358
|
-
exports.getProperty = getProperty
|
|
359
|
-
exports.getPropertyAttr = getPropertyAttr
|
|
360
|
-
exports.getVersionContent = getVersionContent
|
|
361
|
-
exports.removeProperty = removeProperty
|
|
362
|
-
exports.removePropertyAttr = removePropertyAttr
|
|
363
|
-
exports.writeFile = writeFile
|
|
364
|
-
exports.getApiList = getApiList // ⚠️ 临时保留用于向后兼容
|
|
365
|
-
exports.getGroupFileList = getGroupFileList // ✅ 新增:按组获取文件列表
|
|
366
|
-
exports.updateFile = updateFile
|
|
367
|
-
exports.readFile = readFile
|
|
368
|
-
exports.readSession = readSession
|
|
369
|
-
exports.removeFile = removeFile
|
|
370
|
-
exports.getFilename = getFilename
|
|
371
|
-
exports.getApiListUpdated = getApiListUpdated
|
|
372
|
-
exports.setApiListUpdated = setApiListUpdated
|
|
373
|
-
exports.getRule = getRule
|
|
374
|
-
exports.isJsonReq = isJsonReq
|
|
375
|
-
|
|
376
|
-
exports.withTryCatch = withTryCatch
|
|
377
|
-
|
|
378
|
-
exports.handleBuffer2String = withTryCatch(({ body, encoding }) => {
|
|
379
|
-
switch (encoding) {
|
|
380
|
-
case 'gzip':
|
|
381
|
-
body = zlib.gunzipSync(body)
|
|
382
|
-
break
|
|
383
|
-
case 'deflate':
|
|
384
|
-
body = zlib.inflateSync(body)
|
|
385
|
-
break
|
|
386
|
-
case 'br':
|
|
387
|
-
body = zlib.brotliDecompressSync(body)
|
|
388
|
-
break
|
|
389
|
-
default:
|
|
390
|
-
break
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const type = Object.prototype.toString.call(body)
|
|
394
|
-
let content
|
|
395
|
-
switch (type) {
|
|
396
|
-
case '[object String]':
|
|
397
|
-
content = body
|
|
398
|
-
break
|
|
399
|
-
case '[object Object]':
|
|
400
|
-
content = JSON.stringify(body)
|
|
401
|
-
break
|
|
402
|
-
case '[object Uint8Array]':
|
|
403
|
-
content = body.toString()
|
|
404
|
-
break
|
|
405
|
-
default:
|
|
406
|
-
content = body
|
|
407
|
-
}
|
|
408
|
-
return content
|
|
409
|
-
})
|