warehouse-mock 1.1.5 → 1.2.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/README.md +99 -15
- package/dist/index.d.ts +4 -33
- package/dist/index.js +15 -515
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
极简 Vue Mock
|
|
5
|
+
极简 Vue Mock 插件(**Webpack / Vue CLI** 版),零业务代码侵入,完美支持 RPC 风格接口
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/warehouse-mock)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
|
+
> 本包是 Warehouse Mock 的 **Webpack 适配**,核心逻辑由 [`warehouse-mock-core`](https://www.npmjs.com/package/warehouse-mock-core) 提供(会作为依赖自动安装)。
|
|
13
|
+
> Vue 3 + Vite 项目请使用 [`warehouse-mock-vite`](https://www.npmjs.com/package/warehouse-mock-vite),二者数据格式与功能完全一致。
|
|
14
|
+
|
|
12
15
|
## 安装
|
|
13
16
|
|
|
14
17
|
```bash
|
|
@@ -19,20 +22,58 @@ npm install warehouse-mock --save-dev
|
|
|
19
22
|
|
|
20
23
|
### 1. 配置 vue.config.js
|
|
21
24
|
|
|
25
|
+
**Vue CLI 5(Webpack 5):**
|
|
26
|
+
|
|
22
27
|
```javascript
|
|
23
28
|
const WarehouseMockPlugin = require('warehouse-mock');
|
|
24
29
|
|
|
25
30
|
const isMock = process.env.MOCK === 'true';
|
|
31
|
+
const mockPlugin = isMock ? new WarehouseMockPlugin() : null;
|
|
26
32
|
|
|
27
33
|
module.exports = {
|
|
28
34
|
configureWebpack: config => {
|
|
29
|
-
if (isMock) {
|
|
30
|
-
config.plugins.push(
|
|
35
|
+
if (isMock && mockPlugin) {
|
|
36
|
+
config.plugins.push(mockPlugin);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
devServer: {
|
|
40
|
+
// Webpack 5 通过 setupMiddlewares 挂载拦截中间件
|
|
41
|
+
setupMiddlewares: (middlewares, devServer) => {
|
|
42
|
+
if (isMock && mockPlugin) {
|
|
43
|
+
return mockPlugin.setupMiddlewares(middlewares, devServer);
|
|
44
|
+
}
|
|
45
|
+
return middlewares;
|
|
31
46
|
}
|
|
32
47
|
}
|
|
33
48
|
};
|
|
34
49
|
```
|
|
35
50
|
|
|
51
|
+
**Vue CLI 3-4(Webpack 4):** 用 `before` 钩子挂载,并手动注入环境变量:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
const WarehouseMockPlugin = require('warehouse-mock');
|
|
55
|
+
const webpack = require('webpack');
|
|
56
|
+
|
|
57
|
+
const isMock = process.env.MOCK === 'true';
|
|
58
|
+
const mockPlugin = isMock ? new WarehouseMockPlugin() : null;
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
configureWebpack: config => {
|
|
62
|
+
if (isMock && mockPlugin) {
|
|
63
|
+
config.plugins.push(
|
|
64
|
+
new webpack.DefinePlugin({
|
|
65
|
+
'process.env.VUE_APP_MOCK': JSON.stringify('true'),
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
config.plugins.push(mockPlugin);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
devServer: {
|
|
72
|
+
before: isMock && mockPlugin ? app => mockPlugin.runBefore(app) : undefined,
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
36
77
|
### 2. 添加 Mock 脚本
|
|
37
78
|
|
|
38
79
|
```json
|
|
@@ -57,12 +98,16 @@ if (process.env.VUE_APP_MOCK === 'true') {
|
|
|
57
98
|
|
|
58
99
|
### 4. 创建 Mock 数据
|
|
59
100
|
|
|
60
|
-
在项目根目录创建 `warehouseMock/`
|
|
101
|
+
在项目根目录创建 `warehouseMock/` 文件夹,每个接口对应一个目录(详见下方 [Mock 文件命名规则](#mock-文件命名规则)):
|
|
61
102
|
|
|
62
103
|
```
|
|
63
104
|
warehouseMock/
|
|
64
|
-
├── user.account.getInfo
|
|
65
|
-
|
|
105
|
+
├── user.account.getInfo/
|
|
106
|
+
│ ├── .config.json
|
|
107
|
+
│ └── default.json
|
|
108
|
+
└── user.taurus.pointInfo/
|
|
109
|
+
├── .config.json
|
|
110
|
+
└── default.json
|
|
66
111
|
```
|
|
67
112
|
|
|
68
113
|
### 5. 启动
|
|
@@ -98,6 +143,12 @@ interface MockPluginOptions {
|
|
|
98
143
|
target: string;
|
|
99
144
|
changeOrigin?: boolean;
|
|
100
145
|
};
|
|
146
|
+
|
|
147
|
+
// 管理后台配置
|
|
148
|
+
admin?: {
|
|
149
|
+
enabled?: boolean; // 是否启用,默认 true
|
|
150
|
+
port?: number; // 端口,默认 3100
|
|
151
|
+
};
|
|
101
152
|
}
|
|
102
153
|
```
|
|
103
154
|
|
|
@@ -135,19 +186,44 @@ new WarehouseMockPlugin({
|
|
|
135
186
|
|
|
136
187
|
## Mock 文件命名规则
|
|
137
188
|
|
|
138
|
-
|
|
189
|
+
接口名对应 `warehouseMock/` 下的一个**目录**(推荐,支持多场景),目录内含 `.config.json` 配置和若干场景文件:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
warehouseMock/
|
|
193
|
+
└── user.account.getInfo/ # 接口目录(接口名)
|
|
194
|
+
├── .config.json # { name, delay, enabled, activeScene }
|
|
195
|
+
├── default.json # 默认场景数据
|
|
196
|
+
└── success.json # 其他场景数据
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`.config.json`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"name": "user.account.getInfo",
|
|
204
|
+
"delay": 0,
|
|
205
|
+
"enabled": true,
|
|
206
|
+
"activeScene": "default"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
> 也兼容旧的单文件格式 `warehouseMock/user.account.getInfo.json`。
|
|
139
211
|
|
|
140
|
-
|
|
212
|
+
### 接口名与请求 URL 的对应
|
|
213
|
+
|
|
214
|
+
**RPC 风格 (推荐)**
|
|
215
|
+
|
|
216
|
+
| 请求 URL | 接口目录 |
|
|
141
217
|
|---------|-----------|
|
|
142
|
-
| `GET /api?user.account.getInfo` | `user.account.getInfo
|
|
143
|
-
| `POST /mock-api?user.taurus.pointInfo` | `user.taurus.pointInfo
|
|
218
|
+
| `GET /api?user.account.getInfo` | `warehouseMock/user.account.getInfo/` |
|
|
219
|
+
| `POST /mock-api?user.taurus.pointInfo` | `warehouseMock/user.taurus.pointInfo/` |
|
|
144
220
|
|
|
145
|
-
|
|
221
|
+
**RESTful 风格**
|
|
146
222
|
|
|
147
|
-
| 请求 URL |
|
|
223
|
+
| 请求 URL | 接口目录 / 文件 |
|
|
148
224
|
|---------|-----------|
|
|
149
|
-
| `GET /api/user/info` | `api_user_info
|
|
150
|
-
| `GET /api/user/info` | `api/user/info.json
|
|
225
|
+
| `GET /api/user/info` | `warehouseMock/api_user_info/`(扁平化) |
|
|
226
|
+
| `GET /api/user/info` | `warehouseMock/api/user/info.json`(嵌套) |
|
|
151
227
|
|
|
152
228
|
## 调试工具
|
|
153
229
|
|
|
@@ -161,15 +237,23 @@ new WarehouseMockPlugin({
|
|
|
161
237
|
|
|
162
238
|
## 核心特性
|
|
163
239
|
|
|
164
|
-
- ✅ **极简配置** - vue.config.js
|
|
240
|
+
- ✅ **极简配置** - vue.config.js 几行代码即可接入
|
|
165
241
|
- ✅ **零业务侵入** - 无需修改接口调用代码
|
|
166
242
|
- ✅ **RPC 风格支持** - 原生支持 RPC 接口
|
|
243
|
+
- ✅ **多场景** - 每个接口可配置多个场景,一键切换
|
|
167
244
|
- ✅ **实时热更新** - 修改 Mock 数据,刷新即可
|
|
168
245
|
- ✅ **按需拦截** - 只拦截配置了 Mock 文件的接口
|
|
169
246
|
- ✅ **代理模式** - 未匹配请求转发到真实 API
|
|
170
247
|
- ✅ **自动注入环境变量** - 无需手动配置 DefinePlugin
|
|
248
|
+
- ✅ **可视化管理后台** - 内置 Web 界面管理 Mock(可选)
|
|
171
249
|
- ✅ **兼容性强** - 支持 Webpack 4/5,Vue CLI 3/4/5
|
|
172
250
|
|
|
251
|
+
## 相关包
|
|
252
|
+
|
|
253
|
+
- [`warehouse-mock-vite`](https://www.npmjs.com/package/warehouse-mock-vite) - Vue 3 + Vite 版本
|
|
254
|
+
- [`warehouse-mock-core`](https://www.npmjs.com/package/warehouse-mock-core) - 框架无关核心引擎
|
|
255
|
+
- [`warehouse-mock-admin`](https://www.npmjs.com/package/warehouse-mock-admin) - 可视化管理后台
|
|
256
|
+
|
|
173
257
|
## License
|
|
174
258
|
|
|
175
259
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
import { Compiler } from 'webpack';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
apiPrefixes?: string[];
|
|
5
|
-
localApiPrefix?: string;
|
|
6
|
-
enabled?: boolean;
|
|
7
|
-
proxy?: {
|
|
8
|
-
target: string;
|
|
9
|
-
changeOrigin?: boolean;
|
|
10
|
-
};
|
|
2
|
+
import { MockEngineOptions } from 'warehouse-mock-core';
|
|
3
|
+
interface MockPluginOptions extends MockEngineOptions {
|
|
11
4
|
injectEnv?: boolean;
|
|
12
|
-
delay?: number;
|
|
13
|
-
admin?: {
|
|
14
|
-
enabled?: boolean;
|
|
15
|
-
port?: number;
|
|
16
|
-
};
|
|
17
5
|
}
|
|
18
6
|
declare class WarehouseMockPlugin {
|
|
19
|
-
private
|
|
20
|
-
private
|
|
7
|
+
private engine;
|
|
8
|
+
private injectEnv;
|
|
21
9
|
private isEnabled;
|
|
22
|
-
private adminServer;
|
|
23
10
|
constructor(options?: MockPluginOptions);
|
|
24
11
|
/**
|
|
25
12
|
* 实时扫描 mock 目录,获取所有 mock 文件名列表
|
|
26
|
-
* 支持两种格式:
|
|
27
|
-
* 1. 旧格式:api.json
|
|
28
|
-
* 2. 新格式:api/.config.json
|
|
29
13
|
*/
|
|
30
14
|
getMockFileList(): string[];
|
|
31
15
|
/**
|
|
@@ -33,10 +17,6 @@ declare class WarehouseMockPlugin {
|
|
|
33
17
|
*/
|
|
34
18
|
getLocalApiPrefix(): string;
|
|
35
19
|
apply(compiler: Compiler): void;
|
|
36
|
-
/**
|
|
37
|
-
* 启动管理后台服务
|
|
38
|
-
*/
|
|
39
|
-
private startAdminServer;
|
|
40
20
|
/**
|
|
41
21
|
* 自动注入环境变量 VUE_APP_MOCK
|
|
42
22
|
*/
|
|
@@ -49,14 +29,5 @@ declare class WarehouseMockPlugin {
|
|
|
49
29
|
* 公共方法:设置中间件 (Webpack 4 / Vue CLI 3-4)
|
|
50
30
|
*/
|
|
51
31
|
runBefore(app: any, server?: any, compiler?: any, originalBefore?: Function): void;
|
|
52
|
-
private getResolvedMockPath;
|
|
53
|
-
private ensureMockDirectory;
|
|
54
|
-
private createDemoFile;
|
|
55
|
-
private readInterfaceConfig;
|
|
56
|
-
private handleRequest;
|
|
57
|
-
/**
|
|
58
|
-
* 代理请求到真实 API(改进版)
|
|
59
|
-
*/
|
|
60
|
-
private proxyRequest;
|
|
61
32
|
}
|
|
62
33
|
export = WarehouseMockPlugin;
|
package/dist/index.js
CHANGED
|
@@ -2,71 +2,25 @@
|
|
|
2
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
|
-
const fs_1 = __importDefault(require("fs"));
|
|
6
|
-
const path_1 = __importDefault(require("path"));
|
|
7
5
|
const chalk_1 = __importDefault(require("chalk"));
|
|
6
|
+
const warehouse_mock_core_1 = require("warehouse-mock-core");
|
|
8
7
|
class WarehouseMockPlugin {
|
|
9
8
|
constructor(options = {}) {
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
this.isEnabled =
|
|
13
|
-
this.adminServer = null;
|
|
14
|
-
// 默认配置
|
|
15
|
-
this.options = {
|
|
16
|
-
mockPath: options.mockPath || 'warehouseMock',
|
|
17
|
-
apiPrefixes: options.apiPrefixes || ['/api', '/mock-api'],
|
|
18
|
-
localApiPrefix: options.localApiPrefix || '/mock-api',
|
|
19
|
-
enabled: options.enabled !== undefined ? options.enabled : true,
|
|
20
|
-
proxy: options.proxy || undefined,
|
|
21
|
-
injectEnv: options.injectEnv !== undefined ? options.injectEnv : true,
|
|
22
|
-
delay: options.delay || 0,
|
|
23
|
-
admin: {
|
|
24
|
-
enabled: ((_a = options.admin) === null || _a === void 0 ? void 0 : _a.enabled) !== undefined ? options.admin.enabled : true,
|
|
25
|
-
port: ((_b = options.admin) === null || _b === void 0 ? void 0 : _b.port) || 3100,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
// 判断是否启用(支持环境变量控制)
|
|
29
|
-
if (process.env.MOCK === 'false' || process.env.VUE_APP_MOCK === 'false') {
|
|
30
|
-
this.isEnabled = false;
|
|
31
|
-
}
|
|
32
|
-
if (this.options.enabled === false) {
|
|
33
|
-
this.isEnabled = false;
|
|
34
|
-
}
|
|
9
|
+
this.injectEnv = options.injectEnv !== undefined ? options.injectEnv : true;
|
|
10
|
+
this.engine = new warehouse_mock_core_1.MockEngine(options);
|
|
11
|
+
this.isEnabled = this.engine.isEnabled;
|
|
35
12
|
}
|
|
36
13
|
/**
|
|
37
14
|
* 实时扫描 mock 目录,获取所有 mock 文件名列表
|
|
38
|
-
* 支持两种格式:
|
|
39
|
-
* 1. 旧格式:api.json
|
|
40
|
-
* 2. 新格式:api/.config.json
|
|
41
15
|
*/
|
|
42
16
|
getMockFileList() {
|
|
43
|
-
|
|
44
|
-
const fileList = [];
|
|
45
|
-
if (fs_1.default.existsSync(mockPath)) {
|
|
46
|
-
const files = fs_1.default.readdirSync(mockPath);
|
|
47
|
-
files.forEach((file) => {
|
|
48
|
-
const filePath = path_1.default.join(mockPath, file);
|
|
49
|
-
const stats = fs_1.default.statSync(filePath);
|
|
50
|
-
if (stats.isDirectory()) {
|
|
51
|
-
// 新格式:检查目录下是否有 .config.json
|
|
52
|
-
const configPath = path_1.default.join(filePath, '.config.json');
|
|
53
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
54
|
-
fileList.push(file);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else if (file.endsWith('.json') && !file.startsWith('.')) {
|
|
58
|
-
// 旧格式:直接是 .json 文件(排除 .config.json 等隐藏文件)
|
|
59
|
-
fileList.push(file.replace(/\.json$/, ''));
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return fileList;
|
|
17
|
+
return this.engine.getMockFileList();
|
|
64
18
|
}
|
|
65
19
|
/**
|
|
66
20
|
* 获取本地代理路径前缀
|
|
67
21
|
*/
|
|
68
22
|
getLocalApiPrefix() {
|
|
69
|
-
return this.
|
|
23
|
+
return this.engine.getLocalApiPrefix();
|
|
70
24
|
}
|
|
71
25
|
apply(compiler) {
|
|
72
26
|
// 如果未启用,直接返回
|
|
@@ -74,79 +28,18 @@ class WarehouseMockPlugin {
|
|
|
74
28
|
console.log(chalk_1.default.yellow('[WarehouseMock] Mock 模式未启用'));
|
|
75
29
|
return;
|
|
76
30
|
}
|
|
77
|
-
|
|
31
|
+
// 解析 mock 目录(基于 webpack 上下文)
|
|
32
|
+
this.engine.resolveMockPath(compiler.context);
|
|
78
33
|
// 自动注入环境变量 VUE_APP_MOCK
|
|
79
|
-
if (this.
|
|
34
|
+
if (this.injectEnv) {
|
|
80
35
|
this.injectEnvironmentVariable(compiler);
|
|
81
36
|
}
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
fs_1.default.mkdirSync(this.resolvedMockPath, { recursive: true });
|
|
86
|
-
console.log(chalk_1.default.green(`[WarehouseMock] Mock 目录已创建: ${this.resolvedMockPath}`));
|
|
87
|
-
this.createDemoFile(this.resolvedMockPath);
|
|
88
|
-
}
|
|
89
|
-
catch (err) {
|
|
90
|
-
console.error(chalk_1.default.red(`[WarehouseMock] 创建 Mock 目录失败: ${err}`));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const mockFileList = this.getMockFileList();
|
|
94
|
-
console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock 文件`));
|
|
95
|
-
if (mockFileList.length > 0) {
|
|
96
|
-
console.log(chalk_1.default.gray(` → ${mockFileList.join(', ')}`));
|
|
97
|
-
}
|
|
98
|
-
if (this.options.proxy) {
|
|
99
|
-
console.log(chalk_1.default.cyan(`[WarehouseMock] 代理模式: 未匹配请求 → ${this.options.proxy.target}`));
|
|
100
|
-
}
|
|
101
|
-
// 启动管理后台
|
|
102
|
-
if (this.options.admin && this.options.admin.enabled) {
|
|
103
|
-
this.startAdminServer();
|
|
104
|
-
}
|
|
37
|
+
// 启动引擎:确保目录、打印加载信息、启动管理后台
|
|
38
|
+
this.engine.start();
|
|
105
39
|
// 注意:devServer 的配置需要在 vue.config.js 中手动配置
|
|
106
40
|
// 对于 Webpack 5 使用 setupMiddlewares
|
|
107
41
|
// 对于 Webpack 4 使用 before
|
|
108
42
|
}
|
|
109
|
-
/**
|
|
110
|
-
* 启动管理后台服务
|
|
111
|
-
*/
|
|
112
|
-
startAdminServer() {
|
|
113
|
-
var _a;
|
|
114
|
-
try {
|
|
115
|
-
// 动态导入管理后台服务
|
|
116
|
-
let createAdminServer;
|
|
117
|
-
try {
|
|
118
|
-
// 尝试导入已安装的包
|
|
119
|
-
createAdminServer = require('warehouse-mock-admin').createAdminServer;
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
122
|
-
// 如果找不到,尝试相对路径(开发环境)
|
|
123
|
-
const adminPath = require('path').resolve(__dirname, '../../mock-admin/dist/server/index.js');
|
|
124
|
-
createAdminServer = require(adminPath).createAdminServer;
|
|
125
|
-
}
|
|
126
|
-
if (!createAdminServer) {
|
|
127
|
-
throw new Error('无法加载管理后台模块');
|
|
128
|
-
}
|
|
129
|
-
const adminResult = createAdminServer({
|
|
130
|
-
mockPath: this.resolvedMockPath,
|
|
131
|
-
port: ((_a = this.options.admin) === null || _a === void 0 ? void 0 : _a.port) || 3100,
|
|
132
|
-
});
|
|
133
|
-
this.adminServer = adminResult;
|
|
134
|
-
console.log(chalk_1.default.green(`\n╭────────────────────────────────────────────────────╮`));
|
|
135
|
-
console.log(chalk_1.default.green(`│ 🎨 Mock 数据管理后台已启动 │`));
|
|
136
|
-
console.log(chalk_1.default.green(`│ │`));
|
|
137
|
-
console.log(chalk_1.default.green(`│ ➜ 访问地址: ${chalk_1.default.cyan(adminResult.url).padEnd(31)} │`));
|
|
138
|
-
console.log(chalk_1.default.green(`│ │`));
|
|
139
|
-
console.log(chalk_1.default.green(`│ 在浏览器中打开上面的地址来管理 Mock 数据 │`));
|
|
140
|
-
console.log(chalk_1.default.green(`╰────────────────────────────────────────────────────╯\n`));
|
|
141
|
-
}
|
|
142
|
-
catch (err) {
|
|
143
|
-
console.log(chalk_1.default.yellow('[WarehouseMock] 管理后台包未安装或加载失败,跳过启动'));
|
|
144
|
-
console.log(chalk_1.default.gray(' 提示:运行 npm install warehouse-mock-admin 来安装管理后台'));
|
|
145
|
-
if (err.message && !err.message.includes('Cannot find module')) {
|
|
146
|
-
console.log(chalk_1.default.gray(` 错误详情: ${err.message}`));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
43
|
/**
|
|
151
44
|
* 自动注入环境变量 VUE_APP_MOCK
|
|
152
45
|
*/
|
|
@@ -187,11 +80,10 @@ class WarehouseMockPlugin {
|
|
|
187
80
|
*/
|
|
188
81
|
setupMiddlewares(middlewares, devServer, originalSetupMiddlewares) {
|
|
189
82
|
console.log(chalk_1.default.cyan('[WarehouseMock] Mock 服务已启动'));
|
|
190
|
-
|
|
191
|
-
this.ensureMockDirectory(mockPath);
|
|
83
|
+
this.engine.ensureMockDirectory();
|
|
192
84
|
middlewares.unshift({
|
|
193
85
|
name: 'warehouse-mock',
|
|
194
|
-
middleware:
|
|
86
|
+
middleware: this.engine.middleware,
|
|
195
87
|
});
|
|
196
88
|
if (originalSetupMiddlewares) {
|
|
197
89
|
return originalSetupMiddlewares(middlewares, devServer);
|
|
@@ -203,403 +95,11 @@ class WarehouseMockPlugin {
|
|
|
203
95
|
*/
|
|
204
96
|
runBefore(app, server, compiler, originalBefore) {
|
|
205
97
|
console.log(chalk_1.default.cyan('[WarehouseMock] Mock 服务已启动 (before hook)'));
|
|
206
|
-
|
|
207
|
-
this.
|
|
208
|
-
app.use((req, res, next) => this.handleRequest(req, res, next, mockPath));
|
|
98
|
+
this.engine.ensureMockDirectory();
|
|
99
|
+
app.use(this.engine.middleware);
|
|
209
100
|
if (originalBefore) {
|
|
210
101
|
originalBefore(app, server, compiler);
|
|
211
102
|
}
|
|
212
103
|
}
|
|
213
|
-
getResolvedMockPath() {
|
|
214
|
-
if (this.resolvedMockPath) {
|
|
215
|
-
return this.resolvedMockPath;
|
|
216
|
-
}
|
|
217
|
-
return path_1.default.isAbsolute(this.options.mockPath)
|
|
218
|
-
? this.options.mockPath
|
|
219
|
-
: path_1.default.resolve(process.cwd(), this.options.mockPath);
|
|
220
|
-
}
|
|
221
|
-
ensureMockDirectory(mockPath) {
|
|
222
|
-
if (!fs_1.default.existsSync(mockPath)) {
|
|
223
|
-
try {
|
|
224
|
-
fs_1.default.mkdirSync(mockPath, { recursive: true });
|
|
225
|
-
console.log(chalk_1.default.green(`[WarehouseMock] Mock 目录已创建: ${mockPath}`));
|
|
226
|
-
this.createDemoFile(mockPath);
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
console.error(chalk_1.default.red(`[WarehouseMock] 创建 Mock 目录失败: ${err}`));
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
createDemoFile(mockPath) {
|
|
234
|
-
const demoFilePath = path_1.default.join(mockPath, 'demo.json');
|
|
235
|
-
const demoContent = {
|
|
236
|
-
code: 0,
|
|
237
|
-
msg: 'success',
|
|
238
|
-
data: {
|
|
239
|
-
message: '这是自动生成的 demo 数据',
|
|
240
|
-
id: 1,
|
|
241
|
-
name: 'Demo User',
|
|
242
|
-
description: 'Warehouse Mock 自动创建的示例文件',
|
|
243
|
-
},
|
|
244
|
-
};
|
|
245
|
-
try {
|
|
246
|
-
fs_1.default.writeFileSync(demoFilePath, JSON.stringify(demoContent, null, 2));
|
|
247
|
-
console.log(chalk_1.default.green(`[WarehouseMock] 已创建示例 Mock 文件: ${demoFilePath}`));
|
|
248
|
-
}
|
|
249
|
-
catch (e) {
|
|
250
|
-
console.error(chalk_1.default.yellow(`[WarehouseMock] 创建示例文件失败: ${e}`));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
// 读取接口配置
|
|
254
|
-
readInterfaceConfig(apiName, mockPath) {
|
|
255
|
-
const configPath = path_1.default.join(mockPath, apiName, '.config.json');
|
|
256
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
257
|
-
try {
|
|
258
|
-
const content = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
259
|
-
return JSON.parse(content);
|
|
260
|
-
}
|
|
261
|
-
catch (e) {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
handleRequest(req, res, next, mockPath) {
|
|
268
|
-
var _a, _b, _c;
|
|
269
|
-
const url = req.path || ((_a = req.url) === null || _a === void 0 ? void 0 : _a.split('?')[0]);
|
|
270
|
-
if (!url) {
|
|
271
|
-
return next();
|
|
272
|
-
}
|
|
273
|
-
// ============ 特殊端点:实时返回 mock 文件列表 ============
|
|
274
|
-
if (url === '/__mock_list__') {
|
|
275
|
-
const fileList = this.getMockFileList();
|
|
276
|
-
res.setHeader('Content-Type', 'application/json');
|
|
277
|
-
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
278
|
-
res.end(JSON.stringify({
|
|
279
|
-
mockList: fileList,
|
|
280
|
-
localApiPrefix: this.getLocalApiPrefix(),
|
|
281
|
-
proxy: ((_b = this.options.proxy) === null || _b === void 0 ? void 0 : _b.target) || null,
|
|
282
|
-
enabled: this.isEnabled,
|
|
283
|
-
}));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
// 安全检查:防止目录遍历
|
|
287
|
-
if (url.includes('..')) {
|
|
288
|
-
return next();
|
|
289
|
-
}
|
|
290
|
-
// 检查是否在允许的 API 前缀范围内
|
|
291
|
-
const { apiPrefixes } = this.options;
|
|
292
|
-
const isApiRequest = (_c = apiPrefixes === null || apiPrefixes === void 0 ? void 0 : apiPrefixes.some((prefix) => url.startsWith(prefix))) !== null && _c !== void 0 ? _c : true;
|
|
293
|
-
if (!isApiRequest) {
|
|
294
|
-
return next();
|
|
295
|
-
}
|
|
296
|
-
let filePath = '';
|
|
297
|
-
let matched = false;
|
|
298
|
-
let matchedName = '';
|
|
299
|
-
let interfaceConfig = null;
|
|
300
|
-
// 1. 优先尝试 Query String 匹配 (RPC 风格接口,如 /api?user.taurus.pointInfo)
|
|
301
|
-
if (req.url && req.url.includes('?')) {
|
|
302
|
-
const queryPart = req.url.split('?')[1];
|
|
303
|
-
if (queryPart) {
|
|
304
|
-
const params = new URLSearchParams(queryPart);
|
|
305
|
-
for (const key of params.keys()) {
|
|
306
|
-
// 先尝试目录模式(优先,支持多场景)
|
|
307
|
-
const dirPath = path_1.default.join(mockPath, key);
|
|
308
|
-
if (fs_1.default.existsSync(dirPath) && fs_1.default.statSync(dirPath).isDirectory()) {
|
|
309
|
-
// 读取接口配置(新格式)
|
|
310
|
-
const config = this.readInterfaceConfig(key, mockPath);
|
|
311
|
-
if (config) {
|
|
312
|
-
// 新格式:检查是否启用
|
|
313
|
-
if (config.enabled === false) {
|
|
314
|
-
console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${key}`));
|
|
315
|
-
break; // 接口被禁用,不拦截
|
|
316
|
-
}
|
|
317
|
-
// 根据 activeScene 读取场景文件
|
|
318
|
-
const activeScene = config.activeScene || 'default';
|
|
319
|
-
const sceneFilePath = path_1.default.join(dirPath, `${activeScene}.json`);
|
|
320
|
-
if (fs_1.default.existsSync(sceneFilePath)) {
|
|
321
|
-
filePath = sceneFilePath;
|
|
322
|
-
matchedName = key;
|
|
323
|
-
matched = true;
|
|
324
|
-
interfaceConfig = config;
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
// 旧格式:查找 mock: true 的文件(向后兼容)
|
|
330
|
-
const files = fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
|
|
331
|
-
for (const file of files) {
|
|
332
|
-
const fullPath = path_1.default.join(dirPath, file);
|
|
333
|
-
try {
|
|
334
|
-
const content = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
335
|
-
const jsonData = JSON.parse(content);
|
|
336
|
-
if (jsonData.mock === true) {
|
|
337
|
-
filePath = fullPath;
|
|
338
|
-
matchedName = key;
|
|
339
|
-
matched = true;
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
catch (e) {
|
|
344
|
-
// 文件解析失败,跳过
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (matched)
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// 再尝试单文件模式(向后兼容)
|
|
352
|
-
if (!matched) {
|
|
353
|
-
const queryFilePath = path_1.default.join(mockPath, `${key}.json`);
|
|
354
|
-
if (fs_1.default.existsSync(queryFilePath) && fs_1.default.statSync(queryFilePath).isFile()) {
|
|
355
|
-
filePath = queryFilePath;
|
|
356
|
-
matchedName = key;
|
|
357
|
-
matched = true;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// 也尝试匹配 value (例如 method=user.taurus.pointInfo)
|
|
362
|
-
const value = params.get(key);
|
|
363
|
-
if (value && !matched) {
|
|
364
|
-
const valueDirPath = path_1.default.join(mockPath, value);
|
|
365
|
-
if (fs_1.default.existsSync(valueDirPath) && fs_1.default.statSync(valueDirPath).isDirectory()) {
|
|
366
|
-
// 读取接口配置(新格式)
|
|
367
|
-
const config = this.readInterfaceConfig(value, mockPath);
|
|
368
|
-
if (config) {
|
|
369
|
-
// 新格式:检查是否启用
|
|
370
|
-
if (config.enabled === false) {
|
|
371
|
-
console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${value}`));
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
// 根据 activeScene 读取场景文件
|
|
375
|
-
const activeScene = config.activeScene || 'default';
|
|
376
|
-
const sceneFilePath = path_1.default.join(valueDirPath, `${activeScene}.json`);
|
|
377
|
-
if (fs_1.default.existsSync(sceneFilePath)) {
|
|
378
|
-
filePath = sceneFilePath;
|
|
379
|
-
matchedName = value;
|
|
380
|
-
matched = true;
|
|
381
|
-
interfaceConfig = config;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// 旧格式:查找 mock: true 的文件(向后兼容)
|
|
387
|
-
const files = fs_1.default.readdirSync(valueDirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
|
|
388
|
-
for (const file of files) {
|
|
389
|
-
const fullPath = path_1.default.join(valueDirPath, file);
|
|
390
|
-
try {
|
|
391
|
-
const content = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
392
|
-
const jsonData = JSON.parse(content);
|
|
393
|
-
if (jsonData.mock === true) {
|
|
394
|
-
filePath = fullPath;
|
|
395
|
-
matchedName = value;
|
|
396
|
-
matched = true;
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
catch (e) {
|
|
401
|
-
// 跳过
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (matched)
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (!matched) {
|
|
409
|
-
const valueFilePath = path_1.default.join(mockPath, `${value}.json`);
|
|
410
|
-
if (fs_1.default.existsSync(valueFilePath) && fs_1.default.statSync(valueFilePath).isFile()) {
|
|
411
|
-
filePath = valueFilePath;
|
|
412
|
-
matchedName = value;
|
|
413
|
-
matched = true;
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
// 2. 尝试扁平化命名 (将路径中的 / 替换为 _)
|
|
422
|
-
if (!matched) {
|
|
423
|
-
const flatName = url.replace(/^\//, '').replace(/\//g, '_') || 'index';
|
|
424
|
-
const flatFilePath = path_1.default.join(mockPath, `${flatName}.json`);
|
|
425
|
-
if (fs_1.default.existsSync(flatFilePath) && fs_1.default.statSync(flatFilePath).isFile()) {
|
|
426
|
-
filePath = flatFilePath;
|
|
427
|
-
matchedName = flatName;
|
|
428
|
-
matched = true;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// 3. 尝试嵌套目录结构匹配 (向后兼容)
|
|
432
|
-
if (!matched) {
|
|
433
|
-
const nestedFilePath = path_1.default.join(mockPath, url + '.json');
|
|
434
|
-
if (fs_1.default.existsSync(nestedFilePath) && fs_1.default.statSync(nestedFilePath).isFile()) {
|
|
435
|
-
filePath = nestedFilePath;
|
|
436
|
-
matchedName = url;
|
|
437
|
-
matched = true;
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
const indexFilePath = path_1.default.join(mockPath, url, 'index.json');
|
|
441
|
-
if (fs_1.default.existsSync(indexFilePath) && fs_1.default.statSync(indexFilePath).isFile()) {
|
|
442
|
-
filePath = indexFilePath;
|
|
443
|
-
matchedName = url + '/index';
|
|
444
|
-
matched = true;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (matched) {
|
|
449
|
-
console.log(chalk_1.default.green(`[WarehouseMock] ✓ 拦截: ${req.url || url}`));
|
|
450
|
-
// 获取延时配置(优先使用接口级别的 delay)
|
|
451
|
-
const delayTime = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.delay) !== undefined
|
|
452
|
-
? interfaceConfig.delay
|
|
453
|
-
: this.options.delay || 0;
|
|
454
|
-
// 获取场景名称
|
|
455
|
-
const sceneName = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.activeScene) || path_1.default.basename(filePath, '.json');
|
|
456
|
-
console.log(chalk_1.default.gray(` → 场景: ${sceneName}`));
|
|
457
|
-
if (delayTime > 0) {
|
|
458
|
-
console.log(chalk_1.default.gray(` → 延迟: ${delayTime}ms`));
|
|
459
|
-
}
|
|
460
|
-
// 模拟网络延迟
|
|
461
|
-
const respond = () => {
|
|
462
|
-
try {
|
|
463
|
-
const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
464
|
-
try {
|
|
465
|
-
const jsonData = JSON.parse(fileContent);
|
|
466
|
-
// 新格式:纯净的场景数据(不包含 mock、scene、delay 等元数据)
|
|
467
|
-
// 旧格式:包含 mock、scene、delay、data 字段
|
|
468
|
-
let responseData = jsonData;
|
|
469
|
-
// 兼容旧格式
|
|
470
|
-
if (jsonData.mock !== undefined && jsonData.data !== undefined) {
|
|
471
|
-
responseData = jsonData.data;
|
|
472
|
-
}
|
|
473
|
-
res.setHeader('Content-Type', 'application/json');
|
|
474
|
-
res.setHeader('X-Mock-By', 'WarehouseMock');
|
|
475
|
-
res.setHeader('X-Mock-Scene', sceneName);
|
|
476
|
-
res.end(JSON.stringify(responseData));
|
|
477
|
-
}
|
|
478
|
-
catch (jsonErr) {
|
|
479
|
-
console.warn(chalk_1.default.yellow(`[WarehouseMock] 无效的 JSON 文件: ${filePath}`));
|
|
480
|
-
res.setHeader('Content-Type', 'text/plain');
|
|
481
|
-
res.end(fileContent);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
catch (e) {
|
|
485
|
-
console.error(chalk_1.default.red(`[WarehouseMock] 读取 Mock 文件失败: ${e}`));
|
|
486
|
-
res.statusCode = 500;
|
|
487
|
-
res.end('Mock Read Error');
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
// 应用延迟
|
|
491
|
-
if (delayTime > 0) {
|
|
492
|
-
setTimeout(respond, delayTime);
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
respond();
|
|
496
|
-
}
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
// 未匹配到 Mock 文件
|
|
500
|
-
if (this.options.proxy) {
|
|
501
|
-
// 如果配置了代理,转发到真实 API
|
|
502
|
-
console.log(chalk_1.default.gray(`[WarehouseMock] ⊳ 代理: ${req.url || url} → ${this.options.proxy.target}`));
|
|
503
|
-
this.proxyRequest(req, res, next);
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
// 未配置代理,直接放行
|
|
507
|
-
next();
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* 代理请求到真实 API(改进版)
|
|
512
|
-
*/
|
|
513
|
-
proxyRequest(req, res, next) {
|
|
514
|
-
if (!this.options.proxy) {
|
|
515
|
-
return next();
|
|
516
|
-
}
|
|
517
|
-
try {
|
|
518
|
-
const http = require('http');
|
|
519
|
-
const https = require('https');
|
|
520
|
-
const url = require('url');
|
|
521
|
-
// 构建完整的目标 URL
|
|
522
|
-
// 将 /mock-api?xxx 转换为 真实API/api?xxx
|
|
523
|
-
const targetUrl = this.options.proxy.target + req.url.replace('/mock-api', '/api');
|
|
524
|
-
const parsedUrl = url.parse(targetUrl);
|
|
525
|
-
const isHttps = parsedUrl.protocol === 'https:';
|
|
526
|
-
const lib = isHttps ? https : http;
|
|
527
|
-
// 清理请求头
|
|
528
|
-
const headers = Object.assign({}, req.headers);
|
|
529
|
-
delete headers.host;
|
|
530
|
-
headers.host = parsedUrl.hostname;
|
|
531
|
-
// 创建代理请求
|
|
532
|
-
const proxyReq = lib.request({
|
|
533
|
-
hostname: parsedUrl.hostname,
|
|
534
|
-
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
535
|
-
path: parsedUrl.path,
|
|
536
|
-
method: req.method,
|
|
537
|
-
headers: headers,
|
|
538
|
-
timeout: 30000, // 30秒超时
|
|
539
|
-
}, (proxyRes) => {
|
|
540
|
-
// 转发响应头
|
|
541
|
-
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
542
|
-
// 转发响应体
|
|
543
|
-
proxyRes.pipe(res);
|
|
544
|
-
});
|
|
545
|
-
// 错误处理
|
|
546
|
-
proxyReq.on('error', (err) => {
|
|
547
|
-
console.error(chalk_1.default.red(`[WarehouseMock] 代理请求失败: ${err.message}`));
|
|
548
|
-
console.error(chalk_1.default.red(` 目标地址: ${targetUrl}`));
|
|
549
|
-
// 返回友好的错误信息
|
|
550
|
-
if (!res.headersSent) {
|
|
551
|
-
res.statusCode = 502;
|
|
552
|
-
res.setHeader('Content-Type', 'application/json');
|
|
553
|
-
res.end(JSON.stringify({
|
|
554
|
-
code: -1,
|
|
555
|
-
msg: `代理请求失败: ${err.message}`,
|
|
556
|
-
error: 'PROXY_ERROR'
|
|
557
|
-
}));
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
// 超时处理
|
|
561
|
-
proxyReq.on('timeout', () => {
|
|
562
|
-
proxyReq.destroy();
|
|
563
|
-
if (!res.headersSent) {
|
|
564
|
-
res.statusCode = 504;
|
|
565
|
-
res.setHeader('Content-Type', 'application/json');
|
|
566
|
-
res.end(JSON.stringify({
|
|
567
|
-
code: -1,
|
|
568
|
-
msg: '代理请求超时',
|
|
569
|
-
error: 'PROXY_TIMEOUT'
|
|
570
|
-
}));
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
// 转发请求体
|
|
574
|
-
if (req.method === 'POST' || req.method === 'PUT') {
|
|
575
|
-
// 处理 POST/PUT 请求的 body
|
|
576
|
-
let body = '';
|
|
577
|
-
req.on('data', (chunk) => {
|
|
578
|
-
body += chunk.toString();
|
|
579
|
-
});
|
|
580
|
-
req.on('end', () => {
|
|
581
|
-
if (body) {
|
|
582
|
-
proxyReq.write(body);
|
|
583
|
-
}
|
|
584
|
-
proxyReq.end();
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
proxyReq.end();
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
catch (err) {
|
|
592
|
-
console.error(chalk_1.default.red(`[WarehouseMock] 代理配置错误: ${err}`));
|
|
593
|
-
if (!res.headersSent) {
|
|
594
|
-
res.statusCode = 500;
|
|
595
|
-
res.setHeader('Content-Type', 'application/json');
|
|
596
|
-
res.end(JSON.stringify({
|
|
597
|
-
code: -1,
|
|
598
|
-
msg: `代理配置错误: ${err}`,
|
|
599
|
-
error: 'PROXY_CONFIG_ERROR'
|
|
600
|
-
}));
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
104
|
}
|
|
605
105
|
module.exports = WarehouseMockPlugin;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warehouse-mock",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "一个专为 Vue
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "一个专为 Vue 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新(基于 warehouse-mock-core)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"chalk": "^4.1.2",
|
|
38
38
|
"chokidar": "^3.5.3",
|
|
39
|
-
"
|
|
40
|
-
"npm": "^11.7.0"
|
|
39
|
+
"warehouse-mock-core": "^1.0.0"
|
|
41
40
|
},
|
|
42
41
|
"optionalDependencies": {
|
|
43
42
|
"warehouse-mock-admin": "^1.0.1"
|