vite-plugin-automock 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 +15 -0
- package/README.md +350 -0
- package/dist/chunk-KZB7ARYV.mjs +338 -0
- package/dist/chunk-KZB7ARYV.mjs.map +1 -0
- package/dist/chunk-NWIN2A3G.mjs +180 -0
- package/dist/chunk-NWIN2A3G.mjs.map +1 -0
- package/dist/client/index.d.mts +90 -0
- package/dist/client/index.d.ts +90 -0
- package/dist/client/index.js +213 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +19 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +3239 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2682 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mockFileUtils-HQCNPI3U.mjs +15 -0
- package/dist/mockFileUtils-HQCNPI3U.mjs.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 vite-plugin-automock contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# vite-plugin-automock
|
|
2
|
+
|
|
3
|
+
Effortless API mock auto-generation for legacy projects. No manual files, just automock!
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 Automatically captures API requests and generates local mock data
|
|
10
|
+
- 🏆 Perfect for legacy projects, easily backfill missing mocks
|
|
11
|
+
- 🛠️ Supports manual mock file creation with a simple format
|
|
12
|
+
- ⚡ Zero-config, plug-and-play
|
|
13
|
+
- 🧩 Fully compatible with the Vite plugin ecosystem
|
|
14
|
+
- 📦 Production-ready with client-side interceptor
|
|
15
|
+
- 🎛️ Visual mock inspector panel
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add -D vite-plugin-automock
|
|
21
|
+
# or
|
|
22
|
+
npm install --save-dev vite-plugin-automock
|
|
23
|
+
# or
|
|
24
|
+
yarn add -D vite-plugin-automock
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Configure Vite Plugin
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// vite.config.ts
|
|
33
|
+
import { automock } from 'vite-plugin-automock'
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
plugins: [
|
|
37
|
+
automock({
|
|
38
|
+
mockDir: 'mock',
|
|
39
|
+
apiPrefix: '/api',
|
|
40
|
+
bundleMockData: true, // Bundle mock data for production
|
|
41
|
+
productionMock: true, // Enable mock in production
|
|
42
|
+
proxyBaseUrl: 'https://api.example.com'
|
|
43
|
+
})
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Create Mock Files
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// mock/users.ts
|
|
52
|
+
import { MockFileConfig } from 'vite-plugin-automock'
|
|
53
|
+
|
|
54
|
+
export default {
|
|
55
|
+
enable: true,
|
|
56
|
+
data: { users: [{ id: 1, name: 'Alice' }] },
|
|
57
|
+
delay: 100,
|
|
58
|
+
status: 200
|
|
59
|
+
} as MockFileConfig
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Initialize Client Interceptor
|
|
63
|
+
|
|
64
|
+
For PureHttp wrapper:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// src/main.ts or src/api/index.ts
|
|
68
|
+
import {
|
|
69
|
+
initMockInterceptorForPureHttp,
|
|
70
|
+
setMockEnabled,
|
|
71
|
+
registerHttpInstance
|
|
72
|
+
} from 'vite-plugin-automock/client'
|
|
73
|
+
import { http } from './http' // Your PureHttp instance
|
|
74
|
+
|
|
75
|
+
// Register your http instance
|
|
76
|
+
registerHttpInstance(http)
|
|
77
|
+
|
|
78
|
+
// Initialize mock interceptor
|
|
79
|
+
initMockInterceptorForPureHttp()
|
|
80
|
+
.then(() => {
|
|
81
|
+
console.log('[MockInterceptor] Initialized successfully')
|
|
82
|
+
})
|
|
83
|
+
.catch(error => {
|
|
84
|
+
console.error('[MockInterceptor] Failed to initialize:', error)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Enable mock in development
|
|
88
|
+
if (import.meta.env.DEV) {
|
|
89
|
+
setMockEnabled(true)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For plain Axios:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// src/main.ts
|
|
97
|
+
import { initMockInterceptor, setMockEnabled } from 'vite-plugin-automock/client'
|
|
98
|
+
import axios from 'axios'
|
|
99
|
+
|
|
100
|
+
// Initialize with axios instance
|
|
101
|
+
initMockInterceptor(axios)
|
|
102
|
+
.then(() => {
|
|
103
|
+
console.log('[MockInterceptor] Initialized successfully')
|
|
104
|
+
})
|
|
105
|
+
.catch(error => {
|
|
106
|
+
console.error('[MockInterceptor] Failed to initialize:', error)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Enable mock in development
|
|
110
|
+
if (import.meta.env.DEV) {
|
|
111
|
+
setMockEnabled(true)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
For detailed usage instructions, configuration options, and examples, see:
|
|
118
|
+
|
|
119
|
+
**[📚 Complete Usage Guide](./docs/USAGE_GUIDE.md)**
|
|
120
|
+
|
|
121
|
+
Topics covered:
|
|
122
|
+
- Development vs Production modes
|
|
123
|
+
- Client interceptor configuration
|
|
124
|
+
- Runtime mock toggle
|
|
125
|
+
- Visual inspector
|
|
126
|
+
- API reference
|
|
127
|
+
- Common scenarios
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Basic Usage (Development Mode)
|
|
132
|
+
|
|
133
|
+
Import and register the plugin in your `vite.config.ts`:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import { automock } from "vite-plugin-automock";
|
|
137
|
+
|
|
138
|
+
export default {
|
|
139
|
+
plugins: [
|
|
140
|
+
automock({
|
|
141
|
+
proxyBaseUrl: "http://localhost:8888", // Required: Your API server URL
|
|
142
|
+
mockDir: "mock", // Optional: Directory for mock files (default: 'mock')
|
|
143
|
+
apiPrefix: "/api", // Optional: API prefix to intercept (default: '/api')
|
|
144
|
+
pathRewrite: (path) => path, // Optional: Path rewrite function
|
|
145
|
+
inspector: true, // Optional: Enable visual inspector (default: false)
|
|
146
|
+
}),
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### With Custom Inspector Configuration
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
automock({
|
|
155
|
+
proxyBaseUrl: "http://localhost:8888",
|
|
156
|
+
inspector: {
|
|
157
|
+
route: "/__inspector/", // Custom route for inspector UI
|
|
158
|
+
enableToggle: true, // Allow toggling enable/disable
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration Options
|
|
164
|
+
|
|
165
|
+
| Option | Type | Required | Default | Description |
|
|
166
|
+
| -------------- | ----------------- | -------- | ---------------- | --------------------------------- |
|
|
167
|
+
| `proxyBaseUrl` | string | ✅ | - | Base URL of your API server |
|
|
168
|
+
| `mockDir` | string | ❌ | `'mock'` | Directory to store mock files |
|
|
169
|
+
| `apiPrefix` | string | ❌ | `'/api'` | API path prefix to intercept |
|
|
170
|
+
| `pathRewrite` | function | ❌ | `(path) => path` | Function to rewrite request paths |
|
|
171
|
+
| `inspector` | boolean \| object | ❌ | `false` | Enable visual mock inspector UI |
|
|
172
|
+
|
|
173
|
+
### Inspector Options
|
|
174
|
+
|
|
175
|
+
When `inspector` is an object, you can customize:
|
|
176
|
+
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
| -------------- | ------- | ------------ | ---------------------------------- |
|
|
179
|
+
| `route` | string | `'/__mock/'` | Route path for the inspector UI |
|
|
180
|
+
| `enableToggle` | boolean | `true` | Allow toggling mock enable/disable |
|
|
181
|
+
|
|
182
|
+
## Mock Inspector 🎨
|
|
183
|
+
|
|
184
|
+
**NEW!** Visual interface to manage your mock files:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
automock({
|
|
188
|
+
proxyBaseUrl: "http://localhost:8888",
|
|
189
|
+
inspector: true, // Enable inspector UI
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then visit `http://localhost:5173/__mock/` to:
|
|
194
|
+
|
|
195
|
+
- 📋 **Browse** all mock files organized by URL
|
|
196
|
+
- 🎛️ **Toggle** mock enable/disable status
|
|
197
|
+
- ⏱️ **Adjust** response delay and status codes
|
|
198
|
+
- ✏️ **Edit** JSON response data directly
|
|
199
|
+
- 💾 **Save** changes with a single click
|
|
200
|
+
|
|
201
|
+

|
|
202
|
+
|
|
203
|
+
## How It Works
|
|
204
|
+
|
|
205
|
+
1. **Request Interception**: The plugin intercepts all requests matching the `apiPrefix`
|
|
206
|
+
2. **Mock Check**: Checks if a corresponding mock file exists
|
|
207
|
+
3. **Conditional Response**:
|
|
208
|
+
- If mock exists → Returns mock data
|
|
209
|
+
- If mock doesn't exist → Proxies to real API and saves response as mock
|
|
210
|
+
4. **Auto-Generation**: Real API responses are automatically saved as mock files
|
|
211
|
+
5. **Hot Reloading**: Changes to mock files are automatically detected and reloaded
|
|
212
|
+
6. **Visual Inspector**: Manage and edit mocks through the web UI
|
|
213
|
+
|
|
214
|
+
## Mock File Structure
|
|
215
|
+
|
|
216
|
+
Mock files are organized by URL path and HTTP method:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
mock/
|
|
220
|
+
├── api/
|
|
221
|
+
│ ├── users/
|
|
222
|
+
│ │ ├── get.js # GET /api/users
|
|
223
|
+
│ │ └── post.js # POST /api/users
|
|
224
|
+
│ └── items/
|
|
225
|
+
│ ├── get.js # GET /api/items
|
|
226
|
+
│ └── post.js # POST /api/items
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Each mock file exports an object with this structure:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
/**
|
|
233
|
+
* Mock data for /api/users (GET)
|
|
234
|
+
* Generated at 2025-01-11T10:30:00.000Z
|
|
235
|
+
*/
|
|
236
|
+
export default {
|
|
237
|
+
enable: false, // Whether to use this mock (default: false)
|
|
238
|
+
data: {
|
|
239
|
+
// Response data
|
|
240
|
+
code: 0,
|
|
241
|
+
message: "success",
|
|
242
|
+
data: [
|
|
243
|
+
{ id: 1, name: "User 1" },
|
|
244
|
+
{ id: 2, name: "User 2" },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
delay: 0, // Artificial delay in milliseconds
|
|
248
|
+
status: 200, // HTTP status code
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Quick Start
|
|
253
|
+
|
|
254
|
+
1. **Try the Example**:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pnpm run example
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
This starts the playground with a demo API server.
|
|
261
|
+
|
|
262
|
+
2. **Manual Setup**:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Install the plugin
|
|
266
|
+
pnpm add -D vite-plugin-automock
|
|
267
|
+
|
|
268
|
+
# Add to your vite.config.js
|
|
269
|
+
import { automock } from "vite-plugin-automock";
|
|
270
|
+
|
|
271
|
+
export default {
|
|
272
|
+
plugins: [
|
|
273
|
+
automock({
|
|
274
|
+
proxyBaseUrl: "http://localhost:8888"
|
|
275
|
+
})
|
|
276
|
+
]
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
3. **Start Development**:
|
|
281
|
+
- Start your API server
|
|
282
|
+
- Start Vite dev server
|
|
283
|
+
- Make API calls from your frontend
|
|
284
|
+
- Check the mock directory for generated files
|
|
285
|
+
|
|
286
|
+
## Comparison with Traditional Mock Plugins
|
|
287
|
+
|
|
288
|
+
| Feature | Traditional Mock Plugins | vite-plugin-automock |
|
|
289
|
+
| ---------------------- | ------------------------ | -------------------- |
|
|
290
|
+
| Auto backfill | ❌ | ✅ |
|
|
291
|
+
| Legacy project support | ❌ | ✅ |
|
|
292
|
+
| Manual mock files | ✅ | ✅ |
|
|
293
|
+
| Config complexity | High | Very low |
|
|
294
|
+
| Zero setup | ❌ | ✅ |
|
|
295
|
+
| Real API integration | ❌ | ✅ |
|
|
296
|
+
|
|
297
|
+
## When to Use
|
|
298
|
+
|
|
299
|
+
- **Legacy Projects**: Quickly add mock capability to existing projects
|
|
300
|
+
- **API Development**: Test frontend while backend is being developed
|
|
301
|
+
- **Offline Development**: Work without internet or when APIs are unavailable
|
|
302
|
+
- **Testing**: Consistent test data without external dependencies
|
|
303
|
+
- **Demo/Presentation**: Reliable demo data that doesn't change
|
|
304
|
+
|
|
305
|
+
## Advanced Usage
|
|
306
|
+
|
|
307
|
+
### Custom Path Rewriting
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
automock({
|
|
311
|
+
proxyBaseUrl: "http://api.example.com",
|
|
312
|
+
pathRewrite: (path) => {
|
|
313
|
+
// Remove /api prefix when proxying
|
|
314
|
+
return path.replace(/^\/api/, "");
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Conditional Mock Enabling
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// In your mock file
|
|
323
|
+
export default {
|
|
324
|
+
enable: process.env.NODE_ENV === "development",
|
|
325
|
+
data: {
|
|
326
|
+
/* mock data */
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Dynamic Mock Data
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
// In your mock file
|
|
335
|
+
export default {
|
|
336
|
+
enable: true,
|
|
337
|
+
data: () => ({
|
|
338
|
+
timestamp: new Date().toISOString(),
|
|
339
|
+
randomId: Math.floor(Math.random() * 1000),
|
|
340
|
+
}),
|
|
341
|
+
};
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Contributing
|
|
345
|
+
|
|
346
|
+
We welcome contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
347
|
+
|
|
348
|
+
## License
|
|
349
|
+
|
|
350
|
+
ISC
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/mockFileUtils.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import prettier from "prettier";
|
|
5
|
+
var DEFAULT_CONFIG = {
|
|
6
|
+
enable: true,
|
|
7
|
+
data: null,
|
|
8
|
+
delay: 0,
|
|
9
|
+
status: 200
|
|
10
|
+
};
|
|
11
|
+
function toPosixPath(p) {
|
|
12
|
+
return p.replace(/\\/g, "/");
|
|
13
|
+
}
|
|
14
|
+
var isBinaryResponse = (contentType, data) => {
|
|
15
|
+
if (!contentType) return false;
|
|
16
|
+
const binaryTypes = [
|
|
17
|
+
"application/octet-stream",
|
|
18
|
+
"application/pdf",
|
|
19
|
+
"application/zip",
|
|
20
|
+
"application/x-zip-compressed",
|
|
21
|
+
"application/vnd.ms-excel",
|
|
22
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
23
|
+
"application/msword",
|
|
24
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
25
|
+
"application/vnd.ms-powerpoint",
|
|
26
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
27
|
+
"image/",
|
|
28
|
+
"video/",
|
|
29
|
+
"audio/"
|
|
30
|
+
];
|
|
31
|
+
return binaryTypes.some((type) => contentType.toLowerCase().includes(type)) || !isBufferTextLike(data);
|
|
32
|
+
};
|
|
33
|
+
var isBufferTextLike = (buffer) => {
|
|
34
|
+
try {
|
|
35
|
+
const sample = buffer.slice(0, 100);
|
|
36
|
+
const text = sample.toString("utf8");
|
|
37
|
+
const nullBytes = [...sample].filter((b) => b === 0).length;
|
|
38
|
+
const controlChars = [...sample].filter(
|
|
39
|
+
(b) => b < 32 && b !== 9 && b !== 10 && b !== 13
|
|
40
|
+
).length;
|
|
41
|
+
return nullBytes === 0 && controlChars < 5;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var getFileExtension = (contentType, url) => {
|
|
47
|
+
const mimeMap = {
|
|
48
|
+
"application/json": "json",
|
|
49
|
+
"application/pdf": "pdf",
|
|
50
|
+
"application/zip": "zip",
|
|
51
|
+
"application/vnd.ms-excel": "xls",
|
|
52
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
53
|
+
"application/msword": "doc",
|
|
54
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
55
|
+
"application/vnd.ms-powerpoint": "ppt",
|
|
56
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
|
|
57
|
+
"image/jpeg": "jpg",
|
|
58
|
+
"image/png": "png",
|
|
59
|
+
"image/gif": "gif",
|
|
60
|
+
"text/plain": "txt",
|
|
61
|
+
"text/html": "html",
|
|
62
|
+
"text/css": "css",
|
|
63
|
+
"application/javascript": "js",
|
|
64
|
+
"text/xml": "xml"
|
|
65
|
+
};
|
|
66
|
+
if (contentType && mimeMap[contentType.toLowerCase()]) {
|
|
67
|
+
return mimeMap[contentType.toLowerCase()];
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const urlObj = new URL(url, "http://localhost");
|
|
71
|
+
const fileName = urlObj.searchParams.get("file_name");
|
|
72
|
+
if (fileName) {
|
|
73
|
+
const extensionMatch = fileName.match(/\.([a-zA-Z0-9]+)$/);
|
|
74
|
+
if (extensionMatch) {
|
|
75
|
+
return extensionMatch[1];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
}
|
|
80
|
+
return "bin";
|
|
81
|
+
};
|
|
82
|
+
var saveMockData = async (url, method, data, rootDir, statusCode, contentType) => {
|
|
83
|
+
try {
|
|
84
|
+
const absoluteRootDir = path.isAbsolute(rootDir) ? rootDir : path.resolve(process.cwd(), rootDir);
|
|
85
|
+
let pathname;
|
|
86
|
+
let search;
|
|
87
|
+
try {
|
|
88
|
+
if (url.startsWith("http")) {
|
|
89
|
+
const urlObj = new URL(url);
|
|
90
|
+
pathname = urlObj.pathname;
|
|
91
|
+
search = urlObj.search;
|
|
92
|
+
} else {
|
|
93
|
+
const urlObj = new URL(url, "http://localhost");
|
|
94
|
+
pathname = urlObj.pathname;
|
|
95
|
+
search = urlObj.search;
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const [pathPart, ...searchPart] = url.split("?");
|
|
99
|
+
pathname = pathPart;
|
|
100
|
+
search = searchPart.length > 0 ? "?" + searchPart.join("?") : "";
|
|
101
|
+
}
|
|
102
|
+
const filePath = path.join(
|
|
103
|
+
absoluteRootDir,
|
|
104
|
+
pathname.replace(/^\//, ""),
|
|
105
|
+
method.toLowerCase() + ".js"
|
|
106
|
+
);
|
|
107
|
+
const dir = path.dirname(filePath);
|
|
108
|
+
fs.ensureDirSync(dir);
|
|
109
|
+
const isBuffer = Buffer.isBuffer(data);
|
|
110
|
+
const binaryData = isBuffer ? data : Buffer.from(data || "");
|
|
111
|
+
const isBinary = isBinaryResponse(contentType, binaryData);
|
|
112
|
+
if (isBinary) {
|
|
113
|
+
const extension = getFileExtension(contentType, url);
|
|
114
|
+
const binaryFilePath = filePath.replace(/\.js$/, "." + extension);
|
|
115
|
+
if (fs.existsSync(binaryFilePath)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
fs.writeFileSync(binaryFilePath, binaryData);
|
|
119
|
+
const configContent = `/**
|
|
120
|
+
* Mock data for ${pathname} (${method.toUpperCase()})${search || ""}
|
|
121
|
+
* @description ${pathname}${search || ""} - Binary file (${extension})
|
|
122
|
+
* Generated at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
123
|
+
*/
|
|
124
|
+
export default {
|
|
125
|
+
enable: false,
|
|
126
|
+
data: {
|
|
127
|
+
__binaryFile: '${extension}',
|
|
128
|
+
__originalPath: '${pathname}',
|
|
129
|
+
__originalQuery: '${search}',
|
|
130
|
+
__originalUrl: '${pathname}${search || ""}',
|
|
131
|
+
__contentType: '${contentType}',
|
|
132
|
+
__fileSize: ${binaryData.length}
|
|
133
|
+
},
|
|
134
|
+
delay: 0,
|
|
135
|
+
status: ${statusCode || 200}
|
|
136
|
+
}`;
|
|
137
|
+
try {
|
|
138
|
+
const formattedCode = await prettier.format(configContent, {
|
|
139
|
+
parser: "babel"
|
|
140
|
+
});
|
|
141
|
+
fs.writeFileSync(filePath, formattedCode, "utf-8");
|
|
142
|
+
} catch (error) {
|
|
143
|
+
fs.writeFileSync(filePath, configContent, "utf-8");
|
|
144
|
+
}
|
|
145
|
+
return filePath;
|
|
146
|
+
} else {
|
|
147
|
+
if (fs.existsSync(filePath)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const dataStr = isBuffer ? data.toString("utf8") : data || "";
|
|
151
|
+
let jsonData;
|
|
152
|
+
if (!dataStr || dataStr.trim() === "") {
|
|
153
|
+
jsonData = {
|
|
154
|
+
error: true,
|
|
155
|
+
message: `Empty response (${statusCode || "unknown status"})`,
|
|
156
|
+
status: statusCode || 404,
|
|
157
|
+
data: null
|
|
158
|
+
};
|
|
159
|
+
} else {
|
|
160
|
+
try {
|
|
161
|
+
jsonData = JSON.parse(dataStr);
|
|
162
|
+
if (statusCode && statusCode >= 400) {
|
|
163
|
+
if (typeof jsonData === "object" && jsonData !== null) {
|
|
164
|
+
jsonData = {
|
|
165
|
+
...jsonData,
|
|
166
|
+
__mockStatusCode: statusCode,
|
|
167
|
+
__isErrorResponse: true
|
|
168
|
+
};
|
|
169
|
+
} else {
|
|
170
|
+
jsonData = {
|
|
171
|
+
originalData: jsonData,
|
|
172
|
+
__mockStatusCode: statusCode,
|
|
173
|
+
__isErrorResponse: true
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
jsonData = {
|
|
179
|
+
error: true,
|
|
180
|
+
message: `Non-JSON response (${statusCode || "unknown status"})`,
|
|
181
|
+
status: statusCode || 404,
|
|
182
|
+
data: dataStr,
|
|
183
|
+
__mockStatusCode: statusCode,
|
|
184
|
+
__isErrorResponse: true
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const content = `/**
|
|
189
|
+
* Mock data for ${pathname} (${method.toUpperCase()})${search || ""}
|
|
190
|
+
* @description ${pathname}${search || ""}
|
|
191
|
+
* Generated at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
192
|
+
*/
|
|
193
|
+
export default {
|
|
194
|
+
enable: false,
|
|
195
|
+
data: ${JSON.stringify(jsonData)},
|
|
196
|
+
delay: 0,
|
|
197
|
+
status: ${statusCode || 200}
|
|
198
|
+
}`;
|
|
199
|
+
try {
|
|
200
|
+
const formattedCode = await prettier.format(content, {
|
|
201
|
+
parser: "babel"
|
|
202
|
+
});
|
|
203
|
+
fs.writeFileSync(filePath, formattedCode, "utf-8");
|
|
204
|
+
return filePath;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
207
|
+
return filePath;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(`Failed to save mock data for ${url}:`, error);
|
|
212
|
+
console.error(
|
|
213
|
+
`URL details: url=${url}, method=${method}, statusCode=${statusCode}, contentType=${contentType}`
|
|
214
|
+
);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var recursiveReadAllFiles = (dir) => {
|
|
219
|
+
if (!fs.existsSync(dir)) return [];
|
|
220
|
+
const files = [];
|
|
221
|
+
try {
|
|
222
|
+
const list = fs.readdirSync(dir);
|
|
223
|
+
list.forEach((file) => {
|
|
224
|
+
const filePath = path.join(dir, file);
|
|
225
|
+
const stat = fs.statSync(filePath);
|
|
226
|
+
if (stat.isDirectory()) {
|
|
227
|
+
files.push(...recursiveReadAllFiles(filePath));
|
|
228
|
+
} else {
|
|
229
|
+
files.push(filePath);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`Error reading directory ${dir}:`, error);
|
|
234
|
+
}
|
|
235
|
+
return files;
|
|
236
|
+
};
|
|
237
|
+
var buildMockIndex = (mockDir) => {
|
|
238
|
+
const mockFileMap = /* @__PURE__ */ new Map();
|
|
239
|
+
if (!fs.existsSync(mockDir)) {
|
|
240
|
+
fs.ensureDirSync(mockDir);
|
|
241
|
+
return mockFileMap;
|
|
242
|
+
}
|
|
243
|
+
const files = recursiveReadAllFiles(mockDir);
|
|
244
|
+
files.forEach((filePath) => {
|
|
245
|
+
if (!filePath.endsWith(".js")) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const relativePath = path.relative(mockDir, filePath);
|
|
250
|
+
const method = path.basename(filePath, ".js");
|
|
251
|
+
const dirPath = path.dirname(relativePath);
|
|
252
|
+
const urlPath = "/" + toPosixPath(dirPath);
|
|
253
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
254
|
+
const key = `${urlPath}/${method}.js`.toLowerCase();
|
|
255
|
+
mockFileMap.set(key, absolutePath);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(`\u274C [automock] \u5904\u7406\u6587\u4EF6\u5931\u8D25 ${filePath}:`, error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return mockFileMap;
|
|
261
|
+
};
|
|
262
|
+
var parseMockModule = async (filePath) => {
|
|
263
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
264
|
+
if (!fs.existsSync(absolutePath)) {
|
|
265
|
+
throw new Error(`Mock file does not exist: ${absolutePath}`);
|
|
266
|
+
}
|
|
267
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
268
|
+
const headerCommentMatch = content.match(/^(\/\*\*[\s\S]*?\*\/)/);
|
|
269
|
+
const headerComment = headerCommentMatch ? headerCommentMatch[1] : void 0;
|
|
270
|
+
let description;
|
|
271
|
+
if (headerComment) {
|
|
272
|
+
const descMatch = headerComment.match(/@description\s+(.+?)(?:\n|\*\/)/s);
|
|
273
|
+
if (descMatch) {
|
|
274
|
+
description = descMatch[1].trim();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const { default: mockModule } = await import(`${absolutePath}?t=${Date.now()}`);
|
|
278
|
+
const exportedConfig = typeof mockModule === "function" ? mockModule() : mockModule;
|
|
279
|
+
const hasDynamicData = typeof exportedConfig?.data === "function";
|
|
280
|
+
const config = {
|
|
281
|
+
...DEFAULT_CONFIG,
|
|
282
|
+
...exportedConfig ?? {}
|
|
283
|
+
};
|
|
284
|
+
const serializable = !hasDynamicData;
|
|
285
|
+
const dataObj = typeof config.data === "object" && config.data !== null ? config.data : null;
|
|
286
|
+
const isBinary = dataObj !== null && "__binaryFile" in dataObj;
|
|
287
|
+
return {
|
|
288
|
+
config,
|
|
289
|
+
serializable,
|
|
290
|
+
hasDynamicData,
|
|
291
|
+
headerComment,
|
|
292
|
+
description,
|
|
293
|
+
dataText: isBinary ? `/* Binary file: ${dataObj?.__binaryFile} */` : serializable ? JSON.stringify(config.data ?? null, null, 2) : "/* data is generated dynamically and cannot be edited here */",
|
|
294
|
+
isBinary
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
var writeMockFile = async (filePath, mockInfo) => {
|
|
298
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
299
|
+
let header = mockInfo.headerComment || "";
|
|
300
|
+
if (mockInfo.description !== void 0) {
|
|
301
|
+
if (header) {
|
|
302
|
+
if (/@description/.test(header)) {
|
|
303
|
+
header = header.replace(
|
|
304
|
+
/@description\s+.+?(?=\n|\*\/)/s,
|
|
305
|
+
`@description ${mockInfo.description}`
|
|
306
|
+
);
|
|
307
|
+
} else {
|
|
308
|
+
header = header.replace(
|
|
309
|
+
/\*\//,
|
|
310
|
+
` * @description ${mockInfo.description}
|
|
311
|
+
*/`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const content = header ? `${header}
|
|
317
|
+
` : "";
|
|
318
|
+
const finalContent = `${content}export default ${JSON.stringify(mockInfo.config, null, 2)}
|
|
319
|
+
`;
|
|
320
|
+
try {
|
|
321
|
+
const formattedCode = await prettier.format(finalContent, {
|
|
322
|
+
parser: "babel"
|
|
323
|
+
});
|
|
324
|
+
fs.writeFileSync(absolutePath, formattedCode, "utf-8");
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error("Error formatting code with prettier:", error);
|
|
327
|
+
fs.writeFileSync(absolutePath, finalContent, "utf-8");
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export {
|
|
332
|
+
DEFAULT_CONFIG,
|
|
333
|
+
saveMockData,
|
|
334
|
+
buildMockIndex,
|
|
335
|
+
parseMockModule,
|
|
336
|
+
writeMockFile
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=chunk-KZB7ARYV.mjs.map
|