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.
Files changed (56) hide show
  1. package/README.md +38 -0
  2. package/index.js +3 -3
  3. package/lib/config/const.js +138 -0
  4. package/lib/config/rule-collector.js +81 -0
  5. package/lib/constants.js +62 -0
  6. package/lib/core/memory-buffer/index.js +207 -0
  7. package/lib/core/memory-buffer/shared-instance.js +15 -0
  8. package/lib/core/plugin-mode-manager.js +74 -0
  9. package/lib/core/resRulesServer.js +14 -0
  10. package/lib/core/rulesServer.js +31 -0
  11. package/lib/core/server-entry/capture-handler.js +191 -0
  12. package/lib/core/server-entry/request-interceptor.js +82 -0
  13. package/lib/core/server-entry/response-handler.js +147 -0
  14. package/lib/core/server-entry/server.js +230 -0
  15. package/lib/storage/group-manager.js +627 -0
  16. package/lib/storage/storage-adapter.js +712 -0
  17. package/lib/storage/storage-v3.js +1418 -0
  18. package/lib/uiServer/index.js +61 -24
  19. package/lib/uiServer/router/export/import-export-router.js +459 -0
  20. package/lib/uiServer/router/files/api-list-router.js +150 -0
  21. package/lib/uiServer/router/files/batch-operations-router.js +185 -0
  22. package/lib/uiServer/router/files/file-config-router.js +118 -0
  23. package/lib/uiServer/router/files/file-crud-router.js +212 -0
  24. package/lib/uiServer/router/files/file-save-router.js +146 -0
  25. package/lib/uiServer/router/files/version-router.js +260 -0
  26. package/lib/uiServer/router/global/plugin-control.js +135 -0
  27. package/lib/uiServer/router/global/system-info-router.js +386 -0
  28. package/lib/uiServer/router/{group-router.js → groups/group-router.js} +21 -20
  29. package/lib/uiServer/router/index.js +38 -1521
  30. package/lib/uiServer/utils/router-helpers.js +100 -0
  31. package/lib/uiServer/utils/util.js +172 -0
  32. package/lib/uiServer/{validator.js → utils/validator.js} +11 -6
  33. package/lib/utils/archive-utils.js +788 -0
  34. package/lib/utils/error-handler.js +173 -0
  35. package/lib/utils/logger.js +79 -0
  36. package/lib/utils/path-utils.js +147 -0
  37. package/lib/utils/performance.js +265 -0
  38. package/lib/utils/utils.js +541 -0
  39. package/package.json +2 -2
  40. package/public/js/app.js +3732 -1929
  41. package/public/js/app.js.map +1 -1
  42. package/public/js/chunk-vendors.js +5098 -3965
  43. package/public/js/chunk-vendors.js.map +1 -1
  44. package/rules.txt +1 -1
  45. package/CHANGELOG_GROUP_FEATURE.md +0 -468
  46. package/CHANGELOG_P0_FIXES.md +0 -412
  47. package/CHANGELOG_P1_OPTIMIZATIONS.md +0 -292
  48. package/CLAUDE.md +0 -436
  49. package/GROUP_FEATURE_DESIGN.md +0 -520
  50. package/lib/const.js +0 -47
  51. package/lib/group-manager.js +0 -491
  52. package/lib/resRulesServer.js +0 -9
  53. package/lib/server.js +0 -249
  54. package/lib/uiServer/router/version-router.js +0 -205
  55. package/lib/uiServer/util.js +0 -153
  56. 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
- })