vite-plugin-automock 1.0.3 → 1.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/dist/{chunk-NWIN2A3G.mjs → chunk-DJWYFFPU.mjs} +14 -31
- package/dist/chunk-DJWYFFPU.mjs.map +1 -0
- package/dist/client/index.d.mts +0 -22
- package/dist/client/index.d.ts +0 -22
- package/dist/client/index.js +13 -31
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +829 -2830
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +836 -2471
- package/dist/index.mjs.map +1 -1
- package/dist/inspector-template.html +1951 -0
- package/package.json +19 -13
- package/dist/chunk-NWIN2A3G.mjs.map +0 -1
- package/dist/chunk-PS6HLNJZ.mjs +0 -339
- package/dist/chunk-PS6HLNJZ.mjs.map +0 -1
- package/dist/mockFileUtils-MO32XIKQ.mjs +0 -17
- package/dist/mockFileUtils-MO32XIKQ.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,142 +27,178 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// src/
|
|
34
|
-
var
|
|
35
|
-
__export(
|
|
36
|
-
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
automock: () => automock2,
|
|
37
34
|
buildMockIndex: () => buildMockIndex,
|
|
35
|
+
bundleMockFiles: () => bundleMockFiles,
|
|
36
|
+
createMockInterceptor: () => createMockInterceptor,
|
|
37
|
+
initMockInterceptor: () => initMockInterceptor,
|
|
38
|
+
initMockInterceptorForPureHttp: () => initMockInterceptorForPureHttp,
|
|
39
|
+
isMockEnabled: () => isMockEnabled,
|
|
40
|
+
loadMockData: () => loadMockData,
|
|
38
41
|
parseMockModule: () => parseMockModule,
|
|
42
|
+
registerHttpInstance: () => registerHttpInstance,
|
|
39
43
|
saveMockData: () => saveMockData,
|
|
40
|
-
|
|
44
|
+
setMockEnabled: () => setMockEnabled,
|
|
45
|
+
writeMockBundle: () => writeMockBundle,
|
|
41
46
|
writeMockFile: () => writeMockFile
|
|
42
47
|
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
var import_path6 = __toESM(require("path"));
|
|
50
|
+
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
51
|
+
|
|
52
|
+
// src/middleware.ts
|
|
53
|
+
var import_chokidar = __toESM(require("chokidar"));
|
|
54
|
+
var import_lodash = __toESM(require("lodash.debounce"));
|
|
55
|
+
var import_path4 = __toESM(require("path"));
|
|
56
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
57
|
+
var import_http = __toESM(require("http"));
|
|
58
|
+
var import_https = __toESM(require("https"));
|
|
59
|
+
|
|
60
|
+
// src/mockFileUtils.ts
|
|
61
|
+
var import_path2 = __toESM(require("path"));
|
|
62
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
63
|
+
var import_prettier = __toESM(require("prettier"));
|
|
64
|
+
|
|
65
|
+
// src/utils.ts
|
|
66
|
+
var import_path = __toESM(require("path"));
|
|
67
|
+
function resolveAbsolutePath(p) {
|
|
68
|
+
return import_path.default.isAbsolute(p) ? p : import_path.default.resolve(process.cwd(), p);
|
|
69
|
+
}
|
|
43
70
|
function toPosixPath(p) {
|
|
44
71
|
return p.replace(/\\/g, "/");
|
|
45
72
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"image/",
|
|
73
|
-
"video/",
|
|
74
|
-
"audio/"
|
|
75
|
-
];
|
|
76
|
-
return binaryTypes.some((type) => contentType.toLowerCase().includes(type)) || !isBufferTextLike(data);
|
|
77
|
-
};
|
|
78
|
-
isBufferTextLike = (buffer) => {
|
|
79
|
-
try {
|
|
80
|
-
const sample = buffer.slice(0, 100);
|
|
81
|
-
const text = sample.toString("utf8");
|
|
82
|
-
const nullBytes = [...sample].filter((b) => b === 0).length;
|
|
83
|
-
const controlChars = [...sample].filter(
|
|
84
|
-
(b) => b < 32 && b !== 9 && b !== 10 && b !== 13
|
|
85
|
-
).length;
|
|
86
|
-
return nullBytes === 0 && controlChars < 5;
|
|
87
|
-
} catch {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
getFileExtension = (contentType, url) => {
|
|
92
|
-
const mimeMap = {
|
|
93
|
-
"application/json": "json",
|
|
94
|
-
"application/pdf": "pdf",
|
|
95
|
-
"application/zip": "zip",
|
|
96
|
-
"application/vnd.ms-excel": "xls",
|
|
97
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
98
|
-
"application/msword": "doc",
|
|
99
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
100
|
-
"application/vnd.ms-powerpoint": "ppt",
|
|
101
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
|
|
102
|
-
"image/jpeg": "jpg",
|
|
103
|
-
"image/png": "png",
|
|
104
|
-
"image/gif": "gif",
|
|
105
|
-
"text/plain": "txt",
|
|
106
|
-
"text/html": "html",
|
|
107
|
-
"text/css": "css",
|
|
108
|
-
"application/javascript": "js",
|
|
109
|
-
"text/xml": "xml"
|
|
110
|
-
};
|
|
111
|
-
if (contentType && mimeMap[contentType.toLowerCase()]) {
|
|
112
|
-
return mimeMap[contentType.toLowerCase()];
|
|
73
|
+
function getServerAddress(address, isHttps) {
|
|
74
|
+
return {
|
|
75
|
+
protocol: isHttps ? "https" : "http",
|
|
76
|
+
host: address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address,
|
|
77
|
+
port: address.port
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function sendJson(res, data, status = 200) {
|
|
81
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
82
|
+
res.statusCode = status;
|
|
83
|
+
res.end(typeof data === "string" ? data : JSON.stringify(data));
|
|
84
|
+
}
|
|
85
|
+
function sendError(res, message, status = 500) {
|
|
86
|
+
sendJson(res, { error: message }, status);
|
|
87
|
+
}
|
|
88
|
+
var MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
89
|
+
function readBody(req) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const chunks = [];
|
|
92
|
+
let totalSize = 0;
|
|
93
|
+
req.on("data", (chunk) => {
|
|
94
|
+
totalSize += chunk.length;
|
|
95
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
96
|
+
reject(new Error("Request body too large"));
|
|
97
|
+
req.destroy();
|
|
98
|
+
return;
|
|
113
99
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
chunks.push(chunk);
|
|
101
|
+
});
|
|
102
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
103
|
+
req.on("error", reject);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/mockFileUtils.ts
|
|
108
|
+
var DEFAULT_CONFIG = {
|
|
109
|
+
enable: true,
|
|
110
|
+
data: null,
|
|
111
|
+
delay: 0,
|
|
112
|
+
status: 200
|
|
113
|
+
};
|
|
114
|
+
var isBufferTextLike = (buffer) => {
|
|
115
|
+
try {
|
|
116
|
+
const sample = buffer.slice(0, 100);
|
|
117
|
+
const nullBytes = [...sample].filter((b) => b === 0).length;
|
|
118
|
+
const controlChars = [...sample].filter(
|
|
119
|
+
(b) => b < 32 && b !== 9 && b !== 10 && b !== 13
|
|
120
|
+
).length;
|
|
121
|
+
return nullBytes === 0 && controlChars < 5;
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var isBinaryResponse = (contentType, data) => {
|
|
127
|
+
if (!contentType) return false;
|
|
128
|
+
const binaryTypes = [
|
|
129
|
+
"application/octet-stream",
|
|
130
|
+
"application/pdf",
|
|
131
|
+
"application/zip",
|
|
132
|
+
"application/x-zip-compressed",
|
|
133
|
+
"application/vnd.ms-excel",
|
|
134
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
135
|
+
"application/msword",
|
|
136
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
137
|
+
"application/vnd.ms-powerpoint",
|
|
138
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
139
|
+
"image/",
|
|
140
|
+
"video/",
|
|
141
|
+
"audio/"
|
|
142
|
+
];
|
|
143
|
+
return binaryTypes.some((type) => contentType.toLowerCase().includes(type)) || !isBufferTextLike(data);
|
|
144
|
+
};
|
|
145
|
+
var getFileExtension = (contentType, url) => {
|
|
146
|
+
const mimeMap = {
|
|
147
|
+
"application/json": "json",
|
|
148
|
+
"application/pdf": "pdf",
|
|
149
|
+
"application/zip": "zip",
|
|
150
|
+
"application/vnd.ms-excel": "xls",
|
|
151
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
152
|
+
"application/msword": "doc",
|
|
153
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
154
|
+
"application/vnd.ms-powerpoint": "ppt",
|
|
155
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
|
|
156
|
+
"image/jpeg": "jpg",
|
|
157
|
+
"image/png": "png",
|
|
158
|
+
"image/gif": "gif",
|
|
159
|
+
"text/plain": "txt",
|
|
160
|
+
"text/html": "html",
|
|
161
|
+
"text/css": "css",
|
|
162
|
+
"application/javascript": "js",
|
|
163
|
+
"text/xml": "xml"
|
|
164
|
+
};
|
|
165
|
+
if (contentType && mimeMap[contentType.toLowerCase()]) {
|
|
166
|
+
return mimeMap[contentType.toLowerCase()];
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const urlObj = new URL(url, "http://localhost");
|
|
170
|
+
const fileName = urlObj.searchParams.get("file_name");
|
|
171
|
+
if (fileName) {
|
|
172
|
+
const extensionMatch = fileName.match(/\.([a-zA-Z0-9]+)$/);
|
|
173
|
+
if (extensionMatch) {
|
|
174
|
+
return extensionMatch[1];
|
|
124
175
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
pathname = urlObj.pathname;
|
|
140
|
-
search = urlObj.search;
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
const [pathPart, ...searchPart] = url.split("?");
|
|
144
|
-
pathname = pathPart;
|
|
145
|
-
search = searchPart.length > 0 ? "?" + searchPart.join("?") : "";
|
|
146
|
-
}
|
|
147
|
-
const filePath = import_path.default.join(
|
|
148
|
-
absoluteRootDir,
|
|
149
|
-
pathname.replace(/^\//, ""),
|
|
150
|
-
method.toLowerCase() + ".js"
|
|
151
|
-
);
|
|
152
|
-
const dir = import_path.default.dirname(filePath);
|
|
153
|
-
import_fs_extra.default.ensureDirSync(dir);
|
|
154
|
-
const isBuffer = Buffer.isBuffer(data);
|
|
155
|
-
const binaryData = isBuffer ? data : Buffer.from(data || "");
|
|
156
|
-
const isBinary = isBinaryResponse(contentType, binaryData);
|
|
157
|
-
if (isBinary) {
|
|
158
|
-
const extension = getFileExtension(contentType, url);
|
|
159
|
-
const binaryFilePath = filePath.replace(/\.js$/, "." + extension);
|
|
160
|
-
if (import_fs_extra.default.existsSync(binaryFilePath)) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
import_fs_extra.default.writeFileSync(binaryFilePath, binaryData);
|
|
164
|
-
const configContent = `/**
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
return "bin";
|
|
180
|
+
};
|
|
181
|
+
async function formatWithPrettier(content) {
|
|
182
|
+
try {
|
|
183
|
+
return await import_prettier.default.format(content, { parser: "babel" });
|
|
184
|
+
} catch {
|
|
185
|
+
return content;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function buildMockFileHeader(pathname, method, search, extra) {
|
|
189
|
+
return `/**
|
|
165
190
|
* Mock data for ${pathname} (${method.toUpperCase()})${search || ""}
|
|
166
|
-
* @description ${pathname}${search || ""}
|
|
191
|
+
* @description ${pathname}${search || ""}${extra ? ` - ${extra}` : ""}
|
|
167
192
|
* Generated at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
168
|
-
|
|
193
|
+
*/`;
|
|
194
|
+
}
|
|
195
|
+
async function saveBinaryMock(filePath, binaryData, pathname, method, search, contentType, url, statusCode) {
|
|
196
|
+
const extension = getFileExtension(contentType, url);
|
|
197
|
+
const binaryFilePath = filePath.replace(/\.js$/, "." + extension);
|
|
198
|
+
if (import_fs_extra.default.existsSync(binaryFilePath)) return null;
|
|
199
|
+
import_fs_extra.default.writeFileSync(binaryFilePath, binaryData);
|
|
200
|
+
const header = buildMockFileHeader(pathname, method, search, `Binary file (${extension})`);
|
|
201
|
+
const configContent = `${header}
|
|
169
202
|
export default {
|
|
170
203
|
enable: false,
|
|
171
204
|
data: {
|
|
@@ -179,236 +212,211 @@ export default {
|
|
|
179
212
|
delay: 0,
|
|
180
213
|
status: ${statusCode || 200}
|
|
181
214
|
}`;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
originalData: jsonData,
|
|
217
|
-
__mockStatusCode: statusCode,
|
|
218
|
-
__isErrorResponse: true
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
jsonData = {
|
|
224
|
-
error: true,
|
|
225
|
-
message: `Non-JSON response (${statusCode || "unknown status"})`,
|
|
226
|
-
status: statusCode || 404,
|
|
227
|
-
data: dataStr,
|
|
228
|
-
__mockStatusCode: statusCode,
|
|
229
|
-
__isErrorResponse: true
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const content = `/**
|
|
234
|
-
* Mock data for ${pathname} (${method.toUpperCase()})${search || ""}
|
|
235
|
-
* @description ${pathname}${search || ""}
|
|
236
|
-
* Generated at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
237
|
-
*/
|
|
215
|
+
const formatted = await formatWithPrettier(configContent);
|
|
216
|
+
import_fs_extra.default.writeFileSync(filePath, formatted, "utf-8");
|
|
217
|
+
return filePath;
|
|
218
|
+
}
|
|
219
|
+
async function saveJsonMock(filePath, dataStr, pathname, method, search, statusCode) {
|
|
220
|
+
if (import_fs_extra.default.existsSync(filePath)) return null;
|
|
221
|
+
let jsonData;
|
|
222
|
+
if (!dataStr || dataStr.trim() === "") {
|
|
223
|
+
jsonData = {
|
|
224
|
+
error: true,
|
|
225
|
+
message: `Empty response (${statusCode || "unknown status"})`,
|
|
226
|
+
status: statusCode || 404,
|
|
227
|
+
data: null
|
|
228
|
+
};
|
|
229
|
+
} else {
|
|
230
|
+
try {
|
|
231
|
+
jsonData = JSON.parse(dataStr);
|
|
232
|
+
if (statusCode && statusCode >= 400) {
|
|
233
|
+
const errorMeta = { __mockStatusCode: statusCode, __isErrorResponse: true };
|
|
234
|
+
jsonData = typeof jsonData === "object" && jsonData !== null ? { ...jsonData, ...errorMeta } : { originalData: jsonData, ...errorMeta };
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
jsonData = {
|
|
238
|
+
error: true,
|
|
239
|
+
message: `Non-JSON response (${statusCode || "unknown status"})`,
|
|
240
|
+
status: statusCode || 404,
|
|
241
|
+
data: dataStr,
|
|
242
|
+
__mockStatusCode: statusCode,
|
|
243
|
+
__isErrorResponse: true
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const header = buildMockFileHeader(pathname, method, search);
|
|
248
|
+
const content = `${header}
|
|
238
249
|
export default {
|
|
239
250
|
enable: false,
|
|
240
251
|
data: ${JSON.stringify(jsonData)},
|
|
241
252
|
delay: 0,
|
|
242
253
|
status: ${statusCode || 200}
|
|
243
254
|
}`;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const urlPath = "/" + toPosixPath(dirPath);
|
|
298
|
-
const absolutePath = import_path.default.isAbsolute(filePath) ? filePath : import_path.default.resolve(process.cwd(), filePath);
|
|
299
|
-
const key = `${urlPath}/${method}.js`.toLowerCase();
|
|
300
|
-
mockFileMap.set(key, absolutePath);
|
|
301
|
-
} catch (error) {
|
|
302
|
-
console.error(`\u274C [automock] \u5904\u7406\u6587\u4EF6\u5931\u8D25 ${filePath}:`, error);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
return mockFileMap;
|
|
306
|
-
};
|
|
307
|
-
parseMockModule = async (filePath) => {
|
|
308
|
-
const absolutePath = import_path.default.isAbsolute(filePath) ? filePath : import_path.default.resolve(process.cwd(), filePath);
|
|
309
|
-
if (!import_fs_extra.default.existsSync(absolutePath)) {
|
|
310
|
-
throw new Error(`Mock file does not exist: ${absolutePath}`);
|
|
311
|
-
}
|
|
312
|
-
const content = import_fs_extra.default.readFileSync(absolutePath, "utf-8");
|
|
313
|
-
const headerCommentMatch = content.match(/^(\/\*\*[\s\S]*?\*\/)/);
|
|
314
|
-
const headerComment = headerCommentMatch ? headerCommentMatch[1] : void 0;
|
|
315
|
-
let description;
|
|
316
|
-
if (headerComment) {
|
|
317
|
-
const descMatch = headerComment.match(/@description\s+(.+?)(?:\n|\*\/)/s);
|
|
318
|
-
if (descMatch) {
|
|
319
|
-
description = descMatch[1].trim();
|
|
320
|
-
}
|
|
255
|
+
const formatted = await formatWithPrettier(content);
|
|
256
|
+
import_fs_extra.default.writeFileSync(filePath, formatted, "utf-8");
|
|
257
|
+
return filePath;
|
|
258
|
+
}
|
|
259
|
+
async function saveMockData(url, method, data, rootDir, statusCode, contentType) {
|
|
260
|
+
try {
|
|
261
|
+
const absoluteRootDir = resolveAbsolutePath(rootDir);
|
|
262
|
+
const urlObj = new URL(url, "http://localhost");
|
|
263
|
+
const pathname = urlObj.pathname;
|
|
264
|
+
const search = urlObj.search;
|
|
265
|
+
const filePath = import_path2.default.join(
|
|
266
|
+
absoluteRootDir,
|
|
267
|
+
pathname.replace(/^\//, ""),
|
|
268
|
+
method.toLowerCase() + ".js"
|
|
269
|
+
);
|
|
270
|
+
await import_fs_extra.default.ensureDir(import_path2.default.dirname(filePath));
|
|
271
|
+
const isBuffer = Buffer.isBuffer(data);
|
|
272
|
+
const binaryData = isBuffer ? data : Buffer.from(data || "");
|
|
273
|
+
const isBinary = isBinaryResponse(contentType, binaryData);
|
|
274
|
+
if (isBinary) {
|
|
275
|
+
return saveBinaryMock(
|
|
276
|
+
filePath,
|
|
277
|
+
binaryData,
|
|
278
|
+
pathname,
|
|
279
|
+
method,
|
|
280
|
+
search,
|
|
281
|
+
contentType,
|
|
282
|
+
url,
|
|
283
|
+
statusCode
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const dataStr = isBuffer ? data.toString("utf8") : data || "";
|
|
287
|
+
return saveJsonMock(filePath, dataStr, pathname, method, search, statusCode);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error(`Failed to save mock data for ${url}:`, error);
|
|
290
|
+
console.error(
|
|
291
|
+
`URL details: url=${url}, method=${method}, statusCode=${statusCode}, contentType=${contentType}`
|
|
292
|
+
);
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
var recursiveReadAllFiles = async (dir) => {
|
|
297
|
+
if (!await import_fs_extra.default.pathExists(dir)) return [];
|
|
298
|
+
const files = [];
|
|
299
|
+
try {
|
|
300
|
+
const list = await import_fs_extra.default.readdir(dir);
|
|
301
|
+
for (const file of list) {
|
|
302
|
+
const filePath = import_path2.default.join(dir, file);
|
|
303
|
+
const stat = await import_fs_extra.default.stat(filePath);
|
|
304
|
+
if (stat.isDirectory()) {
|
|
305
|
+
files.push(...await recursiveReadAllFiles(filePath));
|
|
306
|
+
} else {
|
|
307
|
+
files.push(filePath);
|
|
321
308
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error(`Error reading directory ${dir}:`, error);
|
|
312
|
+
}
|
|
313
|
+
return files;
|
|
314
|
+
};
|
|
315
|
+
async function buildMockIndex(mockDir) {
|
|
316
|
+
const mockFileMap = /* @__PURE__ */ new Map();
|
|
317
|
+
if (!await import_fs_extra.default.pathExists(mockDir)) {
|
|
318
|
+
await import_fs_extra.default.ensureDir(mockDir);
|
|
319
|
+
return mockFileMap;
|
|
320
|
+
}
|
|
321
|
+
const files = await recursiveReadAllFiles(mockDir);
|
|
322
|
+
files.forEach((filePath) => {
|
|
323
|
+
if (!filePath.endsWith(".js")) return;
|
|
324
|
+
try {
|
|
325
|
+
const relativePath = import_path2.default.relative(mockDir, filePath);
|
|
326
|
+
const method = import_path2.default.basename(filePath, ".js");
|
|
327
|
+
const dirPath = import_path2.default.dirname(relativePath);
|
|
328
|
+
const urlPath = "/" + toPosixPath(dirPath);
|
|
329
|
+
const absolutePath = resolveAbsolutePath(filePath);
|
|
330
|
+
const key = `${urlPath}/${method}.js`.toLowerCase();
|
|
331
|
+
mockFileMap.set(key, absolutePath);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error(`[automock] Failed to process file ${filePath}:`, error);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return mockFileMap;
|
|
337
|
+
}
|
|
338
|
+
async function parseMockModule(filePath) {
|
|
339
|
+
const absolutePath = resolveAbsolutePath(filePath);
|
|
340
|
+
if (!import_fs_extra.default.existsSync(absolutePath)) {
|
|
341
|
+
throw new Error(`Mock file does not exist: ${absolutePath}`);
|
|
342
|
+
}
|
|
343
|
+
const content = import_fs_extra.default.readFileSync(absolutePath, "utf-8");
|
|
344
|
+
const headerCommentMatch = content.match(/^(\/\*\*[\s\S]*?\*\/)/);
|
|
345
|
+
const headerComment = headerCommentMatch ? headerCommentMatch[1] : void 0;
|
|
346
|
+
let description;
|
|
347
|
+
if (headerComment) {
|
|
348
|
+
const descMatch = headerComment.match(/@description\s+(.+?)(?:\n|\*\/)/s);
|
|
349
|
+
if (descMatch) {
|
|
350
|
+
description = descMatch[1].trim();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const { default: mockModule } = await import(`${absolutePath}?t=${Date.now()}`);
|
|
354
|
+
const exportedConfig = typeof mockModule === "function" ? mockModule() : mockModule;
|
|
355
|
+
const hasDynamicData = typeof exportedConfig?.data === "function";
|
|
356
|
+
const config = {
|
|
357
|
+
...DEFAULT_CONFIG,
|
|
358
|
+
...exportedConfig ?? {}
|
|
359
|
+
};
|
|
360
|
+
const serializable = !hasDynamicData;
|
|
361
|
+
const dataObj = typeof config.data === "object" && config.data !== null ? config.data : null;
|
|
362
|
+
const isBinary = dataObj !== null && "__binaryFile" in dataObj;
|
|
363
|
+
return {
|
|
364
|
+
config,
|
|
365
|
+
serializable,
|
|
366
|
+
hasDynamicData,
|
|
367
|
+
headerComment,
|
|
368
|
+
description,
|
|
369
|
+
dataText: isBinary ? `/* Binary file: ${dataObj?.__binaryFile} */` : serializable ? JSON.stringify(config.data ?? null, null, 2) : "/* data is generated dynamically and cannot be edited here */",
|
|
370
|
+
isBinary
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function writeMockFile(filePath, mockInfo) {
|
|
374
|
+
const absolutePath = resolveAbsolutePath(filePath);
|
|
375
|
+
let header = mockInfo.headerComment || "";
|
|
376
|
+
if (mockInfo.description !== void 0) {
|
|
377
|
+
if (header) {
|
|
378
|
+
if (/@description/.test(header)) {
|
|
379
|
+
header = header.replace(
|
|
380
|
+
/@description\s+.+?(?=\n|\*\/)/s,
|
|
381
|
+
`@description ${mockInfo.description}`
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
header = header.replace(
|
|
385
|
+
/\*\//,
|
|
386
|
+
` * @description ${mockInfo.description}
|
|
356
387
|
*/`
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
}
|
|
388
|
+
);
|
|
360
389
|
}
|
|
361
|
-
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const content = header ? `${header}
|
|
362
393
|
` : "";
|
|
363
|
-
|
|
394
|
+
const finalContent = `${content}export default ${JSON.stringify(mockInfo.config, null, 2)}
|
|
364
395
|
`;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
console.error("Error formatting code with prettier:", error);
|
|
372
|
-
import_fs_extra.default.writeFileSync(absolutePath, finalContent, "utf-8");
|
|
373
|
-
}
|
|
374
|
-
};
|
|
396
|
+
try {
|
|
397
|
+
const formatted = await formatWithPrettier(finalContent);
|
|
398
|
+
import_fs_extra.default.writeFileSync(absolutePath, formatted, "utf-8");
|
|
399
|
+
} catch {
|
|
400
|
+
console.error("Error formatting code with prettier, writing raw content");
|
|
401
|
+
import_fs_extra.default.writeFileSync(absolutePath, finalContent, "utf-8");
|
|
375
402
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/index.ts
|
|
379
|
-
var index_exports = {};
|
|
380
|
-
__export(index_exports, {
|
|
381
|
-
automock: () => automock2,
|
|
382
|
-
buildMockIndex: () => buildMockIndex,
|
|
383
|
-
bundleMockFiles: () => bundleMockFiles,
|
|
384
|
-
createMockInterceptor: () => createMockInterceptor,
|
|
385
|
-
initMockInterceptor: () => initMockInterceptor,
|
|
386
|
-
initMockInterceptorForPureHttp: () => initMockInterceptorForPureHttp,
|
|
387
|
-
isMockEnabled: () => isMockEnabled,
|
|
388
|
-
loadMockData: () => loadMockData,
|
|
389
|
-
parseMockModule: () => parseMockModule,
|
|
390
|
-
registerHttpInstance: () => registerHttpInstance,
|
|
391
|
-
saveMockData: () => saveMockData,
|
|
392
|
-
setMockEnabled: () => setMockEnabled,
|
|
393
|
-
writeMockBundle: () => writeMockBundle,
|
|
394
|
-
writeMockFile: () => writeMockFile
|
|
395
|
-
});
|
|
396
|
-
module.exports = __toCommonJS(index_exports);
|
|
397
|
-
var import_path5 = __toESM(require("path"));
|
|
398
|
-
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
403
|
+
}
|
|
399
404
|
|
|
400
|
-
// src/
|
|
401
|
-
var import_chokidar = __toESM(require("chokidar"));
|
|
402
|
-
var import_lodash = __toESM(require("lodash.debounce"));
|
|
405
|
+
// src/inspector.ts
|
|
403
406
|
var import_path3 = __toESM(require("path"));
|
|
404
407
|
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
408
|
+
function buildMockItem(key, filePath, mockDir, info) {
|
|
409
|
+
return {
|
|
410
|
+
key,
|
|
411
|
+
file: import_path3.default.relative(mockDir, filePath),
|
|
412
|
+
method: key.split("/").pop()?.replace(/\.js$/, "") ?? "get",
|
|
413
|
+
path: key.replace(/\/[^/]+\.js$/, ""),
|
|
414
|
+
config: info.config,
|
|
415
|
+
editable: info.serializable,
|
|
416
|
+
description: info.description,
|
|
417
|
+
dataText: info.dataText
|
|
418
|
+
};
|
|
419
|
+
}
|
|
412
420
|
var DEFAULT_ROUTE = "/__mock/";
|
|
413
421
|
function ensureTrailingSlash(route) {
|
|
414
422
|
return route.endsWith("/") ? route : `${route}/`;
|
|
@@ -432,2249 +440,433 @@ function createInspectorHandler(options) {
|
|
|
432
440
|
if (!options.inspector) {
|
|
433
441
|
return null;
|
|
434
442
|
}
|
|
435
|
-
const inspectorConfig = normalizeInspectorConfig(options.inspector);
|
|
436
|
-
const inspectorRoute = ensureTrailingSlash(inspectorConfig.route);
|
|
437
|
-
return async (req, res) => {
|
|
438
|
-
if (!req.url) {
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
const url = new URL(req.url, "http://localhost");
|
|
442
|
-
if (!url.pathname.startsWith(inspectorRoute)) {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
await handleInspectorRequest({
|
|
446
|
-
req,
|
|
447
|
-
res,
|
|
448
|
-
mockDir: options.mockDir,
|
|
449
|
-
inspectorRoute,
|
|
450
|
-
apiPrefix: options.apiPrefix,
|
|
451
|
-
inspectorConfig,
|
|
452
|
-
getMockFileMap: options.getMockFileMap
|
|
453
|
-
});
|
|
454
|
-
return true;
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
async function handleInspectorRequest(context) {
|
|
458
|
-
const { req, res, inspectorRoute } = context;
|
|
459
|
-
const url = new URL(req.url || inspectorRoute, "http://localhost");
|
|
460
|
-
const normalizedRoute = ensureTrailingSlash(inspectorRoute);
|
|
461
|
-
if (url.pathname === normalizedRoute.slice(0, -1) || url.pathname === normalizedRoute) {
|
|
462
|
-
await serveInspectorHtml(context);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const relativePath = url.pathname.startsWith(normalizedRoute) ? url.pathname.slice(normalizedRoute.length) : null;
|
|
466
|
-
if (relativePath && relativePath.startsWith("api/")) {
|
|
467
|
-
await handleInspectorApi({ ...context, pathname: relativePath.slice(4) });
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
res.statusCode = 404;
|
|
471
|
-
res.end("Not Found");
|
|
472
|
-
}
|
|
473
|
-
async function serveInspectorHtml({
|
|
474
|
-
res,
|
|
475
|
-
inspectorRoute,
|
|
476
|
-
apiPrefix,
|
|
477
|
-
inspectorConfig
|
|
478
|
-
}) {
|
|
479
|
-
const routeJson = JSON.stringify(ensureTrailingSlash(inspectorRoute));
|
|
480
|
-
const allowToggleJson = JSON.stringify(inspectorConfig.enableToggle);
|
|
481
|
-
const apiPrefixEscaped = escapeHtml(apiPrefix);
|
|
482
|
-
const html = `<!DOCTYPE html>
|
|
483
|
-
<html lang="en">
|
|
484
|
-
<head>
|
|
485
|
-
<meta charset="UTF-8" />
|
|
486
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
487
|
-
<title>Mock Inspector</title>
|
|
488
|
-
<style>
|
|
489
|
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
490
|
-
|
|
491
|
-
:root {
|
|
492
|
-
--bg-primary: #ffffff;
|
|
493
|
-
--bg-secondary: #f9fafb;
|
|
494
|
-
--bg-tertiary: #f3f4f6;
|
|
495
|
-
--bg-hover: #e5e7eb;
|
|
496
|
-
--border-color: #e5e7eb;
|
|
497
|
-
--border-subtle: #f3f4f6;
|
|
498
|
-
--text-primary: #111827;
|
|
499
|
-
--text-secondary: #4b5563;
|
|
500
|
-
--text-muted: #9ca3af;
|
|
501
|
-
--accent-indigo: #6366f1;
|
|
502
|
-
--accent-indigo-light: #e0e7ff;
|
|
503
|
-
--accent-indigo-hover: #4f46e5;
|
|
504
|
-
--accent-emerald: #10b981;
|
|
505
|
-
--accent-emerald-light: #d1fae5;
|
|
506
|
-
--accent-amber: #f59e0b;
|
|
507
|
-
--accent-amber-light: #fef3c7;
|
|
508
|
-
--accent-rose: #ef4444;
|
|
509
|
-
--accent-rose-light: #fee2e2;
|
|
510
|
-
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
511
|
-
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
512
|
-
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
513
|
-
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
514
|
-
--radius-sm: 6px;
|
|
515
|
-
--radius-md: 8px;
|
|
516
|
-
--radius-lg: 12px;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
* {
|
|
520
|
-
box-sizing: border-box;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
body {
|
|
524
|
-
margin: 0;
|
|
525
|
-
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 25%, #fdf4ff 50%, #ecfdf5 75%, #f0fdf4 100%);
|
|
526
|
-
color: var(--text-primary);
|
|
527
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
528
|
-
display: flex;
|
|
529
|
-
flex-direction: column;
|
|
530
|
-
height: 100vh;
|
|
531
|
-
overflow: hidden;
|
|
532
|
-
position: relative;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/* Multiple ambient gradient orbs */
|
|
536
|
-
body::before {
|
|
537
|
-
content: '';
|
|
538
|
-
position: fixed;
|
|
539
|
-
top: -15%;
|
|
540
|
-
right: -10%;
|
|
541
|
-
width: 60vw;
|
|
542
|
-
height: 60vw;
|
|
543
|
-
background: radial-gradient(circle, rgba(99, 102, 241, 0.25) 0%, rgba(139, 92, 246, 0.15) 30%, transparent 70%);
|
|
544
|
-
filter: blur(100px);
|
|
545
|
-
pointer-events: none;
|
|
546
|
-
z-index: 0;
|
|
547
|
-
animation: float 20s ease-in-out infinite;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
body::after {
|
|
551
|
-
content: '';
|
|
552
|
-
position: fixed;
|
|
553
|
-
bottom: -15%;
|
|
554
|
-
left: -10%;
|
|
555
|
-
width: 50vw;
|
|
556
|
-
height: 50vw;
|
|
557
|
-
background: radial-gradient(circle, rgba(16, 185, 129, 0.2) 0%, rgba(34, 197, 94, 0.12) 30%, transparent 70%);
|
|
558
|
-
filter: blur(100px);
|
|
559
|
-
pointer-events: none;
|
|
560
|
-
z-index: 0;
|
|
561
|
-
animation: float 25s ease-in-out infinite reverse;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
@keyframes float {
|
|
565
|
-
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
566
|
-
33% { transform: translate(30px, -30px) scale(1.05); }
|
|
567
|
-
66% { transform: translate(-20px, 20px) scale(0.95); }
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/* Page load animation */
|
|
571
|
-
@keyframes fadeSlideIn {
|
|
572
|
-
from {
|
|
573
|
-
opacity: 0;
|
|
574
|
-
transform: translateY(8px);
|
|
575
|
-
}
|
|
576
|
-
to {
|
|
577
|
-
opacity: 1;
|
|
578
|
-
transform: translateY(0);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
body > * {
|
|
583
|
-
animation: fadeSlideIn 0.3s ease-out backwards;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
header {
|
|
587
|
-
padding: 1rem 1.5rem;
|
|
588
|
-
display: flex;
|
|
589
|
-
align-items: center;
|
|
590
|
-
gap: 1rem;
|
|
591
|
-
border-bottom: 1px solid rgba(99, 102, 241, 0.1);
|
|
592
|
-
background: linear-gradient(135deg, rgba(255, 255, 255, 0.85) 0%, rgba(238, 242, 255, 0.75) 50%, rgba(250, 245, 255, 0.85) 100%);
|
|
593
|
-
backdrop-filter: blur(20px);
|
|
594
|
-
flex-shrink: 0;
|
|
595
|
-
position: relative;
|
|
596
|
-
z-index: 1;
|
|
597
|
-
box-shadow: 0 4px 30px rgba(99, 102, 241, 0.1);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
header h1 {
|
|
601
|
-
font-size: 1.1rem;
|
|
602
|
-
margin: 0;
|
|
603
|
-
font-weight: 600;
|
|
604
|
-
color: var(--text-primary);
|
|
605
|
-
letter-spacing: -0.01em;
|
|
606
|
-
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #ec4899 100%);
|
|
607
|
-
-webkit-background-clip: text;
|
|
608
|
-
-webkit-text-fill-color: transparent;
|
|
609
|
-
background-clip: text;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
main {
|
|
613
|
-
flex: 1;
|
|
614
|
-
display: grid;
|
|
615
|
-
grid-template-columns: var(--sidebar-width, 380px) 4px 1fr;
|
|
616
|
-
background: transparent;
|
|
617
|
-
min-height: 0;
|
|
618
|
-
overflow: hidden;
|
|
619
|
-
position: relative;
|
|
620
|
-
z-index: 1;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
aside {
|
|
624
|
-
background: linear-gradient(180deg, rgba(238, 242, 255, 0.5) 0%, rgba(250, 245, 255, 0.4) 50%, rgba(236, 253, 245, 0.5) 100%);
|
|
625
|
-
backdrop-filter: blur(15px);
|
|
626
|
-
overflow-y: auto;
|
|
627
|
-
overflow-x: hidden;
|
|
628
|
-
min-width: 200px;
|
|
629
|
-
max-width: 800px;
|
|
630
|
-
height: 100%;
|
|
631
|
-
border-right: 1px solid rgba(99, 102, 241, 0.15);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
aside::-webkit-scrollbar {
|
|
635
|
-
width: 6px;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
aside::-webkit-scrollbar-track {
|
|
639
|
-
background: transparent;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
aside::-webkit-scrollbar-thumb {
|
|
643
|
-
background: var(--border-color);
|
|
644
|
-
border-radius: 3px;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
aside::-webkit-scrollbar-thumb:hover {
|
|
648
|
-
background: var(--text-muted);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
.resizer {
|
|
652
|
-
background: var(--border-color);
|
|
653
|
-
cursor: col-resize;
|
|
654
|
-
position: relative;
|
|
655
|
-
user-select: none;
|
|
656
|
-
transition: all 0.2s ease;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
.resizer:hover,
|
|
660
|
-
.resizer.active {
|
|
661
|
-
background: var(--accent-indigo);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
.resizer::after {
|
|
665
|
-
content: '';
|
|
666
|
-
position: absolute;
|
|
667
|
-
left: 50%;
|
|
668
|
-
top: 50%;
|
|
669
|
-
transform: translate(-50%, -50%);
|
|
670
|
-
width: 3px;
|
|
671
|
-
height: 32px;
|
|
672
|
-
background: var(--text-muted);
|
|
673
|
-
border-radius: 2px;
|
|
674
|
-
opacity: 0;
|
|
675
|
-
transition: opacity 0.2s ease;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
.resizer:hover::after,
|
|
679
|
-
.resizer.active::after {
|
|
680
|
-
opacity: 1;
|
|
681
|
-
background: white;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
.global-controls {
|
|
685
|
-
padding: 0.75rem 1rem;
|
|
686
|
-
border-bottom: 1px solid var(--border-color);
|
|
687
|
-
display: flex;
|
|
688
|
-
gap: 0.5rem;
|
|
689
|
-
background: var(--bg-secondary);
|
|
690
|
-
position: sticky;
|
|
691
|
-
top: 0;
|
|
692
|
-
z-index: 10;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
.global-controls .secondary {
|
|
696
|
-
flex: 1;
|
|
697
|
-
padding: 0.45rem 0.65rem;
|
|
698
|
-
font-size: 0.7rem;
|
|
699
|
-
display: flex;
|
|
700
|
-
align-items: center;
|
|
701
|
-
justify-content: center;
|
|
702
|
-
gap: 0.3rem;
|
|
703
|
-
font-weight: 500;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
.global-controls .secondary:hover {
|
|
707
|
-
background: var(--accent-indigo-light);
|
|
708
|
-
border-color: var(--accent-indigo);
|
|
709
|
-
color: var(--accent-indigo);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/* Tree view styles */
|
|
713
|
-
.tree-node {
|
|
714
|
-
user-select: none;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.tree-node-content {
|
|
718
|
-
display: flex;
|
|
719
|
-
align-items: center;
|
|
720
|
-
padding: 0.4rem 0.6rem;
|
|
721
|
-
cursor: pointer;
|
|
722
|
-
transition: all 0.2s ease;
|
|
723
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
724
|
-
position: relative;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
.tree-node-content::before {
|
|
728
|
-
content: '';
|
|
729
|
-
position: absolute;
|
|
730
|
-
inset: 0;
|
|
731
|
-
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(236, 72, 153, 0.1) 100%);
|
|
732
|
-
opacity: 0;
|
|
733
|
-
transition: opacity 0.2s ease;
|
|
734
|
-
border-radius: var(--radius-sm);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.tree-node-content:hover::before {
|
|
738
|
-
opacity: 1;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
.tree-node-content > * {
|
|
742
|
-
position: relative;
|
|
743
|
-
z-index: 1;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
.tree-node-content.selected {
|
|
747
|
-
background: linear-gradient(135deg, rgba(139, 92, 246, 0.2) 0%, rgba(236, 72, 153, 0.15) 100%);
|
|
748
|
-
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.25);
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
.tree-expand-icon {
|
|
752
|
-
width: 18px;
|
|
753
|
-
height: 18px;
|
|
754
|
-
display: flex;
|
|
755
|
-
align-items: center;
|
|
756
|
-
justify-content: center;
|
|
757
|
-
margin-right: 0.25rem;
|
|
758
|
-
transition: transform 0.2s ease;
|
|
759
|
-
cursor: pointer;
|
|
760
|
-
color: var(--text-muted);
|
|
761
|
-
font-size: 0.65rem;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
.tree-expand-icon.expanded {
|
|
765
|
-
transform: rotate(90deg);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
.tree-expand-icon.hidden {
|
|
769
|
-
visibility: hidden;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
.tree-node-checkbox {
|
|
773
|
-
appearance: none;
|
|
774
|
-
-webkit-appearance: none;
|
|
775
|
-
width: 16px;
|
|
776
|
-
height: 16px;
|
|
777
|
-
cursor: pointer;
|
|
778
|
-
margin-right: 0.5rem;
|
|
779
|
-
border: 2px solid var(--border-color);
|
|
780
|
-
border-radius: 4px;
|
|
781
|
-
background: var(--bg-primary);
|
|
782
|
-
position: relative;
|
|
783
|
-
transition: all 0.15s ease;
|
|
784
|
-
flex-shrink: 0;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
.tree-node-checkbox:hover {
|
|
788
|
-
border-color: var(--accent-emerald);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
.tree-node-checkbox:checked {
|
|
792
|
-
background: var(--bg-primary);
|
|
793
|
-
border-color: var(--accent-emerald);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
.tree-node-checkbox:checked::after {
|
|
797
|
-
content: '';
|
|
798
|
-
position: absolute;
|
|
799
|
-
top: 50%;
|
|
800
|
-
left: 50%;
|
|
801
|
-
width: 3px;
|
|
802
|
-
height: 6px;
|
|
803
|
-
border: solid var(--accent-emerald);
|
|
804
|
-
border-width: 0 2px 2px 0;
|
|
805
|
-
transform: translate(-50%, -60%) rotate(45deg);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
.tree-node-checkbox:indeterminate {
|
|
809
|
-
background: var(--bg-primary);
|
|
810
|
-
border-color: var(--accent-emerald);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
.tree-node-checkbox:indeterminate::after {
|
|
814
|
-
content: '';
|
|
815
|
-
position: absolute;
|
|
816
|
-
top: 50%;
|
|
817
|
-
left: 50%;
|
|
818
|
-
width: 8px;
|
|
819
|
-
height: 2px;
|
|
820
|
-
background: var(--accent-emerald);
|
|
821
|
-
transform: translate(-50%, -50%);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
.tree-node-label {
|
|
825
|
-
flex: 1;
|
|
826
|
-
font-size: 0.82rem;
|
|
827
|
-
white-space: nowrap;
|
|
828
|
-
overflow: hidden;
|
|
829
|
-
text-overflow: ellipsis;
|
|
830
|
-
min-width: 0;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
.tree-node-label.folder {
|
|
834
|
-
font-weight: 600;
|
|
835
|
-
color: var(--text-primary);
|
|
836
|
-
display: flex;
|
|
837
|
-
align-items: center;
|
|
838
|
-
gap: 0.25rem;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
.tree-node-label.folder .tree-node-count {
|
|
842
|
-
flex-shrink: 0;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
.tree-node-label.file {
|
|
846
|
-
color: var(--text-secondary);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
.tree-node-method {
|
|
850
|
-
font-size: 0.65rem;
|
|
851
|
-
padding: 0.15rem 0.45rem;
|
|
852
|
-
border-radius: var(--radius-sm);
|
|
853
|
-
margin-right: 0.4rem;
|
|
854
|
-
font-weight: 600;
|
|
855
|
-
text-transform: uppercase;
|
|
856
|
-
letter-spacing: 0.03em;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
.tree-node-method.get {
|
|
860
|
-
background: linear-gradient(135deg, var(--accent-emerald-light) 0%, rgba(16, 185, 129, 0.15) 100%);
|
|
861
|
-
color: #047857;
|
|
862
|
-
box-shadow: 0 2px 6px rgba(16, 185, 129, 0.15);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
.tree-node-method.post {
|
|
866
|
-
background: linear-gradient(135deg, var(--accent-amber-light) 0%, rgba(245, 158, 11, 0.15) 100%);
|
|
867
|
-
color: #b45309;
|
|
868
|
-
box-shadow: 0 2px 6px rgba(245, 158, 11, 0.15);
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
.tree-node-method.put {
|
|
872
|
-
background: linear-gradient(135deg, var(--accent-indigo-light) 0%, rgba(99, 102, 241, 0.15) 100%);
|
|
873
|
-
color: #4338ca;
|
|
874
|
-
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.15);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
.tree-node-method.delete {
|
|
878
|
-
background: linear-gradient(135deg, var(--accent-rose-light) 0%, rgba(239, 68, 68, 0.15) 100%);
|
|
879
|
-
color: #b91c1c;
|
|
880
|
-
box-shadow: 0 2px 6px rgba(239, 68, 68, 0.15);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
.tree-node-method.patch {
|
|
884
|
-
background: linear-gradient(135deg, #ede9fe 0%, rgba(139, 92, 246, 0.15) 100%);
|
|
885
|
-
color: #7c3aed;
|
|
886
|
-
box-shadow: 0 2px 6px rgba(139, 92, 246, 0.15);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
.tree-children {
|
|
890
|
-
padding-left: 1rem;
|
|
891
|
-
display: none;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
.tree-children.expanded {
|
|
895
|
-
display: block;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
.tree-node-count {
|
|
899
|
-
font-size: 0.7rem;
|
|
900
|
-
color: var(--text-muted);
|
|
901
|
-
margin-left: 0.4rem;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
.tree-node-delete {
|
|
905
|
-
display: none;
|
|
906
|
-
align-items: center;
|
|
907
|
-
justify-content: center;
|
|
908
|
-
width: 18px;
|
|
909
|
-
height: 18px;
|
|
910
|
-
margin-left: auto;
|
|
911
|
-
border-radius: var(--radius-sm);
|
|
912
|
-
cursor: pointer;
|
|
913
|
-
color: var(--accent-rose);
|
|
914
|
-
font-size: 0.85rem;
|
|
915
|
-
transition: all 0.15s ease;
|
|
916
|
-
user-select: none;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
.tree-node-delete:hover {
|
|
920
|
-
background: var(--accent-rose-light);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
.tree-node-content:hover .tree-node-delete {
|
|
924
|
-
display: flex;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
#mock-details {
|
|
928
|
-
height: calc(100% - 60px);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
section {
|
|
932
|
-
background: linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(238, 242, 255, 0.5) 50%, rgba(250, 245, 255, 0.6) 100%);
|
|
933
|
-
backdrop-filter: blur(15px);
|
|
934
|
-
padding: 1.5rem;
|
|
935
|
-
overflow-y: auto;
|
|
936
|
-
overflow-x: hidden;
|
|
937
|
-
display: flex;
|
|
938
|
-
flex-direction: column;
|
|
939
|
-
gap: 1rem;
|
|
940
|
-
height: 100%;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
section::-webkit-scrollbar {
|
|
944
|
-
width: 6px;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
section::-webkit-scrollbar-track {
|
|
948
|
-
background: transparent;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
section::-webkit-scrollbar-thumb {
|
|
952
|
-
background: var(--border-color);
|
|
953
|
-
border-radius: 3px;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
section::-webkit-scrollbar-thumb:hover {
|
|
957
|
-
background: var(--text-muted);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
section > h3 {
|
|
961
|
-
margin: 0;
|
|
962
|
-
flex-shrink: 0;
|
|
963
|
-
color: var(--text-primary);
|
|
964
|
-
font-size: 0.85rem;
|
|
965
|
-
font-weight: 600;
|
|
966
|
-
text-transform: uppercase;
|
|
967
|
-
letter-spacing: 0.05em;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
section .data-container {
|
|
971
|
-
flex: 1 1 auto;
|
|
972
|
-
min-height: 0;
|
|
973
|
-
display: flex;
|
|
974
|
-
flex-direction: column;
|
|
975
|
-
overflow: hidden;
|
|
976
|
-
height: 100%;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
.controls {
|
|
980
|
-
display: flex;
|
|
981
|
-
flex-wrap: wrap;
|
|
982
|
-
gap: 1rem;
|
|
983
|
-
align-items: flex-start;
|
|
984
|
-
flex-shrink: 0;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
.controls h2 {
|
|
988
|
-
width: 100%;
|
|
989
|
-
margin: 0 0 0.75rem 0;
|
|
990
|
-
font-size: 1rem;
|
|
991
|
-
display: flex;
|
|
992
|
-
align-items: center;
|
|
993
|
-
gap: 0.75rem;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
.controls label input[type="text"] {
|
|
997
|
-
padding: 0.4rem 0.6rem;
|
|
998
|
-
border-radius: var(--radius-sm);
|
|
999
|
-
border: 1px solid var(--border-color);
|
|
1000
|
-
background: var(--bg-primary);
|
|
1001
|
-
color: var(--text-primary);
|
|
1002
|
-
font-family: inherit;
|
|
1003
|
-
font-size: 0.8rem;
|
|
1004
|
-
outline: none;
|
|
1005
|
-
transition: all 0.15s ease;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
.controls label input[type="text"]:focus {
|
|
1009
|
-
border-color: var(--accent-indigo);
|
|
1010
|
-
box-shadow: 0 0 0 3px var(--accent-indigo-light);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
.badge {
|
|
1014
|
-
display: inline-flex;
|
|
1015
|
-
align-items: center;
|
|
1016
|
-
gap: 0.25rem;
|
|
1017
|
-
border-radius: var(--radius-sm);
|
|
1018
|
-
padding: 0.25rem 0.65rem;
|
|
1019
|
-
font-size: 0.65rem;
|
|
1020
|
-
font-weight: 600;
|
|
1021
|
-
background: linear-gradient(135deg, #818cf8 0%, #a78bfa 50%, #f472b6 100%);
|
|
1022
|
-
color: white;
|
|
1023
|
-
text-transform: uppercase;
|
|
1024
|
-
letter-spacing: 0.05em;
|
|
1025
|
-
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
textarea {
|
|
1029
|
-
width: 100%;
|
|
1030
|
-
flex: 1;
|
|
1031
|
-
min-height: 300px;
|
|
1032
|
-
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
|
|
1033
|
-
font-size: 0.82rem;
|
|
1034
|
-
padding: 1rem;
|
|
1035
|
-
border: 1px solid var(--border-color);
|
|
1036
|
-
border-radius: var(--radius-md);
|
|
1037
|
-
resize: none;
|
|
1038
|
-
background: var(--bg-secondary);
|
|
1039
|
-
color: var(--text-primary);
|
|
1040
|
-
overflow-y: auto;
|
|
1041
|
-
outline: none;
|
|
1042
|
-
transition: all 0.15s ease;
|
|
1043
|
-
line-height: 1.6;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
textarea:focus {
|
|
1047
|
-
border-color: var(--accent-indigo);
|
|
1048
|
-
box-shadow: 0 0 0 3px var(--accent-indigo-light);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
label {
|
|
1052
|
-
font-size: 0.75rem;
|
|
1053
|
-
color: var(--text-secondary);
|
|
1054
|
-
display: flex;
|
|
1055
|
-
gap: 0.5rem;
|
|
1056
|
-
align-items: center;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
input[type="number"] {
|
|
1060
|
-
width: 90px;
|
|
1061
|
-
padding: 0.35rem 0.5rem;
|
|
1062
|
-
border-radius: var(--radius-sm);
|
|
1063
|
-
border: 1px solid var(--border-color);
|
|
1064
|
-
background: var(--bg-primary);
|
|
1065
|
-
color: var(--text-primary);
|
|
1066
|
-
font-family: inherit;
|
|
1067
|
-
font-size: 0.8rem;
|
|
1068
|
-
outline: none;
|
|
1069
|
-
transition: all 0.15s ease;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
input[type="number"]:focus {
|
|
1073
|
-
border-color: var(--accent-indigo);
|
|
1074
|
-
box-shadow: 0 0 0 3px var(--accent-indigo-light);
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
.actions {
|
|
1078
|
-
display: flex;
|
|
1079
|
-
gap: 0.75rem;
|
|
1080
|
-
flex-shrink: 0;
|
|
1081
|
-
margin-top: 0.5rem;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
button.primary {
|
|
1085
|
-
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
|
|
1086
|
-
color: white;
|
|
1087
|
-
padding: 0.5rem 1rem;
|
|
1088
|
-
border-radius: var(--radius-md);
|
|
1089
|
-
border: none;
|
|
1090
|
-
cursor: pointer;
|
|
1091
|
-
font-weight: 500;
|
|
1092
|
-
font-family: inherit;
|
|
1093
|
-
font-size: 0.8rem;
|
|
1094
|
-
transition: all 0.2s ease;
|
|
1095
|
-
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4);
|
|
1096
|
-
display: inline-flex;
|
|
1097
|
-
align-items: center;
|
|
1098
|
-
gap: 0.4rem;
|
|
1099
|
-
position: relative;
|
|
1100
|
-
overflow: hidden;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
button.primary::before {
|
|
1104
|
-
content: '';
|
|
1105
|
-
position: absolute;
|
|
1106
|
-
inset: 0;
|
|
1107
|
-
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%);
|
|
1108
|
-
opacity: 0;
|
|
1109
|
-
transition: opacity 0.3s ease;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
button.primary:hover::before {
|
|
1113
|
-
opacity: 1;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
button.primary .btn-icon {
|
|
1117
|
-
color: white;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
button.primary:hover {
|
|
1121
|
-
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #9333ea 100%);
|
|
1122
|
-
box-shadow: 0 6px 25px rgba(139, 92, 246, 0.5);
|
|
1123
|
-
transform: translateY(-2px);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
button.secondary {
|
|
1127
|
-
background: var(--bg-primary);
|
|
1128
|
-
color: var(--text-secondary);
|
|
1129
|
-
padding: 0.5rem 1rem;
|
|
1130
|
-
border-radius: var(--radius-md);
|
|
1131
|
-
border: 1px solid var(--border-color);
|
|
1132
|
-
cursor: pointer;
|
|
1133
|
-
font-family: inherit;
|
|
1134
|
-
font-size: 0.8rem;
|
|
1135
|
-
transition: all 0.15s ease;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
button.secondary:hover {
|
|
1139
|
-
background: var(--bg-hover);
|
|
1140
|
-
color: var(--text-primary);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
/* Button icons */
|
|
1144
|
-
.btn-icon {
|
|
1145
|
-
display: inline-flex;
|
|
1146
|
-
align-items: center;
|
|
1147
|
-
justify-content: center;
|
|
1148
|
-
font-size: 1rem;
|
|
1149
|
-
font-weight: 300;
|
|
1150
|
-
line-height: 1;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
.btn-icon-check {
|
|
1154
|
-
display: inline-flex;
|
|
1155
|
-
align-items: center;
|
|
1156
|
-
justify-content: center;
|
|
1157
|
-
font-size: 0.85rem;
|
|
1158
|
-
font-weight: 600;
|
|
1159
|
-
line-height: 1;
|
|
1160
|
-
color: var(--accent-emerald);
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
.btn-icon-cross {
|
|
1164
|
-
display: inline-flex;
|
|
1165
|
-
align-items: center;
|
|
1166
|
-
justify-content: center;
|
|
1167
|
-
font-size: 0.85rem;
|
|
1168
|
-
font-weight: 600;
|
|
1169
|
-
line-height: 1;
|
|
1170
|
-
color: var(--accent-rose);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/* Detail panel checkbox */
|
|
1174
|
-
#toggle-enable {
|
|
1175
|
-
appearance: none;
|
|
1176
|
-
-webkit-appearance: none;
|
|
1177
|
-
width: 16px;
|
|
1178
|
-
height: 16px;
|
|
1179
|
-
cursor: pointer;
|
|
1180
|
-
border: 2px solid var(--border-color);
|
|
1181
|
-
border-radius: 4px;
|
|
1182
|
-
background: var(--bg-primary);
|
|
1183
|
-
position: relative;
|
|
1184
|
-
transition: all 0.15s ease;
|
|
1185
|
-
flex-shrink: 0;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
#toggle-enable:hover {
|
|
1189
|
-
border-color: var(--accent-emerald);
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
#toggle-enable:checked {
|
|
1193
|
-
background: var(--bg-primary);
|
|
1194
|
-
border-color: var(--accent-emerald);
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
#toggle-enable:checked::after {
|
|
1198
|
-
content: '';
|
|
1199
|
-
position: absolute;
|
|
1200
|
-
top: 50%;
|
|
1201
|
-
left: 50%;
|
|
1202
|
-
width: 3px;
|
|
1203
|
-
height: 6px;
|
|
1204
|
-
border: solid var(--accent-emerald);
|
|
1205
|
-
border-width: 0 2px 2px 0;
|
|
1206
|
-
transform: translate(-50%, -60%) rotate(45deg);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
.empty {
|
|
1210
|
-
display: flex;
|
|
1211
|
-
flex-direction: column;
|
|
1212
|
-
align-items: center;
|
|
1213
|
-
justify-content: center;
|
|
1214
|
-
color: var(--text-muted);
|
|
1215
|
-
gap: 0.5rem;
|
|
1216
|
-
height: 100%;
|
|
1217
|
-
font-size: 0.85rem;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
pre {
|
|
1221
|
-
background: var(--bg-secondary);
|
|
1222
|
-
padding: 1rem;
|
|
1223
|
-
border-radius: var(--radius-md);
|
|
1224
|
-
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
|
|
1225
|
-
overflow: auto;
|
|
1226
|
-
flex: 1;
|
|
1227
|
-
margin: 0;
|
|
1228
|
-
min-height: 300px;
|
|
1229
|
-
color: var(--text-primary);
|
|
1230
|
-
font-size: 0.82rem;
|
|
1231
|
-
line-height: 1.6;
|
|
1232
|
-
border: 1px solid var(--border-color);
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
textarea.error {
|
|
1236
|
-
border-color: var(--accent-rose);
|
|
1237
|
-
box-shadow: 0 0 0 3px var(--accent-rose-light);
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
/* Modal styles */
|
|
1241
|
-
.modal-overlay {
|
|
1242
|
-
position: fixed;
|
|
1243
|
-
top: 0;
|
|
1244
|
-
left: 0;
|
|
1245
|
-
right: 0;
|
|
1246
|
-
bottom: 0;
|
|
1247
|
-
background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.4) 100%);
|
|
1248
|
-
backdrop-filter: blur(8px);
|
|
1249
|
-
display: none;
|
|
1250
|
-
align-items: center;
|
|
1251
|
-
justify-content: center;
|
|
1252
|
-
z-index: 1000;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
.modal-overlay.show {
|
|
1256
|
-
display: flex;
|
|
1257
|
-
animation: modalFadeIn 0.2s ease-out;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
@keyframes modalFadeIn {
|
|
1261
|
-
from {
|
|
1262
|
-
opacity: 0;
|
|
1263
|
-
}
|
|
1264
|
-
to {
|
|
1265
|
-
opacity: 1;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
.modal {
|
|
1270
|
-
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(238, 242, 255, 0.9) 50%, rgba(250, 245, 255, 0.95) 100%);
|
|
1271
|
-
backdrop-filter: blur(25px);
|
|
1272
|
-
border: 1px solid rgba(255, 255, 255, 0.6);
|
|
1273
|
-
border-radius: var(--radius-lg);
|
|
1274
|
-
padding: 2rem;
|
|
1275
|
-
max-width: 500px;
|
|
1276
|
-
width: 90%;
|
|
1277
|
-
box-shadow: 0 25px 50px rgba(139, 92, 246, 0.25), 0 0 100px rgba(236, 72, 153, 0.15);
|
|
1278
|
-
animation: modalSlideUp 0.3s ease-out;
|
|
1279
|
-
position: relative;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
.modal::before {
|
|
1283
|
-
content: '';
|
|
1284
|
-
position: absolute;
|
|
1285
|
-
inset: -2px;
|
|
1286
|
-
background: linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(236, 72, 153, 0.3), rgba(59, 130, 246, 0.3));
|
|
1287
|
-
border-radius: calc(var(--radius-lg) + 2px);
|
|
1288
|
-
z-index: -1;
|
|
1289
|
-
opacity: 0.5;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
@keyframes modalSlideUp {
|
|
1293
|
-
from {
|
|
1294
|
-
opacity: 0;
|
|
1295
|
-
transform: translateY(16px) scale(0.98);
|
|
1296
|
-
}
|
|
1297
|
-
to {
|
|
1298
|
-
opacity: 1;
|
|
1299
|
-
transform: translateY(0) scale(1);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
.modal h2 {
|
|
1304
|
-
margin: 0 0 1.5rem 0;
|
|
1305
|
-
color: var(--text-primary);
|
|
1306
|
-
font-size: 1.1rem;
|
|
1307
|
-
font-weight: 600;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
.modal .form-group {
|
|
1311
|
-
margin-bottom: 1.25rem;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
.modal .form-group label {
|
|
1315
|
-
display: block;
|
|
1316
|
-
margin-bottom: 0.5rem;
|
|
1317
|
-
font-weight: 500;
|
|
1318
|
-
color: var(--text-primary);
|
|
1319
|
-
font-size: 0.8rem;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
.modal .form-group input,
|
|
1323
|
-
.modal .form-group select,
|
|
1324
|
-
.modal .form-group textarea {
|
|
1325
|
-
width: 100%;
|
|
1326
|
-
padding: 0.6rem;
|
|
1327
|
-
border: 1px solid var(--border-color);
|
|
1328
|
-
border-radius: var(--radius-sm);
|
|
1329
|
-
font-size: 0.85rem;
|
|
1330
|
-
font-family: inherit;
|
|
1331
|
-
background: var(--bg-primary);
|
|
1332
|
-
color: var(--text-primary);
|
|
1333
|
-
outline: none;
|
|
1334
|
-
transition: all 0.15s ease;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
.modal .form-group input:focus,
|
|
1338
|
-
.modal .form-group select:focus,
|
|
1339
|
-
.modal .form-group textarea:focus {
|
|
1340
|
-
border-color: var(--accent-indigo);
|
|
1341
|
-
box-shadow: 0 0 0 3px var(--accent-indigo-light);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
.modal .form-group textarea {
|
|
1345
|
-
min-height: 100px;
|
|
1346
|
-
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
|
|
1347
|
-
font-size: 0.8rem;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
.modal .form-group small {
|
|
1351
|
-
display: block;
|
|
1352
|
-
margin-top: 0.35rem;
|
|
1353
|
-
color: var(--text-muted);
|
|
1354
|
-
font-size: 0.7rem;
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
.modal .form-actions {
|
|
1358
|
-
display: flex;
|
|
1359
|
-
gap: 1rem;
|
|
1360
|
-
justify-content: flex-end;
|
|
1361
|
-
margin-top: 1.5rem;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
/* Animation delays for stagger effect */
|
|
1365
|
-
header { animation-delay: 0.05s; }
|
|
1366
|
-
main { animation-delay: 0.1s; }
|
|
1367
|
-
</style>
|
|
1368
|
-
</head>
|
|
1369
|
-
<body>
|
|
1370
|
-
<header>
|
|
1371
|
-
<h1>Mock Inspector</h1>
|
|
1372
|
-
<span class="badge">${apiPrefixEscaped}</span>
|
|
1373
|
-
<button id="new-api-btn" class="primary" style="margin-left: auto;"><span class="btn-icon">+</span> New API</button>
|
|
1374
|
-
</header>
|
|
1375
|
-
<main>
|
|
1376
|
-
<aside id="sidebar">
|
|
1377
|
-
<div class="global-controls">
|
|
1378
|
-
<button id="enable-all" class="secondary"><span class="btn-icon-check">\u2713</span> \u5F00\u542F\u6240\u6709</button>
|
|
1379
|
-
<button id="disable-all" class="secondary"><span class="btn-icon-cross">\u2717</span> \u5173\u95ED\u6240\u6709</button>
|
|
1380
|
-
</div>
|
|
1381
|
-
<ul id="mock-list"></ul>
|
|
1382
|
-
</aside>
|
|
1383
|
-
<div class="resizer" id="resizer"></div>
|
|
1384
|
-
<section>
|
|
1385
|
-
<div id="mock-details" class="empty">
|
|
1386
|
-
<p>Select a mock entry to inspect</p>
|
|
1387
|
-
</div>
|
|
1388
|
-
</section>
|
|
1389
|
-
</main>
|
|
1390
|
-
|
|
1391
|
-
<div id="new-api-modal" class="modal-overlay">
|
|
1392
|
-
<div class="modal">
|
|
1393
|
-
<h2><span class="btn-icon">+</span> New API Mock</h2>
|
|
1394
|
-
<form id="new-api-form">
|
|
1395
|
-
<div class="form-group">
|
|
1396
|
-
<label for="new-api-method">HTTP Method</label>
|
|
1397
|
-
<select id="new-api-method" required>
|
|
1398
|
-
<option value="get">GET</option>
|
|
1399
|
-
<option value="post">POST</option>
|
|
1400
|
-
<option value="put">PUT</option>
|
|
1401
|
-
<option value="delete">DELETE</option>
|
|
1402
|
-
<option value="patch">PATCH</option>
|
|
1403
|
-
</select>
|
|
1404
|
-
</div>
|
|
1405
|
-
<div class="form-group">
|
|
1406
|
-
<label for="new-api-path">API Path (without prefix)</label>
|
|
1407
|
-
<input type="text" id="new-api-path" placeholder="/users/list" required />
|
|
1408
|
-
<small style="color: rgba(15, 23, 42, 0.6); font-size: 0.85rem;">\u4F8B\u5982\uFF1A/users/list \u6216 /api/items</small>
|
|
1409
|
-
</div>
|
|
1410
|
-
<div class="form-group">
|
|
1411
|
-
<label for="new-api-description">Description (Optional)</label>
|
|
1412
|
-
<input type="text" id="new-api-description" placeholder="\u4F8B\u5982\uFF1A\u7528\u6237\u5217\u8868\u63A5\u53E3" />
|
|
1413
|
-
</div>
|
|
1414
|
-
<div class="form-group">
|
|
1415
|
-
<label for="new-api-data">Response Data (JSON)</label>
|
|
1416
|
-
<textarea id="new-api-data" placeholder='{ "code": 200, "data": [] }'>{ "code": 200, "data": [] }</textarea>
|
|
1417
|
-
</div>
|
|
1418
|
-
<div class="form-actions">
|
|
1419
|
-
<button type="button" id="cancel-new-api">Cancel</button>
|
|
1420
|
-
<button type="submit" class="primary">Create</button>
|
|
1421
|
-
</div>
|
|
1422
|
-
</form>
|
|
1423
|
-
</div>
|
|
1424
|
-
</div>
|
|
1425
|
-
|
|
1426
|
-
<script>
|
|
1427
|
-
const inspectorRoute = ${routeJson};
|
|
1428
|
-
const apiBase = (inspectorRoute.endsWith('/') ? inspectorRoute.slice(0, -1) : inspectorRoute) + '/api';
|
|
1429
|
-
const allowToggle = ${allowToggleJson};
|
|
1430
|
-
|
|
1431
|
-
function escapeHtml(value) {
|
|
1432
|
-
if (value == null) return '';
|
|
1433
|
-
return String(value)
|
|
1434
|
-
.replace(/&/g, '&')
|
|
1435
|
-
.replace(/</g, '<')
|
|
1436
|
-
.replace(/>/g, '>')
|
|
1437
|
-
.replace(/"/g, '"')
|
|
1438
|
-
.replace(/'/g, ''');
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// Tree data structure conversion
|
|
1442
|
-
function buildMockTree(mocks) {
|
|
1443
|
-
const root = { id: 'root', name: 'root', type: 'folder', children: [], checked: false, indeterminate: false };
|
|
1444
|
-
|
|
1445
|
-
mocks.forEach(mock => {
|
|
1446
|
-
// Parse the file path to build tree structure
|
|
1447
|
-
// Example: "automock/api/v1/asset-groups/prod-db-redis/put.js"
|
|
1448
|
-
const parts = mock.file.split('/').filter(p => p);
|
|
1449
|
-
let currentNode = root;
|
|
1450
|
-
|
|
1451
|
-
parts.forEach((part, index) => {
|
|
1452
|
-
const isFile = part.endsWith('.js') || part.endsWith('.ts');
|
|
1453
|
-
const nodeName = isFile ? part.replace(/.(js|ts)$/, '') : part;
|
|
1454
|
-
const nodeId = parts.slice(0, index + 1).join('/');
|
|
1455
|
-
|
|
1456
|
-
let childNode = currentNode.children.find(child => child.name === nodeName);
|
|
1457
|
-
|
|
1458
|
-
if (!childNode) {
|
|
1459
|
-
childNode = {
|
|
1460
|
-
id: nodeId,
|
|
1461
|
-
name: nodeName,
|
|
1462
|
-
type: isFile ? 'file' : 'folder',
|
|
1463
|
-
level: index,
|
|
1464
|
-
children: [],
|
|
1465
|
-
checked: false,
|
|
1466
|
-
indeterminate: false
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
if (isFile) {
|
|
1470
|
-
childNode.mockInfo = mock;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
currentNode.children.push(childNode);
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
currentNode = childNode;
|
|
1477
|
-
});
|
|
1478
|
-
});
|
|
1479
|
-
|
|
1480
|
-
// Initialize checked state based on mock.config.enable
|
|
1481
|
-
function initCheckedState(node) {
|
|
1482
|
-
if (node.type === 'file' && node.mockInfo) {
|
|
1483
|
-
node.checked = node.mockInfo.config.enable || false;
|
|
1484
|
-
node.indeterminate = false;
|
|
1485
|
-
}
|
|
1486
|
-
if (node.children && node.children.length > 0) {
|
|
1487
|
-
node.children.forEach(initCheckedState);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
initCheckedState(root);
|
|
1492
|
-
|
|
1493
|
-
// Calculate parent states
|
|
1494
|
-
function updateParentStates(node) {
|
|
1495
|
-
if (node.children && node.children.length > 0) {
|
|
1496
|
-
node.children.forEach(updateParentStates);
|
|
1497
|
-
|
|
1498
|
-
const allChecked = node.children.every(child => child.checked && !child.indeterminate);
|
|
1499
|
-
const someChecked = node.children.some(child => child.checked || child.indeterminate);
|
|
1500
|
-
|
|
1501
|
-
node.checked = allChecked;
|
|
1502
|
-
node.indeterminate = !allChecked && someChecked;
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
updateParentStates(root);
|
|
1507
|
-
|
|
1508
|
-
return root.children;
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// Get all leaf node (file) keys under a node
|
|
1512
|
-
function getAllFileKeys(node) {
|
|
1513
|
-
if (node.type === 'file') {
|
|
1514
|
-
return [node.mockInfo?.key].filter(Boolean);
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
if (node.children && node.children.length > 0) {
|
|
1518
|
-
const keys = [];
|
|
1519
|
-
node.children.forEach(child => {
|
|
1520
|
-
keys.push(...getAllFileKeys(child));
|
|
1521
|
-
});
|
|
1522
|
-
return keys;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
return [];
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
// Update tree node state recursively
|
|
1529
|
-
function updateTreeNodeState(node, checked, updateChildren = true) {
|
|
1530
|
-
if (updateChildren && node.children && node.children.length > 0) {
|
|
1531
|
-
node.children.forEach(child => {
|
|
1532
|
-
updateTreeNodeState(child, checked, true);
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
node.checked = checked;
|
|
1537
|
-
node.indeterminate = false;
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
// Update parent states bottom-up
|
|
1541
|
-
function updateParentNodeState(node, tree) {
|
|
1542
|
-
// Find parent node
|
|
1543
|
-
function findParent(n, targetId, parent = null) {
|
|
1544
|
-
if (n.id === targetId) return parent;
|
|
1545
|
-
if (n.children) {
|
|
1546
|
-
for (const child of n.children) {
|
|
1547
|
-
const result = findParent(child, targetId, n);
|
|
1548
|
-
if (result) return result;
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
return null;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
const parent = findParent({ children: tree }, node.id);
|
|
1555
|
-
|
|
1556
|
-
if (parent) {
|
|
1557
|
-
const allChecked = parent.children.every(child => child.checked && !child.indeterminate);
|
|
1558
|
-
const someChecked = parent.children.some(child => child.checked || child.indeterminate);
|
|
1559
|
-
|
|
1560
|
-
parent.checked = allChecked;
|
|
1561
|
-
parent.indeterminate = !allChecked && someChecked;
|
|
1562
|
-
|
|
1563
|
-
updateParentNodeState(parent, tree);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
// Render tree node recursively
|
|
1568
|
-
function renderTreeNode(node, level = 0, expandedNodes = new Set()) {
|
|
1569
|
-
const hasChildren = node.children && node.children.length > 0;
|
|
1570
|
-
const isExpanded = expandedNodes.has(node.id);
|
|
1571
|
-
const paddingLeft = level * 1.2 + 0.5;
|
|
1572
|
-
|
|
1573
|
-
let html = '<div class="tree-node" data-node-id="' + escapeHtml(node.id) + '" data-node-type="' + node.type + '">';
|
|
1574
|
-
|
|
1575
|
-
// Node content
|
|
1576
|
-
html += '<div class="tree-node-content" style="padding-left: ' + paddingLeft + 'rem">';
|
|
1577
|
-
|
|
1578
|
-
// Expand/collapse icon
|
|
1579
|
-
if (hasChildren) {
|
|
1580
|
-
html += '<span class="tree-expand-icon' + (isExpanded ? ' expanded' : '') + '">\u25B6</span>';
|
|
1581
|
-
} else {
|
|
1582
|
-
html += '<span class="tree-expand-icon hidden"></span>';
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
// Checkbox
|
|
1586
|
-
const checkedAttr = node.checked ? 'checked' : '';
|
|
1587
|
-
const indeterminateAttr = node.indeterminate ? 'data-indeterminate="true"' : '';
|
|
1588
|
-
html += '<input type="checkbox" class="tree-node-checkbox" ' + checkedAttr + ' ' + indeterminateAttr + ' />';
|
|
1589
|
-
|
|
1590
|
-
// Node label
|
|
1591
|
-
if (node.type === 'file' && node.mockInfo) {
|
|
1592
|
-
const mock = node.mockInfo;
|
|
1593
|
-
const methodClass = mock.method?.toLowerCase() || 'get';
|
|
1594
|
-
html += '<span class="tree-node-method ' + methodClass + '">' + escapeHtml(mock.method?.toUpperCase() || 'GET') + '</span>';
|
|
1595
|
-
html += '<span class="tree-node-label file" title="' + escapeHtml(mock.path) + '">' + escapeHtml(mock.path) + '</span>';
|
|
1596
|
-
if (mock.description) {
|
|
1597
|
-
html += '<span class="tree-node-count" title="' + escapeHtml(mock.description) + '">' + escapeHtml(mock.description) + '</span>';
|
|
1598
|
-
}
|
|
1599
|
-
// Delete button (only for files)
|
|
1600
|
-
html += '<span class="tree-node-delete" data-mock-key="' + escapeHtml(mock.key) + '" data-is-folder="false" title="\u5220\u9664\u6B64 Mock">\u2715</span>';
|
|
1601
|
-
} else {
|
|
1602
|
-
const fileCount = getAllFileKeys(node);
|
|
1603
|
-
const fileKeysJson = JSON.stringify(fileCount);
|
|
1604
|
-
// Put count inside the label to avoid layout shift when delete button appears
|
|
1605
|
-
html += '<span class="tree-node-label folder">' + escapeHtml(node.name) + ' <span class="tree-node-count">(' + fileCount.length + ')</span></span>';
|
|
1606
|
-
// Delete button for folders
|
|
1607
|
-
html += '<span class="tree-node-delete" data-mock-keys="' + escapeHtml(fileKeysJson) + '" data-is-folder="true" data-folder-name="' + escapeHtml(node.name) + '" title="\u5220\u9664\u6B64\u6587\u4EF6\u5939\u53CA\u6240\u6709 Mock">\u2715</span>';
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
html += '</div>';
|
|
1611
|
-
|
|
1612
|
-
// Children container
|
|
1613
|
-
if (hasChildren) {
|
|
1614
|
-
html += '<div class="tree-children' + (isExpanded ? ' expanded' : '') + '">';
|
|
1615
|
-
node.children.forEach(child => {
|
|
1616
|
-
html += renderTreeNode(child, level + 1, expandedNodes);
|
|
1617
|
-
});
|
|
1618
|
-
html += '</div>';
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
html += '</div>';
|
|
1622
|
-
|
|
1623
|
-
return html;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// Find node by id in tree
|
|
1627
|
-
function findNodeById(nodes, id) {
|
|
1628
|
-
for (const node of nodes) {
|
|
1629
|
-
if (node.id === id) return node;
|
|
1630
|
-
if (node.children) {
|
|
1631
|
-
const found = findNodeById(node.children, id);
|
|
1632
|
-
if (found) return found;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
return null;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
// Toggle tree node expand/collapse
|
|
1639
|
-
function toggleTreeNodeExpand(nodeId) {
|
|
1640
|
-
const nodeEl = document.querySelector('.tree-node[data-node-id="' + nodeId + '"]');
|
|
1641
|
-
if (!nodeEl) return;
|
|
1642
|
-
|
|
1643
|
-
const childrenEl = nodeEl.querySelector('.tree-children');
|
|
1644
|
-
const iconEl = nodeEl.querySelector('.tree-expand-icon');
|
|
1645
|
-
|
|
1646
|
-
if (childrenEl && iconEl && !iconEl.classList.contains('hidden')) {
|
|
1647
|
-
const isExpanded = childrenEl.classList.contains('expanded');
|
|
1648
|
-
if (isExpanded) {
|
|
1649
|
-
childrenEl.classList.remove('expanded');
|
|
1650
|
-
iconEl.classList.remove('expanded');
|
|
1651
|
-
} else {
|
|
1652
|
-
childrenEl.classList.add('expanded');
|
|
1653
|
-
iconEl.classList.add('expanded');
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
function renderToggleSection(mock) {
|
|
1659
|
-
if (!allowToggle) {
|
|
1660
|
-
return '';
|
|
1661
|
-
}
|
|
1662
|
-
const checked = mock.config.enable ? 'checked' : '';
|
|
1663
|
-
return (
|
|
1664
|
-
'<label>' +
|
|
1665
|
-
'<input type="checkbox" id="toggle-enable" ' + checked + ' />' +
|
|
1666
|
-
'Enable' +
|
|
1667
|
-
'</label>'
|
|
1668
|
-
);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
function renderDataSection(mock) {
|
|
1672
|
-
if (mock.editable) {
|
|
1673
|
-
return '<div class="data-container"><textarea id="data-editor"></textarea><div class="actions"><button class="primary" id="save-btn">Save</button></div></div>';
|
|
1674
|
-
}
|
|
1675
|
-
return '<div class="data-container"><pre id="data-preview"></pre></div>';
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
async function fetchMocks() {
|
|
1679
|
-
try {
|
|
1680
|
-
const res = await fetch(apiBase + '/list');
|
|
1681
|
-
if (!res.ok) {
|
|
1682
|
-
throw new Error('HTTP ' + res.status + ': ' + res.statusText);
|
|
1683
|
-
}
|
|
1684
|
-
const data = await res.json();
|
|
1685
|
-
return data.mocks || [];
|
|
1686
|
-
} catch (error) {
|
|
1687
|
-
console.error('[Inspector] Failed to fetch mocks:', error);
|
|
1688
|
-
return [];
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
function renderMockList(mocks, preserveExpandedState = false) {
|
|
1693
|
-
const list = document.getElementById('mock-list');
|
|
1694
|
-
if (!list) {
|
|
1695
|
-
console.error('[Inspector] Element #mock-list not found!');
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
// Save current expanded state if requested
|
|
1700
|
-
let savedExpandedNodes = new Set();
|
|
1701
|
-
if (preserveExpandedState) {
|
|
1702
|
-
list.querySelectorAll('.tree-children.expanded').forEach(el => {
|
|
1703
|
-
const parentNode = el.closest('.tree-node');
|
|
1704
|
-
if (parentNode) {
|
|
1705
|
-
savedExpandedNodes.add(parentNode.dataset.nodeId);
|
|
1706
|
-
}
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
list.innerHTML = '';
|
|
1711
|
-
if (!mocks || mocks.length === 0) {
|
|
1712
|
-
console.warn('[Inspector] No mocks to display');
|
|
1713
|
-
list.innerHTML = '<div style="padding: 1rem; color: #999;">No mock files found</div>';
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// Build tree structure
|
|
1718
|
-
const tree = buildMockTree(mocks);
|
|
1719
|
-
|
|
1720
|
-
// Use saved expanded state or expand first level by default
|
|
1721
|
-
const expandedNodes = savedExpandedNodes.size > 0 ? savedExpandedNodes : new Set();
|
|
1722
|
-
if (expandedNodes.size === 0) {
|
|
1723
|
-
tree.forEach(node => {
|
|
1724
|
-
if (node.type === 'folder') {
|
|
1725
|
-
expandedNodes.add(node.id);
|
|
1726
|
-
}
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
let treeHtml = '';
|
|
1731
|
-
tree.forEach(node => {
|
|
1732
|
-
const nodeHtml = renderTreeNode(node, 0, expandedNodes);
|
|
1733
|
-
treeHtml += nodeHtml;
|
|
1734
|
-
});
|
|
1735
|
-
|
|
1736
|
-
list.innerHTML = treeHtml;
|
|
1737
|
-
|
|
1738
|
-
// Attach event listeners
|
|
1739
|
-
attachTreeEventListeners();
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
// Attach event listeners for tree interactions
|
|
1743
|
-
function attachTreeEventListeners() {
|
|
1744
|
-
const list = document.getElementById('mock-list');
|
|
1745
|
-
if (!list) return;
|
|
1746
|
-
|
|
1747
|
-
// Handle expand/collapse clicks
|
|
1748
|
-
list.querySelectorAll('.tree-expand-icon').forEach(icon => {
|
|
1749
|
-
icon.addEventListener('click', (e) => {
|
|
1750
|
-
e.stopPropagation();
|
|
1751
|
-
const nodeEl = icon.closest('.tree-node');
|
|
1752
|
-
if (nodeEl) {
|
|
1753
|
-
const nodeId = nodeEl.dataset.nodeId;
|
|
1754
|
-
toggleTreeNodeExpand(nodeId);
|
|
1755
|
-
}
|
|
1756
|
-
});
|
|
1757
|
-
});
|
|
1758
|
-
|
|
1759
|
-
// Handle checkbox clicks
|
|
1760
|
-
list.querySelectorAll('.tree-node-checkbox').forEach(checkbox => {
|
|
1761
|
-
checkbox.addEventListener('click', async (e) => {
|
|
1762
|
-
e.stopPropagation();
|
|
1763
|
-
const nodeEl = checkbox.closest('.tree-node');
|
|
1764
|
-
if (!nodeEl) return;
|
|
1765
|
-
|
|
1766
|
-
const nodeId = nodeEl.dataset.nodeId;
|
|
1767
|
-
const nodeType = nodeEl.dataset.nodeType;
|
|
1768
|
-
const checked = checkbox.checked;
|
|
1769
|
-
|
|
1770
|
-
// Get current tree data
|
|
1771
|
-
const tree = buildMockTree(currentMocks);
|
|
1772
|
-
|
|
1773
|
-
// Find and update node
|
|
1774
|
-
const node = findNodeById(tree, nodeId);
|
|
1775
|
-
if (node) {
|
|
1776
|
-
// Update children
|
|
1777
|
-
updateTreeNodeState(node, checked, true);
|
|
1778
|
-
// Update parents
|
|
1779
|
-
updateParentNodeState(node, tree);
|
|
1780
|
-
|
|
1781
|
-
// Apply changes to all affected file nodes
|
|
1782
|
-
const affectedKeys = getAllFileKeys(node);
|
|
1783
|
-
await Promise.all(affectedKeys.map(key => toggleMockEnable(key, checked)));
|
|
1784
|
-
|
|
1785
|
-
// Re-render tree with preserved expanded state
|
|
1786
|
-
renderMockList(currentMocks, true);
|
|
1787
|
-
}
|
|
1788
|
-
});
|
|
1789
|
-
});
|
|
1790
|
-
|
|
1791
|
-
// Handle node content clicks (for selection)
|
|
1792
|
-
list.querySelectorAll('.tree-node-content').forEach(content => {
|
|
1793
|
-
content.addEventListener('click', (e) => {
|
|
1794
|
-
// Don't select if clicking on checkbox or expand icon
|
|
1795
|
-
if (e.target.classList.contains('tree-node-checkbox') ||
|
|
1796
|
-
e.target.classList.contains('tree-expand-icon')) {
|
|
1797
|
-
return;
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
const nodeEl = content.closest('.tree-node');
|
|
1801
|
-
if (!nodeEl) return;
|
|
1802
|
-
|
|
1803
|
-
const nodeId = nodeEl.dataset.nodeId;
|
|
1804
|
-
const nodeType = nodeEl.dataset.nodeType;
|
|
1805
|
-
|
|
1806
|
-
// Remove previous selection
|
|
1807
|
-
document.querySelectorAll('.tree-node-content.selected').forEach(el => {
|
|
1808
|
-
el.classList.remove('selected');
|
|
1809
|
-
});
|
|
1810
|
-
|
|
1811
|
-
// Add selection to current node
|
|
1812
|
-
content.classList.add('selected');
|
|
1813
|
-
|
|
1814
|
-
// For file nodes, select the mock
|
|
1815
|
-
if (nodeType === 'file') {
|
|
1816
|
-
const tree = buildMockTree(currentMocks);
|
|
1817
|
-
const node = findNodeById(tree, nodeId);
|
|
1818
|
-
if (node && node.mockInfo) {
|
|
1819
|
-
selectMock(node.mockInfo.key, content);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
});
|
|
1823
|
-
});
|
|
1824
|
-
|
|
1825
|
-
// Set indeterminate state for checkboxes
|
|
1826
|
-
list.querySelectorAll('.tree-node-checkbox[data-indeterminate="true"]').forEach(cb => {
|
|
1827
|
-
cb.indeterminate = true;
|
|
1828
|
-
});
|
|
1829
|
-
|
|
1830
|
-
// Handle delete button clicks
|
|
1831
|
-
list.querySelectorAll('.tree-node-delete').forEach(deleteBtn => {
|
|
1832
|
-
deleteBtn.addEventListener('click', async (e) => {
|
|
1833
|
-
e.stopPropagation();
|
|
1834
|
-
|
|
1835
|
-
// Check if user has chosen "never ask again"
|
|
1836
|
-
const skipConfirm = localStorage.getItem('mockInspector_skipDeleteConfirm') === 'true';
|
|
1837
|
-
|
|
1838
|
-
const isFolder = deleteBtn.dataset.isFolder === 'true';
|
|
1839
|
-
let confirmMessage = '';
|
|
1840
|
-
let keysToDelete = [];
|
|
1841
|
-
|
|
1842
|
-
if (isFolder) {
|
|
1843
|
-
// Folder deletion
|
|
1844
|
-
const folderName = deleteBtn.dataset.folderName || '';
|
|
1845
|
-
const keysJson = deleteBtn.dataset.mockKeys || '[]';
|
|
1846
|
-
keysToDelete = JSON.parse(keysJson);
|
|
1847
|
-
confirmMessage = '\u786E\u5B9A\u8981\u5220\u9664\u6587\u4EF6\u5939 "' + folderName + '" \u53CA\u5176\u5305\u542B\u7684 ' + keysToDelete.length + ' \u4E2A Mock \u6587\u4EF6\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002';
|
|
1848
|
-
} else {
|
|
1849
|
-
// Single file deletion
|
|
1850
|
-
const mockKey = deleteBtn.dataset.mockKey;
|
|
1851
|
-
if (!mockKey) return;
|
|
1852
|
-
keysToDelete = [mockKey];
|
|
1853
|
-
confirmMessage = '\u786E\u5B9A\u8981\u5220\u9664\u8FD9\u4E2A Mock \u6570\u636E\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002';
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
// If user chose "never ask again", delete directly
|
|
1857
|
-
if (skipConfirm) {
|
|
1858
|
-
await performDelete(keysToDelete);
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
// Otherwise, show custom confirmation dialog
|
|
1863
|
-
showDeleteConfirmDialog(confirmMessage, keysToDelete);
|
|
1864
|
-
});
|
|
1865
|
-
});
|
|
1866
|
-
|
|
1867
|
-
// Custom delete confirmation dialog
|
|
1868
|
-
function showDeleteConfirmDialog(message, keysToDelete) {
|
|
1869
|
-
const modal = document.getElementById('delete-confirm-modal');
|
|
1870
|
-
const messageEl = document.getElementById('delete-confirm-message');
|
|
1871
|
-
const neverAskCheckbox = document.getElementById('delete-never-ask');
|
|
1872
|
-
const confirmBtn = document.getElementById('delete-confirm-btn');
|
|
1873
|
-
const cancelBtn = document.getElementById('delete-cancel-btn');
|
|
1874
|
-
|
|
1875
|
-
messageEl.textContent = message;
|
|
1876
|
-
neverAskCheckbox.checked = false;
|
|
1877
|
-
modal.classList.add('show');
|
|
1878
|
-
|
|
1879
|
-
// Remove old event listeners
|
|
1880
|
-
const newConfirmBtn = confirmBtn.cloneNode(true);
|
|
1881
|
-
const newCancelBtn = cancelBtn.cloneNode(true);
|
|
1882
|
-
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
|
|
1883
|
-
cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);
|
|
1884
|
-
|
|
1885
|
-
// Confirm button handler
|
|
1886
|
-
newConfirmBtn.addEventListener('click', async () => {
|
|
1887
|
-
// Save preference if "never ask again" is checked
|
|
1888
|
-
if (neverAskCheckbox.checked) {
|
|
1889
|
-
localStorage.setItem('mockInspector_skipDeleteConfirm', 'true');
|
|
1890
|
-
}
|
|
1891
|
-
modal.classList.remove('show');
|
|
1892
|
-
await performDelete(keysToDelete);
|
|
1893
|
-
});
|
|
1894
|
-
|
|
1895
|
-
// Cancel button handler
|
|
1896
|
-
newCancelBtn.addEventListener('click', () => {
|
|
1897
|
-
modal.classList.remove('show');
|
|
1898
|
-
});
|
|
1899
|
-
|
|
1900
|
-
// Close on overlay click
|
|
1901
|
-
modal.addEventListener('click', (e) => {
|
|
1902
|
-
if (e.target === modal) {
|
|
1903
|
-
modal.classList.remove('show');
|
|
1904
|
-
}
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// Perform the actual deletion
|
|
1909
|
-
async function performDelete(keysToDelete) {
|
|
1910
|
-
try {
|
|
1911
|
-
// Delete all keys (either single file or entire folder)
|
|
1912
|
-
const deletePromises = keysToDelete.map(key =>
|
|
1913
|
-
fetch(apiBase + '/delete?key=' + encodeURIComponent(key), {
|
|
1914
|
-
method: 'DELETE'
|
|
1915
|
-
})
|
|
1916
|
-
);
|
|
1917
|
-
|
|
1918
|
-
const results = await Promise.all(deletePromises);
|
|
1919
|
-
|
|
1920
|
-
// Check if any deletion failed
|
|
1921
|
-
const failedDeletions = results.filter(res => !res.ok);
|
|
1922
|
-
if (failedDeletions.length > 0) {
|
|
1923
|
-
const errors = await Promise.all(failedDeletions.map(res => res.json()));
|
|
1924
|
-
throw new Error(errors.map(e => e.error).join(', '));
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
// Refresh the list
|
|
1928
|
-
currentMocks = await fetchMocks();
|
|
1929
|
-
renderMockList(currentMocks, true);
|
|
1930
|
-
|
|
1931
|
-
// Clear details panel if any deleted mock was selected
|
|
1932
|
-
if (keysToDelete.includes(window.currentKey)) {
|
|
1933
|
-
window.currentKey = null;
|
|
1934
|
-
document.getElementById('mock-details').innerHTML = '<p>Select a mock entry to inspect</p>';
|
|
1935
|
-
document.getElementById('mock-details').className = 'empty';
|
|
1936
|
-
}
|
|
1937
|
-
} catch (error) {
|
|
1938
|
-
alert('\u5220\u9664\u5931\u8D25: ' + error.message);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
function renderDetails(mock) {
|
|
1944
|
-
const container = document.getElementById('mock-details');
|
|
1945
|
-
if (!mock) {
|
|
1946
|
-
container.className = 'empty';
|
|
1947
|
-
container.innerHTML = '<p>Select a mock entry to inspect</p>';
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
container.className = '';
|
|
1952
|
-
container.innerHTML = [
|
|
1953
|
-
'<div style="display: flex; flex-direction: column; height: 100%;">',
|
|
1954
|
-
' <div class="controls">',
|
|
1955
|
-
' <h2>',
|
|
1956
|
-
' <span class="badge">' + escapeHtml(mock.method.toUpperCase()) + '</span>',
|
|
1957
|
-
' ' + escapeHtml(mock.path),
|
|
1958
|
-
' </h2>',
|
|
1959
|
-
' <label style="flex: 1;" >',
|
|
1960
|
-
' Desc: ',
|
|
1961
|
-
' <input type="text" id="description-input" placeholder="\u4F8B\u5982\uFF1A\u7528\u6237\u5217\u8868\u63A5\u53E3" value="' + escapeHtml(mock.description || '') + '" style="width: 100%; margin-top: 0.25rem;" />',
|
|
1962
|
-
' </label>',
|
|
1963
|
-
' <label>Delay <input type="number" id="delay-input" value="' + mock.config.delay + '" min="0" step="50" /> ms</label>',
|
|
1964
|
-
' <label>Status <input type="number" id="status-input" value="' + mock.config.status + '" min="100" max="599" /></label>',
|
|
1965
|
-
' ' + renderToggleSection(mock),
|
|
1966
|
-
' </div>',
|
|
1967
|
-
' <h3>Response Data</h3>',
|
|
1968
|
-
' ' + renderDataSection(mock),
|
|
1969
|
-
'</div>'
|
|
1970
|
-
].join('\\n');
|
|
1971
|
-
|
|
1972
|
-
const descriptionInput = document.getElementById('description-input');
|
|
1973
|
-
if (descriptionInput) {
|
|
1974
|
-
descriptionInput.addEventListener('change', () => updateDescription(descriptionInput.value));
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
if (allowToggle) {
|
|
1978
|
-
const enableToggle = document.getElementById('toggle-enable');
|
|
1979
|
-
if (enableToggle) {
|
|
1980
|
-
enableToggle.addEventListener('change', () => updateConfig({ enable: enableToggle.checked }));
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
const delayInput = document.getElementById('delay-input');
|
|
1985
|
-
if (delayInput) {
|
|
1986
|
-
delayInput.addEventListener('change', () => updateConfig({ delay: Number(delayInput.value) || 0 }));
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
const statusInput = document.getElementById('status-input');
|
|
1990
|
-
if (statusInput) {
|
|
1991
|
-
statusInput.addEventListener('change', () => updateConfig({ status: Number(statusInput.value) || 200 }));
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
if (mock.editable) {
|
|
1995
|
-
const textarea = document.getElementById('data-editor');
|
|
1996
|
-
if (textarea) {
|
|
1997
|
-
textarea.value = mock.dataText || '';
|
|
1998
|
-
const saveBtn = document.getElementById('save-btn');
|
|
1999
|
-
if (saveBtn) {
|
|
2000
|
-
saveBtn.addEventListener('click', async () => {
|
|
2001
|
-
try {
|
|
2002
|
-
const raw = textarea.value || 'null';
|
|
2003
|
-
const parsed = JSON.parse(raw);
|
|
2004
|
-
await updateConfig({ data: parsed });
|
|
2005
|
-
textarea.classList.remove('error');
|
|
2006
|
-
} catch (err) {
|
|
2007
|
-
textarea.classList.add('error');
|
|
2008
|
-
alert('Invalid JSON: ' + err.message);
|
|
2009
|
-
}
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
} else {
|
|
2014
|
-
const pre = document.getElementById('data-preview');
|
|
2015
|
-
if (pre) {
|
|
2016
|
-
pre.textContent = mock.dataText || '';
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
let currentMocks = [];
|
|
2022
|
-
|
|
2023
|
-
async function updateConfig(partial) {
|
|
2024
|
-
if (!window.currentKey) return;
|
|
2025
|
-
if (!allowToggle && 'enable' in partial) {
|
|
2026
|
-
delete partial.enable;
|
|
2027
|
-
}
|
|
2028
|
-
const res = await fetch(apiBase + '/update', {
|
|
2029
|
-
method: 'POST',
|
|
2030
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2031
|
-
body: JSON.stringify({ key: window.currentKey, config: partial })
|
|
2032
|
-
});
|
|
2033
|
-
const data = await res.json();
|
|
2034
|
-
const updated = data.mock;
|
|
2035
|
-
const index = currentMocks.findIndex((item) => item.key === window.currentKey);
|
|
2036
|
-
if (index >= 0) {
|
|
2037
|
-
currentMocks[index] = updated;
|
|
2038
|
-
renderDetails(updated);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
async function updateDescription(description) {
|
|
2043
|
-
if (!window.currentKey) return;
|
|
2044
|
-
try {
|
|
2045
|
-
const res = await fetch(apiBase + '/update', {
|
|
2046
|
-
method: 'POST',
|
|
2047
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2048
|
-
body: JSON.stringify({ key: window.currentKey, description: description })
|
|
2049
|
-
});
|
|
2050
|
-
const data = await res.json();
|
|
2051
|
-
const updated = data.mock;
|
|
2052
|
-
const index = currentMocks.findIndex((item) => item.key === window.currentKey);
|
|
2053
|
-
if (index >= 0) {
|
|
2054
|
-
currentMocks[index] = updated;
|
|
2055
|
-
renderDetails(updated);
|
|
2056
|
-
// \u91CD\u65B0\u6E32\u67D3\u5217\u8868\u4EE5\u66F4\u65B0\u663E\u793A
|
|
2057
|
-
renderMockList(currentMocks, true);
|
|
2058
|
-
}
|
|
2059
|
-
} catch (error) {
|
|
2060
|
-
console.error('[Inspector] Failed to update description:', error);
|
|
2061
|
-
alert('\u66F4\u65B0\u63CF\u8FF0\u5931\u8D25: ' + error.message);
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
async function selectMock(key, button) {
|
|
2066
|
-
window.currentKey = key;
|
|
2067
|
-
document.querySelectorAll('aside button').forEach((btn) => btn.classList.remove('active'));
|
|
2068
|
-
button.classList.add('active');
|
|
2069
|
-
const res = await fetch(apiBase + '/detail?key=' + encodeURIComponent(key));
|
|
2070
|
-
const data = await res.json();
|
|
2071
|
-
const mock = data.mock;
|
|
2072
|
-
const index = currentMocks.findIndex((item) => item.key === key);
|
|
2073
|
-
if (index >= 0) {
|
|
2074
|
-
currentMocks[index] = mock;
|
|
2075
|
-
}
|
|
2076
|
-
renderDetails(mock);
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
async function toggleMockEnable(key, enable) {
|
|
2080
|
-
try {
|
|
2081
|
-
const res = await fetch(apiBase + '/update', {
|
|
2082
|
-
method: 'POST',
|
|
2083
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2084
|
-
body: JSON.stringify({ key: key, config: { enable: enable } })
|
|
2085
|
-
});
|
|
2086
|
-
const data = await res.json();
|
|
2087
|
-
const updated = data.mock;
|
|
2088
|
-
const index = currentMocks.findIndex((item) => item.key === key);
|
|
2089
|
-
if (index >= 0) {
|
|
2090
|
-
currentMocks[index] = updated;
|
|
2091
|
-
// \u5982\u679C\u5F53\u524D\u6B63\u5728\u67E5\u770B\u8FD9\u4E2A mock\uFF0C\u66F4\u65B0\u8BE6\u60C5
|
|
2092
|
-
if (window.currentKey === key) {
|
|
2093
|
-
renderDetails(updated);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
} catch (error) {
|
|
2097
|
-
console.error('[Inspector] Failed to toggle mock:', error);
|
|
2098
|
-
alert('\u66F4\u65B0\u5931\u8D25: ' + error.message);
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
function startEditDescription(li, mock) {
|
|
2103
|
-
// \u6E05\u7A7A li \u5185\u5BB9\uFF0C\u521B\u5EFA\u7F16\u8F91\u6846
|
|
2104
|
-
li.innerHTML = '';
|
|
2105
|
-
|
|
2106
|
-
const input = document.createElement('input');
|
|
2107
|
-
input.type = 'text';
|
|
2108
|
-
input.className = 'description-edit';
|
|
2109
|
-
input.value = mock.description || '';
|
|
2110
|
-
input.placeholder = '\u8F93\u5165\u4E1A\u52A1\u63CF\u8FF0\uFF0C\u4F8B\u5982\uFF1A\u7528\u6237\u5217\u8868\u63A5\u53E3';
|
|
2111
|
-
|
|
2112
|
-
const saveEdit = async () => {
|
|
2113
|
-
const newDescription = input.value.trim();
|
|
2114
|
-
try {
|
|
2115
|
-
const res = await fetch(apiBase + '/update', {
|
|
2116
|
-
method: 'POST',
|
|
2117
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2118
|
-
body: JSON.stringify({ key: mock.key, description: newDescription })
|
|
2119
|
-
});
|
|
2120
|
-
const data = await res.json();
|
|
2121
|
-
const updated = data.mock;
|
|
2122
|
-
const index = currentMocks.findIndex((item) => item.key === mock.key);
|
|
2123
|
-
if (index >= 0) {
|
|
2124
|
-
currentMocks[index] = updated;
|
|
2125
|
-
}
|
|
2126
|
-
// \u91CD\u65B0\u6E32\u67D3\u5217\u8868
|
|
2127
|
-
renderMockList(currentMocks, true);
|
|
2128
|
-
// \u5982\u679C\u5F53\u524D\u6B63\u5728\u67E5\u770B\u8FD9\u4E2A mock\uFF0C\u66F4\u65B0\u8BE6\u60C5
|
|
2129
|
-
if (window.currentKey === mock.key) {
|
|
2130
|
-
renderDetails(updated);
|
|
2131
|
-
}
|
|
2132
|
-
} catch (error) {
|
|
2133
|
-
console.error('[Inspector] Failed to update description:', error);
|
|
2134
|
-
alert('\u66F4\u65B0\u5931\u8D25: ' + error.message);
|
|
2135
|
-
renderMockList(currentMocks, true);
|
|
2136
|
-
}
|
|
2137
|
-
};
|
|
2138
|
-
|
|
2139
|
-
input.addEventListener('blur', saveEdit);
|
|
2140
|
-
input.addEventListener('keydown', (e) => {
|
|
2141
|
-
if (e.key === 'Enter') {
|
|
2142
|
-
saveEdit();
|
|
2143
|
-
} else if (e.key === 'Escape') {
|
|
2144
|
-
renderMockList(currentMocks, true);
|
|
2145
|
-
}
|
|
2146
|
-
});
|
|
2147
|
-
|
|
2148
|
-
li.appendChild(input);
|
|
2149
|
-
input.focus();
|
|
2150
|
-
input.select();
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
async function enableAllMocks() {
|
|
2154
|
-
const promises = currentMocks.map(mock =>
|
|
2155
|
-
toggleMockEnable(mock.key, true)
|
|
2156
|
-
);
|
|
2157
|
-
await Promise.all(promises);
|
|
2158
|
-
// \u91CD\u65B0\u83B7\u53D6\u5217\u8868\u4EE5\u66F4\u65B0 UI
|
|
2159
|
-
currentMocks = await fetchMocks();
|
|
2160
|
-
renderMockList(currentMocks, true);
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
async function disableAllMocks() {
|
|
2164
|
-
const promises = currentMocks.map(mock =>
|
|
2165
|
-
toggleMockEnable(mock.key, false)
|
|
2166
|
-
);
|
|
2167
|
-
await Promise.all(promises);
|
|
2168
|
-
// \u91CD\u65B0\u83B7\u53D6\u5217\u8868\u4EE5\u66F4\u65B0 UI
|
|
2169
|
-
currentMocks = await fetchMocks();
|
|
2170
|
-
renderMockList(currentMocks, true);
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
// Initialize sidebar resizer functionality
|
|
2174
|
-
function initSidebarResizer() {
|
|
2175
|
-
const sidebar = document.getElementById('sidebar');
|
|
2176
|
-
const resizer = document.getElementById('resizer');
|
|
2177
|
-
const main = document.querySelector('main');
|
|
2178
|
-
|
|
2179
|
-
if (!sidebar || !resizer || !main) {
|
|
2180
|
-
console.warn('[Inspector] Sidebar resizer elements not found');
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
// Load saved width from localStorage
|
|
2185
|
-
const savedWidth = localStorage.getItem('mockInspectorSidebarWidth');
|
|
2186
|
-
if (savedWidth) {
|
|
2187
|
-
const width = Math.max(200, Math.min(800, parseInt(savedWidth, 10)));
|
|
2188
|
-
main.style.setProperty('--sidebar-width', width + 'px');
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
let isResizing = false;
|
|
2192
|
-
let startX = 0;
|
|
2193
|
-
let startWidth = 0;
|
|
2194
|
-
|
|
2195
|
-
resizer.addEventListener('mousedown', (e) => {
|
|
2196
|
-
isResizing = true;
|
|
2197
|
-
startX = e.clientX;
|
|
2198
|
-
startWidth = sidebar.offsetWidth;
|
|
2199
|
-
resizer.classList.add('active');
|
|
2200
|
-
document.body.style.cursor = 'col-resize';
|
|
2201
|
-
document.body.style.userSelect = 'none';
|
|
2202
|
-
e.preventDefault();
|
|
2203
|
-
});
|
|
2204
|
-
|
|
2205
|
-
document.addEventListener('mousemove', (e) => {
|
|
2206
|
-
if (!isResizing) return;
|
|
2207
|
-
|
|
2208
|
-
const deltaX = e.clientX - startX;
|
|
2209
|
-
const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));
|
|
2210
|
-
main.style.setProperty('--sidebar-width', newWidth + 'px');
|
|
2211
|
-
});
|
|
2212
|
-
|
|
2213
|
-
document.addEventListener('mouseup', () => {
|
|
2214
|
-
if (isResizing) {
|
|
2215
|
-
isResizing = false;
|
|
2216
|
-
resizer.classList.remove('active');
|
|
2217
|
-
document.body.style.cursor = '';
|
|
2218
|
-
document.body.style.userSelect = '';
|
|
2219
|
-
|
|
2220
|
-
// Save width to localStorage
|
|
2221
|
-
const currentWidth = sidebar.offsetWidth;
|
|
2222
|
-
localStorage.setItem('mockInspectorSidebarWidth', currentWidth.toString());
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
|
|
2226
|
-
// Touch support for mobile devices
|
|
2227
|
-
resizer.addEventListener('touchstart', (e) => {
|
|
2228
|
-
const touch = e.touches[0];
|
|
2229
|
-
isResizing = true;
|
|
2230
|
-
startX = touch.clientX;
|
|
2231
|
-
startWidth = sidebar.offsetWidth;
|
|
2232
|
-
resizer.classList.add('active');
|
|
2233
|
-
e.preventDefault();
|
|
2234
|
-
}, { passive: false });
|
|
2235
|
-
|
|
2236
|
-
document.addEventListener('touchmove', (e) => {
|
|
2237
|
-
if (!isResizing) return;
|
|
2238
|
-
|
|
2239
|
-
const touch = e.touches[0];
|
|
2240
|
-
const deltaX = touch.clientX - startX;
|
|
2241
|
-
const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));
|
|
2242
|
-
main.style.setProperty('--sidebar-width', newWidth + 'px');
|
|
2243
|
-
}, { passive: false });
|
|
2244
|
-
|
|
2245
|
-
document.addEventListener('touchend', () => {
|
|
2246
|
-
if (isResizing) {
|
|
2247
|
-
isResizing = false;
|
|
2248
|
-
resizer.classList.remove('active');
|
|
2249
|
-
|
|
2250
|
-
// Save width to localStorage
|
|
2251
|
-
const currentWidth = sidebar.offsetWidth;
|
|
2252
|
-
localStorage.setItem('mockInspectorSidebarWidth', currentWidth.toString());
|
|
2253
|
-
}
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
async function bootstrap() {
|
|
2258
|
-
// Initialize sidebar resizer
|
|
2259
|
-
initSidebarResizer();
|
|
2260
|
-
|
|
2261
|
-
try {
|
|
2262
|
-
currentMocks = await fetchMocks();
|
|
2263
|
-
renderMockList(currentMocks, true);
|
|
2264
|
-
|
|
2265
|
-
// \u7ED1\u5B9A\u5168\u5C40\u63A7\u5236\u6309\u94AE
|
|
2266
|
-
const enableAllBtn = document.getElementById('enable-all');
|
|
2267
|
-
const disableAllBtn = document.getElementById('disable-all');
|
|
2268
|
-
if (enableAllBtn) {
|
|
2269
|
-
enableAllBtn.addEventListener('click', async () => {
|
|
2270
|
-
enableAllBtn.disabled = true;
|
|
2271
|
-
enableAllBtn.textContent = '\u5904\u7406\u4E2D...';
|
|
2272
|
-
await enableAllMocks();
|
|
2273
|
-
enableAllBtn.disabled = false;
|
|
2274
|
-
enableAllBtn.textContent = '\u2713 \u5F00\u542F\u6240\u6709';
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
if (disableAllBtn) {
|
|
2278
|
-
disableAllBtn.addEventListener('click', async () => {
|
|
2279
|
-
disableAllBtn.disabled = true;
|
|
2280
|
-
disableAllBtn.textContent = '\u5904\u7406\u4E2D...';
|
|
2281
|
-
await disableAllMocks();
|
|
2282
|
-
disableAllBtn.disabled = false;
|
|
2283
|
-
disableAllBtn.textContent = '\u2717 \u5173\u95ED\u6240\u6709';
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
// \u7ED1\u5B9A\u65B0\u5EFAAPI\u6309\u94AE
|
|
2288
|
-
const newApiBtn = document.getElementById('new-api-btn');
|
|
2289
|
-
const newApiModal = document.getElementById('new-api-modal');
|
|
2290
|
-
const newApiForm = document.getElementById('new-api-form');
|
|
2291
|
-
const cancelNewApi = document.getElementById('cancel-new-api');
|
|
2292
|
-
|
|
2293
|
-
if (newApiBtn && newApiModal) {
|
|
2294
|
-
newApiBtn.addEventListener('click', () => {
|
|
2295
|
-
newApiModal.classList.add('show');
|
|
2296
|
-
document.getElementById('new-api-path').focus();
|
|
2297
|
-
});
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
if (cancelNewApi && newApiModal) {
|
|
2301
|
-
cancelNewApi.addEventListener('click', () => {
|
|
2302
|
-
newApiModal.classList.remove('show');
|
|
2303
|
-
newApiForm.reset();
|
|
2304
|
-
});
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// \u70B9\u51FB\u80CC\u666F\u5173\u95ED\u6A21\u6001\u6846
|
|
2308
|
-
if (newApiModal) {
|
|
2309
|
-
newApiModal.addEventListener('click', (e) => {
|
|
2310
|
-
if (e.target === newApiModal) {
|
|
2311
|
-
newApiModal.classList.remove('show');
|
|
2312
|
-
newApiForm.reset();
|
|
2313
|
-
}
|
|
2314
|
-
});
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
// \u5904\u7406\u8868\u5355\u63D0\u4EA4
|
|
2318
|
-
if (newApiForm) {
|
|
2319
|
-
newApiForm.addEventListener('submit', async (e) => {
|
|
2320
|
-
e.preventDefault();
|
|
2321
|
-
|
|
2322
|
-
const method = document.getElementById('new-api-method').value;
|
|
2323
|
-
const path = document.getElementById('new-api-path').value.trim();
|
|
2324
|
-
const description = document.getElementById('new-api-description').value.trim();
|
|
2325
|
-
const dataText = document.getElementById('new-api-data').value.trim();
|
|
2326
|
-
|
|
2327
|
-
if (!path) {
|
|
2328
|
-
alert('\u8BF7\u8F93\u5165 API \u8DEF\u5F84');
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
// \u9A8C\u8BC1JSON
|
|
2333
|
-
let data;
|
|
2334
|
-
try {
|
|
2335
|
-
data = JSON.parse(dataText || '{}');
|
|
2336
|
-
} catch (err) {
|
|
2337
|
-
alert('Response Data \u4E0D\u662F\u6709\u6548\u7684 JSON: ' + err.message);
|
|
2338
|
-
return;
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
// \u53D1\u9001\u521B\u5EFA\u8BF7\u6C42
|
|
2342
|
-
try {
|
|
2343
|
-
const submitBtn = newApiForm.querySelector('button[type="submit"]');
|
|
2344
|
-
const originalText = submitBtn.textContent;
|
|
2345
|
-
submitBtn.disabled = true;
|
|
2346
|
-
submitBtn.textContent = 'Creating...';
|
|
2347
|
-
|
|
2348
|
-
const res = await fetch(apiBase + '/create', {
|
|
2349
|
-
method: 'POST',
|
|
2350
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2351
|
-
body: JSON.stringify({
|
|
2352
|
-
method: method,
|
|
2353
|
-
path: path,
|
|
2354
|
-
description: description || path,
|
|
2355
|
-
data: data
|
|
2356
|
-
})
|
|
2357
|
-
});
|
|
2358
|
-
|
|
2359
|
-
const result = await res.json();
|
|
2360
|
-
|
|
2361
|
-
if (!res.ok) {
|
|
2362
|
-
throw new Error(result.error || 'Failed to create API');
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
// \u5173\u95ED\u6A21\u6001\u6846
|
|
2366
|
-
newApiModal.classList.remove('show');
|
|
2367
|
-
newApiForm.reset();
|
|
2368
|
-
|
|
2369
|
-
// \u5237\u65B0\u5217\u8868
|
|
2370
|
-
currentMocks = await fetchMocks();
|
|
2371
|
-
renderMockList(currentMocks, true);
|
|
2372
|
-
|
|
2373
|
-
// \u81EA\u52A8\u9009\u4E2D\u65B0\u521B\u5EFA\u7684 API
|
|
2374
|
-
if (result.mock && result.mock.key) {
|
|
2375
|
-
const button = document.querySelector('aside button[data-key="' + result.mock.key + '"]');
|
|
2376
|
-
if (button) {
|
|
2377
|
-
await selectMock(result.mock.key, button);
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
alert('\u2705 API Mock \u521B\u5EFA\u6210\u529F\uFF01');
|
|
2382
|
-
|
|
2383
|
-
submitBtn.disabled = false;
|
|
2384
|
-
submitBtn.textContent = originalText;
|
|
2385
|
-
} catch (err) {
|
|
2386
|
-
alert('\u521B\u5EFA\u5931\u8D25: ' + err.message);
|
|
2387
|
-
const submitBtn = newApiForm.querySelector('button[type="submit"]');
|
|
2388
|
-
submitBtn.disabled = false;
|
|
2389
|
-
submitBtn.textContent = 'Create';
|
|
2390
|
-
}
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
} catch (error) {
|
|
2394
|
-
console.error('[Inspector] Bootstrap failed:', error);
|
|
2395
|
-
}
|
|
443
|
+
const inspectorConfig = normalizeInspectorConfig(options.inspector);
|
|
444
|
+
const inspectorRoute = ensureTrailingSlash(inspectorConfig.route);
|
|
445
|
+
return async (req, res) => {
|
|
446
|
+
if (!req.url) {
|
|
447
|
+
return false;
|
|
2396
448
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
449
|
+
const url = new URL(req.url, "http://localhost");
|
|
450
|
+
if (!url.pathname.startsWith(inspectorRoute)) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
await handleInspectorRequest({
|
|
454
|
+
req,
|
|
455
|
+
res,
|
|
456
|
+
mockDir: options.mockDir,
|
|
457
|
+
inspectorRoute,
|
|
458
|
+
apiPrefix: options.apiPrefix,
|
|
459
|
+
inspectorConfig,
|
|
460
|
+
getMockFileMap: options.getMockFileMap
|
|
461
|
+
});
|
|
462
|
+
return true;
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async function handleInspectorRequest(context) {
|
|
466
|
+
const { req, res, inspectorRoute } = context;
|
|
467
|
+
const url = new URL(req.url || inspectorRoute, "http://localhost");
|
|
468
|
+
const normalizedRoute = ensureTrailingSlash(inspectorRoute);
|
|
469
|
+
if (url.pathname === normalizedRoute.slice(0, -1) || url.pathname === normalizedRoute) {
|
|
470
|
+
await serveInspectorHtml(context);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const relativePath = url.pathname.startsWith(normalizedRoute) ? url.pathname.slice(normalizedRoute.length) : null;
|
|
474
|
+
if (relativePath && relativePath.startsWith("api/")) {
|
|
475
|
+
await handleInspectorApi({ ...context, pathname: relativePath.slice(4) });
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
res.statusCode = 404;
|
|
479
|
+
res.end("Not Found");
|
|
2420
480
|
}
|
|
2421
|
-
async function
|
|
481
|
+
async function serveInspectorHtml({
|
|
2422
482
|
res,
|
|
2423
|
-
|
|
2424
|
-
pathname,
|
|
2425
|
-
mockDir,
|
|
483
|
+
inspectorRoute,
|
|
2426
484
|
apiPrefix,
|
|
2427
|
-
inspectorConfig
|
|
2428
|
-
getMockFileMap
|
|
485
|
+
inspectorConfig
|
|
2429
486
|
}) {
|
|
487
|
+
const routeJson = JSON.stringify(ensureTrailingSlash(inspectorRoute));
|
|
488
|
+
const allowToggleJson = JSON.stringify(inspectorConfig.enableToggle);
|
|
489
|
+
const apiPrefixEscaped = escapeHtml(apiPrefix);
|
|
490
|
+
const templatePath = import_path3.default.join(__dirname, "inspector-template.html");
|
|
491
|
+
const template = import_fs_extra2.default.readFileSync(templatePath, "utf-8");
|
|
492
|
+
const html = template.replace("__API_PREFIX__", apiPrefixEscaped).replace("__ROUTE_JSON__", routeJson).replace("__ALLOW_TOGGLE_JSON__", allowToggleJson);
|
|
493
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
494
|
+
res.end(html);
|
|
495
|
+
}
|
|
496
|
+
async function handleList(ctx, mockFileMap) {
|
|
497
|
+
const { res, mockDir } = ctx;
|
|
498
|
+
const list = await Promise.all(
|
|
499
|
+
Array.from(mockFileMap.entries()).map(async ([key, filePath]) => {
|
|
500
|
+
const info = await parseMockModule(filePath);
|
|
501
|
+
return buildMockItem(key, filePath, mockDir, info);
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
sendJson(res, { mocks: list });
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
async function handleDetail(ctx, mockFileMap) {
|
|
508
|
+
const { req, res, mockDir } = ctx;
|
|
509
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
510
|
+
const key = url.searchParams.get("key");
|
|
511
|
+
if (!key) {
|
|
512
|
+
sendJson(res, { error: "Missing key" }, 400);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
const filePath = mockFileMap.get(key);
|
|
516
|
+
if (!filePath) {
|
|
517
|
+
sendJson(res, { error: "Mock not found" }, 404);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
const info = await parseMockModule(filePath);
|
|
521
|
+
sendJson(res, { mock: buildMockItem(key, filePath, mockDir, info) });
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
async function handleUpdate(ctx, mockFileMap) {
|
|
525
|
+
const { req, res, mockDir, inspectorConfig } = ctx;
|
|
526
|
+
if (req.method !== "POST") {
|
|
527
|
+
sendJson(res, { error: "Method not allowed" }, 405);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
const body = await readBody(req);
|
|
531
|
+
let payload;
|
|
2430
532
|
try {
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
533
|
+
payload = JSON.parse(body);
|
|
534
|
+
} catch {
|
|
535
|
+
sendJson(res, { error: "Invalid JSON body" }, 400);
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
const { key, config, description } = payload || {};
|
|
539
|
+
if (!key) {
|
|
540
|
+
sendJson(res, { error: "Invalid payload: missing key" }, 400);
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
const filePath = mockFileMap.get(key);
|
|
544
|
+
if (!filePath) {
|
|
545
|
+
sendJson(res, { error: "Mock not found" }, 404);
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
const info = await parseMockModule(filePath);
|
|
549
|
+
let nextConfig = info.config;
|
|
550
|
+
if (config && typeof config === "object") {
|
|
551
|
+
if (!inspectorConfig.enableToggle && config.enable !== void 0) {
|
|
552
|
+
delete config.enable;
|
|
2450
553
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
554
|
+
nextConfig = { ...info.config, ...config };
|
|
555
|
+
}
|
|
556
|
+
const nextDescription = description !== void 0 ? description : info.description;
|
|
557
|
+
await writeMockFile(filePath, {
|
|
558
|
+
headerComment: info.headerComment,
|
|
559
|
+
config: nextConfig,
|
|
560
|
+
description: nextDescription
|
|
561
|
+
});
|
|
562
|
+
const updatedInfo = await parseMockModule(filePath);
|
|
563
|
+
sendJson(res, { mock: buildMockItem(key, filePath, mockDir, updatedInfo) });
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
async function handleCreate(ctx, mockFileMap) {
|
|
567
|
+
const { req, res, mockDir, apiPrefix } = ctx;
|
|
568
|
+
if (req.method !== "POST") {
|
|
569
|
+
sendJson(res, { error: "Method not allowed" }, 405);
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
const body = await readBody(req);
|
|
573
|
+
let payload;
|
|
574
|
+
try {
|
|
575
|
+
payload = JSON.parse(body);
|
|
576
|
+
} catch {
|
|
577
|
+
sendJson(res, { error: "Invalid JSON body" }, 400);
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
const { method, path: apiPath, data } = payload || {};
|
|
581
|
+
if (!method || !apiPath) {
|
|
582
|
+
sendJson(res, { error: "Invalid payload: missing method or path" }, 400);
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
const fullUrl = apiPrefix + apiPath;
|
|
586
|
+
try {
|
|
587
|
+
let dataToSave;
|
|
588
|
+
if (typeof data === "object" && data !== null) {
|
|
589
|
+
dataToSave = JSON.stringify(data, null, 2);
|
|
590
|
+
} else {
|
|
591
|
+
dataToSave = typeof data === "string" ? data : "";
|
|
592
|
+
}
|
|
593
|
+
await saveMockData(fullUrl, method.toLowerCase(), dataToSave, mockDir, 200);
|
|
594
|
+
const newMockFileMap = await buildMockIndex(mockDir);
|
|
595
|
+
for (const [key2, value] of newMockFileMap.entries()) {
|
|
596
|
+
mockFileMap.set(key2, value);
|
|
597
|
+
}
|
|
598
|
+
const normalizedPath = toPosixPath(apiPath);
|
|
599
|
+
const key = (normalizedPath.startsWith("/") ? "" : "/") + normalizedPath.toLowerCase() + "/" + method.toLowerCase() + ".js";
|
|
600
|
+
const filePath = mockFileMap.get(key);
|
|
601
|
+
if (!filePath) {
|
|
602
|
+
sendJson(res, { error: "Mock file created but not found in map" }, 500);
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
const info = await parseMockModule(filePath);
|
|
606
|
+
sendJson(res, {
|
|
607
|
+
success: true,
|
|
608
|
+
mock: buildMockItem(key, filePath, mockDir, info)
|
|
609
|
+
});
|
|
610
|
+
return true;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
613
|
+
sendJson(res, { error: "Failed to create mock: " + errorMessage }, 500);
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function handleDelete(ctx, mockFileMap) {
|
|
618
|
+
const { req, res } = ctx;
|
|
619
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
620
|
+
const key = url.searchParams.get("key");
|
|
621
|
+
if (!key) {
|
|
622
|
+
sendJson(res, { error: "Missing key" }, 400);
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
const filePath = mockFileMap.get(key);
|
|
626
|
+
if (!filePath) {
|
|
627
|
+
sendJson(res, { error: "Mock not found" }, 404);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const stats = await import_fs_extra2.default.stat(filePath);
|
|
632
|
+
if (stats.isDirectory()) {
|
|
633
|
+
await import_fs_extra2.default.remove(filePath);
|
|
634
|
+
for (const [mapKey, mapPath] of mockFileMap.entries()) {
|
|
635
|
+
if (mapPath.startsWith(filePath + import_path3.default.sep) || mapPath === filePath) {
|
|
636
|
+
mockFileMap.delete(mapKey);
|
|
2474
637
|
}
|
|
2475
|
-
});
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
if (pathname === "update") {
|
|
2479
|
-
if (req.method !== "POST") {
|
|
2480
|
-
sendJson(res, { error: "Method not allowed" }, 405);
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
const body = await readBody(req);
|
|
2484
|
-
let payload;
|
|
2485
|
-
try {
|
|
2486
|
-
payload = JSON.parse(body);
|
|
2487
|
-
} catch (error) {
|
|
2488
|
-
sendJson(res, { error: "Invalid JSON body" }, 400);
|
|
2489
|
-
return;
|
|
2490
|
-
}
|
|
2491
|
-
const { key, config, description } = payload || {};
|
|
2492
|
-
if (!key) {
|
|
2493
|
-
sendJson(res, { error: "Invalid payload: missing key" }, 400);
|
|
2494
|
-
return;
|
|
2495
|
-
}
|
|
2496
|
-
const filePath = mockFileMap.get(key);
|
|
2497
|
-
if (!filePath) {
|
|
2498
|
-
sendJson(res, { error: "Mock not found" }, 404);
|
|
2499
|
-
return;
|
|
2500
638
|
}
|
|
639
|
+
} else {
|
|
640
|
+
await import_fs_extra2.default.unlink(filePath);
|
|
641
|
+
mockFileMap.delete(key);
|
|
642
|
+
}
|
|
643
|
+
sendJson(res, { success: true });
|
|
644
|
+
} catch (error) {
|
|
645
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
646
|
+
sendJson(res, { error: "Failed to delete: " + errorMessage }, 500);
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
async function handleBatchUpdate(ctx, mockFileMap) {
|
|
651
|
+
const { req, res } = ctx;
|
|
652
|
+
if (req.method !== "POST") {
|
|
653
|
+
sendJson(res, { error: "Method not allowed" }, 405);
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
const body = await readBody(req);
|
|
657
|
+
let payload;
|
|
658
|
+
try {
|
|
659
|
+
payload = JSON.parse(body);
|
|
660
|
+
} catch {
|
|
661
|
+
sendJson(res, { error: "Invalid JSON body" }, 400);
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
const updates = payload?.updates;
|
|
665
|
+
if (!Array.isArray(updates) || updates.length === 0) {
|
|
666
|
+
sendJson(res, { error: "Invalid payload: updates must be a non-empty array" }, 400);
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
let updated = 0;
|
|
670
|
+
const errors = [];
|
|
671
|
+
for (const item of updates) {
|
|
672
|
+
const { key, config } = item;
|
|
673
|
+
if (!key) continue;
|
|
674
|
+
const filePath = mockFileMap.get(key);
|
|
675
|
+
if (!filePath) {
|
|
676
|
+
errors.push(`Mock not found: ${key}`);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
2501
680
|
const info = await parseMockModule(filePath);
|
|
2502
|
-
|
|
2503
|
-
if (config && typeof config === "object") {
|
|
2504
|
-
if (!inspectorConfig.enableToggle && config.enable !== void 0) {
|
|
2505
|
-
delete config.enable;
|
|
2506
|
-
}
|
|
2507
|
-
nextConfig = {
|
|
2508
|
-
...info.config,
|
|
2509
|
-
...config
|
|
2510
|
-
};
|
|
2511
|
-
}
|
|
2512
|
-
const nextDescription = description !== void 0 ? description : info.description;
|
|
681
|
+
const nextConfig = config ? { ...info.config, ...config } : info.config;
|
|
2513
682
|
await writeMockFile(filePath, {
|
|
2514
683
|
headerComment: info.headerComment,
|
|
2515
684
|
config: nextConfig,
|
|
2516
|
-
description:
|
|
2517
|
-
});
|
|
2518
|
-
const updatedInfo = await parseMockModule(filePath);
|
|
2519
|
-
sendJson(res, {
|
|
2520
|
-
mock: {
|
|
2521
|
-
key,
|
|
2522
|
-
file: import_path2.default.relative(mockDir, filePath),
|
|
2523
|
-
method: key.split("/").pop()?.replace(/\.js$/, "") ?? "get",
|
|
2524
|
-
path: key.replace(/\/[^/]+\.js$/, ""),
|
|
2525
|
-
config: updatedInfo.config,
|
|
2526
|
-
editable: updatedInfo.serializable,
|
|
2527
|
-
description: updatedInfo.description,
|
|
2528
|
-
dataText: updatedInfo.dataText
|
|
2529
|
-
}
|
|
685
|
+
description: info.description
|
|
2530
686
|
});
|
|
687
|
+
updated++;
|
|
688
|
+
} catch (err) {
|
|
689
|
+
errors.push(`Failed to update ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
sendJson(res, { success: true, updated, errors: errors.length > 0 ? errors : void 0 });
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
var API_ROUTES = {
|
|
696
|
+
list: handleList,
|
|
697
|
+
detail: handleDetail,
|
|
698
|
+
update: handleUpdate,
|
|
699
|
+
"batch-update": handleBatchUpdate,
|
|
700
|
+
create: handleCreate,
|
|
701
|
+
delete: handleDelete
|
|
702
|
+
};
|
|
703
|
+
async function handleInspectorApi(ctx) {
|
|
704
|
+
try {
|
|
705
|
+
const mockFileMap = ctx.getMockFileMap();
|
|
706
|
+
const handler = API_ROUTES[ctx.pathname];
|
|
707
|
+
if (handler) {
|
|
708
|
+
await handler(ctx, mockFileMap);
|
|
2531
709
|
return;
|
|
2532
710
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
711
|
+
sendJson(ctx.res, { error: "Unknown inspector endpoint" }, 404);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
console.error("[automock] Inspector request failed:", error);
|
|
714
|
+
sendJson(ctx.res, { error: "Inspector failed" }, 500);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/middleware.ts
|
|
719
|
+
function serveMockResponse(res, mockFilePath, mockResult) {
|
|
720
|
+
const { data, delay = 0, status = 200 } = mockResult;
|
|
721
|
+
setTimeout(() => {
|
|
722
|
+
const isBinaryMock = data && typeof data === "object" && data.__binaryFile;
|
|
723
|
+
if (isBinaryMock) {
|
|
724
|
+
const binaryData = data;
|
|
725
|
+
const binaryFilePath = mockFilePath.replace(
|
|
726
|
+
/\.js$/,
|
|
727
|
+
"." + binaryData.__binaryFile
|
|
728
|
+
);
|
|
729
|
+
if (!import_fs_extra3.default.existsSync(binaryFilePath)) {
|
|
730
|
+
console.error(
|
|
731
|
+
`[automock] Binary mock file not found: ${binaryFilePath}`
|
|
2552
732
|
);
|
|
733
|
+
sendError(res, "Binary mock file not found", 404);
|
|
2553
734
|
return;
|
|
2554
735
|
}
|
|
2555
|
-
const { saveMockData: saveMockData2 } = await Promise.resolve().then(() => (init_mockFileUtils(), mockFileUtils_exports));
|
|
2556
|
-
const fullUrl = apiPrefix + apiPath;
|
|
2557
736
|
try {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
dataToSave,
|
|
2568
|
-
mockDir,
|
|
2569
|
-
200
|
|
2570
|
-
);
|
|
2571
|
-
const { buildMockIndex: buildMockIndex2 } = await Promise.resolve().then(() => (init_mockFileUtils(), mockFileUtils_exports));
|
|
2572
|
-
const newMockFileMap = await buildMockIndex2(mockDir);
|
|
2573
|
-
for (const [key2, value] of newMockFileMap.entries()) {
|
|
2574
|
-
mockFileMap.set(key2, value);
|
|
2575
|
-
}
|
|
2576
|
-
const normalizedPath = toPosixPath(apiPath);
|
|
2577
|
-
const key = (normalizedPath.startsWith("/") ? "" : "/") + normalizedPath.toLowerCase() + "/" + method.toLowerCase() + ".js";
|
|
2578
|
-
const filePath = mockFileMap.get(key);
|
|
2579
|
-
if (!filePath) {
|
|
2580
|
-
sendJson(
|
|
2581
|
-
res,
|
|
2582
|
-
{ error: "Mock file created but not found in map" },
|
|
2583
|
-
500
|
|
2584
|
-
);
|
|
2585
|
-
return;
|
|
2586
|
-
}
|
|
2587
|
-
const info = await parseMockModule(filePath);
|
|
2588
|
-
sendJson(res, {
|
|
2589
|
-
success: true,
|
|
2590
|
-
mock: {
|
|
2591
|
-
key,
|
|
2592
|
-
file: import_path2.default.relative(mockDir, filePath),
|
|
2593
|
-
method: method.toLowerCase(),
|
|
2594
|
-
path: apiPath,
|
|
2595
|
-
config: info.config,
|
|
2596
|
-
editable: info.serializable,
|
|
2597
|
-
description: info.description,
|
|
2598
|
-
dataText: info.dataText
|
|
2599
|
-
}
|
|
2600
|
-
});
|
|
2601
|
-
return;
|
|
737
|
+
const fileData = import_fs_extra3.default.readFileSync(binaryFilePath);
|
|
738
|
+
const contentType = binaryData.__contentType || "application/octet-stream";
|
|
739
|
+
res.setHeader("Content-Type", contentType);
|
|
740
|
+
res.setHeader("Content-Length", fileData.length);
|
|
741
|
+
res.setHeader("X-Mock-Response", "true");
|
|
742
|
+
res.setHeader("X-Mock-Source", "vite-plugin-automock");
|
|
743
|
+
res.setHeader("X-Mock-Binary-File", "true");
|
|
744
|
+
res.statusCode = status;
|
|
745
|
+
res.end(fileData);
|
|
2602
746
|
} catch (error) {
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
return;
|
|
747
|
+
console.error("[automock] Failed to read binary mock file:", error);
|
|
748
|
+
sendError(res, "Failed to read binary mock file");
|
|
2606
749
|
}
|
|
750
|
+
} else {
|
|
751
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
752
|
+
res.setHeader("X-Mock-Response", "true");
|
|
753
|
+
res.setHeader("X-Mock-Source", "vite-plugin-automock");
|
|
754
|
+
res.statusCode = status;
|
|
755
|
+
res.end(typeof data === "string" ? data : JSON.stringify(data));
|
|
2607
756
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
757
|
+
}, delay);
|
|
758
|
+
}
|
|
759
|
+
function proxyAndCapture(req, res, options) {
|
|
760
|
+
const { proxyBaseUrl, pathRewrite, shouldSave, method, pathname, mockDir, onMockSaved } = options;
|
|
761
|
+
const targetUrl = proxyBaseUrl + pathRewrite(req.url || "");
|
|
762
|
+
const client = targetUrl.startsWith("https") ? import_https.default : import_http.default;
|
|
763
|
+
function sendProxyRequest(body) {
|
|
764
|
+
const targetUrlObj = new URL(proxyBaseUrl);
|
|
765
|
+
const proxyOptions = {
|
|
766
|
+
method: req.method,
|
|
767
|
+
headers: {
|
|
768
|
+
...req.headers,
|
|
769
|
+
host: targetUrlObj.host
|
|
770
|
+
},
|
|
771
|
+
rejectUnauthorized: false
|
|
772
|
+
};
|
|
773
|
+
const proxyReq = client.request(targetUrl, proxyOptions, (proxyRes) => {
|
|
774
|
+
const contentType = proxyRes.headers["content-type"] || "";
|
|
775
|
+
const isJson = contentType.includes("application/json");
|
|
776
|
+
if (!isJson) {
|
|
777
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
778
|
+
proxyRes.pipe(res);
|
|
2618
779
|
return;
|
|
2619
780
|
}
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
781
|
+
const chunks = [];
|
|
782
|
+
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
783
|
+
proxyRes.on("end", async () => {
|
|
784
|
+
try {
|
|
785
|
+
const responseData = Buffer.concat(chunks);
|
|
786
|
+
if (shouldSave) {
|
|
787
|
+
try {
|
|
788
|
+
console.log(
|
|
789
|
+
`[automock] Capturing mock: ${req.url} -> ${pathname}`
|
|
790
|
+
);
|
|
791
|
+
const savedFilePath = await saveMockData(
|
|
792
|
+
req.url,
|
|
793
|
+
method,
|
|
794
|
+
responseData,
|
|
795
|
+
mockDir,
|
|
796
|
+
proxyRes.statusCode,
|
|
797
|
+
contentType
|
|
798
|
+
);
|
|
799
|
+
if (savedFilePath) {
|
|
800
|
+
console.log(`[automock] Mock saved: ${pathname}`);
|
|
801
|
+
onMockSaved();
|
|
802
|
+
} else {
|
|
803
|
+
console.log(
|
|
804
|
+
`[automock] Mock file already exists, skipped: ${pathname}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
} catch (saveError) {
|
|
808
|
+
console.error("[automock] Failed to save mock:", saveError);
|
|
2629
809
|
}
|
|
2630
810
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
811
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
812
|
+
res.end(responseData);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
console.error("[automock] Failed to process response:", error);
|
|
815
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
816
|
+
res.end(Buffer.concat(chunks));
|
|
2634
817
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
proxyReq.on("error", (err) => {
|
|
821
|
+
console.error("[automock] Proxy request failed:", err);
|
|
822
|
+
sendError(res, err.message);
|
|
823
|
+
});
|
|
824
|
+
if (body && (req.method === "POST" || req.method === "PUT" || req.method === "PATCH")) {
|
|
825
|
+
proxyReq.write(body);
|
|
2641
826
|
}
|
|
2642
|
-
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
|
|
827
|
+
proxyReq.end();
|
|
828
|
+
}
|
|
829
|
+
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
|
|
830
|
+
let bodyStr = "";
|
|
831
|
+
req.on("data", (chunk) => bodyStr += chunk.toString());
|
|
832
|
+
req.on("end", () => sendProxyRequest(bodyStr));
|
|
833
|
+
} else if (req.readableEnded) {
|
|
834
|
+
sendProxyRequest();
|
|
835
|
+
} else {
|
|
836
|
+
req.on("end", () => sendProxyRequest());
|
|
2646
837
|
}
|
|
2647
838
|
}
|
|
2648
|
-
function
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
839
|
+
function printServerUrls(server, urls) {
|
|
840
|
+
server.httpServer?.once("listening", () => {
|
|
841
|
+
const address = server.httpServer?.address();
|
|
842
|
+
if (!address || typeof address !== "object") return;
|
|
843
|
+
const { protocol, host, port } = getServerAddress(
|
|
844
|
+
address,
|
|
845
|
+
!!server.config.server.https
|
|
846
|
+
);
|
|
847
|
+
for (const { path: urlPath, label, color, delay } of urls) {
|
|
848
|
+
setTimeout(() => {
|
|
849
|
+
const fullUrl = `${protocol}://${host}:${port}${urlPath}`;
|
|
850
|
+
console.log(` \u279C ${color}${label}\x1B[0m: \x1B[1m${fullUrl}\x1B[0m`);
|
|
851
|
+
}, delay);
|
|
852
|
+
}
|
|
2659
853
|
});
|
|
2660
854
|
}
|
|
2661
|
-
|
|
2662
|
-
// src/middleware.ts
|
|
2663
855
|
function automock(options) {
|
|
2664
856
|
const {
|
|
2665
|
-
mockDir: configMockDir =
|
|
857
|
+
mockDir: configMockDir = import_path4.default.join(process.cwd(), "mock"),
|
|
2666
858
|
apiPrefix = "/api",
|
|
2667
859
|
pathRewrite = (p) => p,
|
|
2668
860
|
proxyBaseUrl,
|
|
2669
861
|
inspector = false
|
|
2670
862
|
} = options;
|
|
2671
|
-
const mockDir =
|
|
2672
|
-
if (!
|
|
863
|
+
const mockDir = resolveAbsolutePath(configMockDir);
|
|
864
|
+
if (!import_fs_extra3.default.existsSync(mockDir)) {
|
|
2673
865
|
try {
|
|
2674
|
-
|
|
2675
|
-
console.log(
|
|
866
|
+
import_fs_extra3.default.ensureDirSync(mockDir);
|
|
867
|
+
console.log(`[automock] Mock directory created: ${mockDir}`);
|
|
2676
868
|
} catch (error) {
|
|
2677
|
-
console.error(
|
|
869
|
+
console.error(`[automock] Failed to create mock directory: ${mockDir}`, error);
|
|
2678
870
|
}
|
|
2679
871
|
}
|
|
2680
872
|
let watcher;
|
|
@@ -2699,11 +891,11 @@ function automock(options) {
|
|
|
2699
891
|
}
|
|
2700
892
|
}
|
|
2701
893
|
},
|
|
2702
|
-
configureServer(server) {
|
|
2703
|
-
let mockFileMap = buildMockIndex(mockDir);
|
|
2704
|
-
console.log(
|
|
2705
|
-
const rebuildMockFileMap = (0, import_lodash.default)(() => {
|
|
2706
|
-
mockFileMap = buildMockIndex(mockDir);
|
|
894
|
+
async configureServer(server) {
|
|
895
|
+
let mockFileMap = await buildMockIndex(mockDir);
|
|
896
|
+
console.log(`[automock] Loaded ${mockFileMap.size} mock files`);
|
|
897
|
+
const rebuildMockFileMap = (0, import_lodash.default)(async () => {
|
|
898
|
+
mockFileMap = await buildMockIndex(mockDir);
|
|
2707
899
|
}, 200);
|
|
2708
900
|
watcher = import_chokidar.default.watch(mockDir, {
|
|
2709
901
|
ignoreInitial: true,
|
|
@@ -2712,251 +904,78 @@ function automock(options) {
|
|
|
2712
904
|
});
|
|
2713
905
|
watcher.on("add", () => rebuildMockFileMap());
|
|
2714
906
|
watcher.on("unlink", () => rebuildMockFileMap());
|
|
2715
|
-
server.httpServer?.on("close", () =>
|
|
2716
|
-
watcher?.close();
|
|
2717
|
-
});
|
|
907
|
+
server.httpServer?.on("close", () => watcher?.close());
|
|
2718
908
|
const inspectorHandler = createInspectorHandler({
|
|
2719
909
|
inspector,
|
|
2720
910
|
apiPrefix,
|
|
2721
911
|
mockDir,
|
|
2722
912
|
getMockFileMap: () => mockFileMap
|
|
2723
913
|
});
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
if (address && typeof address === "object") {
|
|
2728
|
-
const protocol = server.config.server.https ? "https" : "http";
|
|
2729
|
-
const host = address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address;
|
|
2730
|
-
const port = address.port;
|
|
2731
|
-
const mockApiUrl = `${protocol}://${host}:${port}${apiPrefix}`;
|
|
2732
|
-
console.log(
|
|
2733
|
-
` \u279C \x1B[32mMock API\x1B[0m: \x1B[1m${mockApiUrl}\x1B[0m`
|
|
2734
|
-
);
|
|
2735
|
-
}
|
|
2736
|
-
}, 100);
|
|
2737
|
-
});
|
|
914
|
+
const urlsToPrint = [
|
|
915
|
+
{ path: apiPrefix, label: "Mock API", color: "\x1B[32m", delay: 100 }
|
|
916
|
+
];
|
|
2738
917
|
if (inspector) {
|
|
2739
918
|
const inspectorConfig = normalizeInspectorConfig(inspector);
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
const host = address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address;
|
|
2746
|
-
const port = address.port;
|
|
2747
|
-
const inspectorUrl = `${protocol}://${host}:${port}${inspectorConfig.route}`;
|
|
2748
|
-
console.log(
|
|
2749
|
-
` \u279C \x1B[95mMock Inspector\x1B[0m: \x1B[1m\x1B[96m${inspectorUrl}\x1B[0m`
|
|
2750
|
-
);
|
|
2751
|
-
}
|
|
2752
|
-
}, 150);
|
|
919
|
+
urlsToPrint.push({
|
|
920
|
+
path: inspectorConfig.route,
|
|
921
|
+
label: "Mock Inspector",
|
|
922
|
+
color: "\x1B[95m",
|
|
923
|
+
delay: 150
|
|
2753
924
|
});
|
|
2754
925
|
}
|
|
926
|
+
printServerUrls(server, urlsToPrint);
|
|
927
|
+
async function tryLoadMock(key) {
|
|
928
|
+
const mockFilePath = mockFileMap.get(key);
|
|
929
|
+
if (!mockFilePath) return null;
|
|
930
|
+
const absolutePath = resolveAbsolutePath(mockFilePath);
|
|
931
|
+
if (!import_fs_extra3.default.existsSync(absolutePath)) return null;
|
|
932
|
+
const { default: mockModule } = await import(`${absolutePath}?t=${Date.now()}`);
|
|
933
|
+
const mockResult = typeof mockModule.data === "function" ? mockModule.data() : mockModule;
|
|
934
|
+
const { enable = true } = mockResult || {};
|
|
935
|
+
return enable ? { absolutePath, mockResult } : null;
|
|
936
|
+
}
|
|
2755
937
|
server.middlewares.use(
|
|
2756
938
|
async (req, res, next) => {
|
|
2757
939
|
if (inspectorHandler && req.url) {
|
|
2758
940
|
const handled = await inspectorHandler(req, res);
|
|
2759
|
-
if (handled)
|
|
2760
|
-
return;
|
|
2761
|
-
}
|
|
941
|
+
if (handled) return;
|
|
2762
942
|
}
|
|
2763
943
|
if (!req.url?.startsWith(apiPrefix)) {
|
|
2764
944
|
return next();
|
|
2765
945
|
}
|
|
946
|
+
const accept = req.headers.accept || "";
|
|
947
|
+
const upgrade = req.headers.upgrade || "";
|
|
948
|
+
if (accept.includes("text/event-stream") || upgrade.toLowerCase() === "websocket") {
|
|
949
|
+
return next();
|
|
950
|
+
}
|
|
2766
951
|
const method = (req.method || "GET").toLowerCase();
|
|
2767
|
-
const
|
|
2768
|
-
const pathname = urlObj.pathname;
|
|
952
|
+
const pathname = new URL(req.url, "http://localhost").pathname;
|
|
2769
953
|
const key = `${pathname}/${method}.js`.toLowerCase();
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
if (!import_fs_extra2.default.existsSync(absolutePath)) {
|
|
2776
|
-
throw new Error(`Mock file does not exist: ${absolutePath}`);
|
|
2777
|
-
}
|
|
2778
|
-
const { default: mockModule } = await import(`${absolutePath}?t=${Date.now()}`);
|
|
2779
|
-
const mockResult = typeof mockModule.data === "function" ? mockModule.data() : mockModule;
|
|
2780
|
-
const {
|
|
2781
|
-
enable = true,
|
|
2782
|
-
data,
|
|
2783
|
-
delay = 0,
|
|
2784
|
-
status = 200
|
|
2785
|
-
} = mockResult || {};
|
|
2786
|
-
if (enable) {
|
|
2787
|
-
setTimeout(() => {
|
|
2788
|
-
const isBinaryMock = data?.__binaryFile;
|
|
2789
|
-
if (isBinaryMock) {
|
|
2790
|
-
const binaryFilePath = absolutePath.replace(
|
|
2791
|
-
/\.js$/,
|
|
2792
|
-
"." + data.__binaryFile
|
|
2793
|
-
);
|
|
2794
|
-
if (import_fs_extra2.default.existsSync(binaryFilePath)) {
|
|
2795
|
-
try {
|
|
2796
|
-
const binaryData = import_fs_extra2.default.readFileSync(binaryFilePath);
|
|
2797
|
-
const contentType = data.__contentType || "application/octet-stream";
|
|
2798
|
-
res.setHeader("Content-Type", contentType);
|
|
2799
|
-
res.setHeader("Content-Length", binaryData.length);
|
|
2800
|
-
res.setHeader("X-Mock-Response", "true");
|
|
2801
|
-
res.setHeader("X-Mock-Source", "vite-plugin-automock");
|
|
2802
|
-
res.setHeader("X-Mock-Binary-File", "true");
|
|
2803
|
-
res.statusCode = status;
|
|
2804
|
-
res.end(binaryData);
|
|
2805
|
-
} catch (error) {
|
|
2806
|
-
console.error(
|
|
2807
|
-
"\u274C [automock] \u8BFB\u53D6\u4E8C\u8FDB\u5236mock\u6587\u4EF6\u5931\u8D25:",
|
|
2808
|
-
error
|
|
2809
|
-
);
|
|
2810
|
-
res.statusCode = 500;
|
|
2811
|
-
res.end(
|
|
2812
|
-
JSON.stringify({
|
|
2813
|
-
error: "Failed to read binary mock file"
|
|
2814
|
-
})
|
|
2815
|
-
);
|
|
2816
|
-
}
|
|
2817
|
-
} else {
|
|
2818
|
-
console.error(
|
|
2819
|
-
"\u274C [automock] \u4E8C\u8FDB\u5236mock\u6587\u4EF6\u4E0D\u5B58\u5728:",
|
|
2820
|
-
binaryFilePath
|
|
2821
|
-
);
|
|
2822
|
-
res.statusCode = 404;
|
|
2823
|
-
res.end(
|
|
2824
|
-
JSON.stringify({ error: "Binary mock file not found" })
|
|
2825
|
-
);
|
|
2826
|
-
}
|
|
2827
|
-
} else {
|
|
2828
|
-
res.setHeader(
|
|
2829
|
-
"Content-Type",
|
|
2830
|
-
"application/json; charset=utf-8"
|
|
2831
|
-
);
|
|
2832
|
-
res.setHeader("X-Mock-Response", "true");
|
|
2833
|
-
res.setHeader("X-Mock-Source", "vite-plugin-automock");
|
|
2834
|
-
res.statusCode = status;
|
|
2835
|
-
res.end(
|
|
2836
|
-
typeof data === "string" ? data : JSON.stringify(data)
|
|
2837
|
-
);
|
|
2838
|
-
}
|
|
2839
|
-
}, delay);
|
|
2840
|
-
return;
|
|
2841
|
-
}
|
|
2842
|
-
} catch (error) {
|
|
2843
|
-
console.error(`\u274C [automock] \u52A0\u8F7D mock \u6587\u4EF6\u5931\u8D25:`, error);
|
|
954
|
+
try {
|
|
955
|
+
const mock = await tryLoadMock(key);
|
|
956
|
+
if (mock) {
|
|
957
|
+
serveMockResponse(res, mock.absolutePath, mock.mockResult);
|
|
958
|
+
return;
|
|
2844
959
|
}
|
|
960
|
+
} catch (error) {
|
|
961
|
+
console.error("[automock] Failed to load mock file:", error);
|
|
2845
962
|
}
|
|
2846
|
-
|
|
2847
|
-
if (!proxyBaseUrl) {
|
|
2848
|
-
res.statusCode = 404;
|
|
2849
|
-
res.end(
|
|
2850
|
-
JSON.stringify({
|
|
2851
|
-
error: "No mock found and proxyBaseUrl not configured"
|
|
2852
|
-
})
|
|
2853
|
-
);
|
|
2854
|
-
return;
|
|
2855
|
-
}
|
|
963
|
+
if (!proxyBaseUrl) return next();
|
|
2856
964
|
try {
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
rejectUnauthorized: false
|
|
2867
|
-
};
|
|
2868
|
-
const proxyReq = client.request(
|
|
2869
|
-
targetUrl,
|
|
2870
|
-
proxyOptions,
|
|
2871
|
-
(proxyRes) => {
|
|
2872
|
-
const contentType = proxyRes.headers["content-type"];
|
|
2873
|
-
const chunks = [];
|
|
2874
|
-
proxyRes.on("data", (chunk) => {
|
|
2875
|
-
chunks.push(chunk);
|
|
2876
|
-
});
|
|
2877
|
-
proxyRes.on("end", async () => {
|
|
2878
|
-
try {
|
|
2879
|
-
const responseData = Buffer.concat(chunks);
|
|
2880
|
-
if (shouldSaveMockData) {
|
|
2881
|
-
try {
|
|
2882
|
-
console.log(
|
|
2883
|
-
`\u{1F504} [automock] \u5C1D\u8BD5\u4FDD\u5B58 mock: ${req.url} -> ${pathname}`
|
|
2884
|
-
);
|
|
2885
|
-
const savedFilePath = await saveMockData(
|
|
2886
|
-
req.url,
|
|
2887
|
-
method,
|
|
2888
|
-
responseData,
|
|
2889
|
-
mockDir,
|
|
2890
|
-
proxyRes.statusCode,
|
|
2891
|
-
contentType
|
|
2892
|
-
);
|
|
2893
|
-
if (savedFilePath) {
|
|
2894
|
-
console.log(
|
|
2895
|
-
`\u2705 [automock] \u5DF2\u4FDD\u5B58 mock: ${pathname}`
|
|
2896
|
-
);
|
|
2897
|
-
mockFileMap = buildMockIndex(mockDir);
|
|
2898
|
-
} else {
|
|
2899
|
-
console.log(
|
|
2900
|
-
`\u2139\uFE0F [automock] mock \u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7: ${pathname}`
|
|
2901
|
-
);
|
|
2902
|
-
}
|
|
2903
|
-
} catch (saveError) {
|
|
2904
|
-
console.error(
|
|
2905
|
-
"\u274C [automock] \u4FDD\u5B58 mock \u5931\u8D25:",
|
|
2906
|
-
saveError
|
|
2907
|
-
);
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
res.writeHead(
|
|
2911
|
-
proxyRes.statusCode || 200,
|
|
2912
|
-
proxyRes.headers
|
|
2913
|
-
);
|
|
2914
|
-
res.end(responseData);
|
|
2915
|
-
} catch (error) {
|
|
2916
|
-
console.error("\u274C [automock] \u5904\u7406\u54CD\u5E94\u5931\u8D25:", error);
|
|
2917
|
-
res.writeHead(
|
|
2918
|
-
proxyRes.statusCode || 200,
|
|
2919
|
-
proxyRes.headers
|
|
2920
|
-
);
|
|
2921
|
-
res.end(Buffer.concat(chunks));
|
|
2922
|
-
}
|
|
2923
|
-
});
|
|
2924
|
-
}
|
|
2925
|
-
);
|
|
2926
|
-
proxyReq.on("error", (err) => {
|
|
2927
|
-
console.error("\u274C [automock] \u4EE3\u7406\u8BF7\u6C42\u5931\u8D25:", err);
|
|
2928
|
-
res.statusCode = 500;
|
|
2929
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
2930
|
-
});
|
|
2931
|
-
if (body && (req.method === "POST" || req.method === "PUT" || req.method === "PATCH")) {
|
|
2932
|
-
proxyReq.write(body);
|
|
2933
|
-
}
|
|
2934
|
-
proxyReq.end();
|
|
2935
|
-
};
|
|
2936
|
-
var sendProxyRequest = sendProxyRequest2;
|
|
2937
|
-
const targetUrl = proxyBaseUrl + pathRewrite(req.url || "");
|
|
2938
|
-
const client = targetUrl.startsWith("https") ? import_https.default : import_http.default;
|
|
2939
|
-
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
|
|
2940
|
-
let bodyStr = "";
|
|
2941
|
-
req.on("data", (chunk) => {
|
|
2942
|
-
bodyStr += chunk.toString();
|
|
2943
|
-
});
|
|
2944
|
-
req.on("end", () => {
|
|
2945
|
-
sendProxyRequest2(bodyStr);
|
|
2946
|
-
});
|
|
2947
|
-
} else {
|
|
2948
|
-
if (req.readableEnded) {
|
|
2949
|
-
sendProxyRequest2();
|
|
2950
|
-
} else {
|
|
2951
|
-
req.on("end", () => {
|
|
2952
|
-
sendProxyRequest2();
|
|
2953
|
-
});
|
|
965
|
+
proxyAndCapture(req, res, {
|
|
966
|
+
proxyBaseUrl,
|
|
967
|
+
pathRewrite,
|
|
968
|
+
shouldSave: !mockFileMap.has(key),
|
|
969
|
+
method,
|
|
970
|
+
pathname,
|
|
971
|
+
mockDir,
|
|
972
|
+
onMockSaved: async () => {
|
|
973
|
+
mockFileMap = await buildMockIndex(mockDir);
|
|
2954
974
|
}
|
|
2955
|
-
}
|
|
975
|
+
});
|
|
2956
976
|
} catch (error) {
|
|
2957
|
-
console.error("
|
|
2958
|
-
res
|
|
2959
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
977
|
+
console.error("[automock] Proxy request error:", error);
|
|
978
|
+
sendError(res, "Internal server error");
|
|
2960
979
|
}
|
|
2961
980
|
}
|
|
2962
981
|
);
|
|
@@ -2971,9 +990,8 @@ function automock(options) {
|
|
|
2971
990
|
}
|
|
2972
991
|
|
|
2973
992
|
// src/mockBundler.ts
|
|
2974
|
-
var
|
|
2975
|
-
var
|
|
2976
|
-
init_mockFileUtils();
|
|
993
|
+
var import_path5 = __toESM(require("path"));
|
|
994
|
+
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
2977
995
|
var DEFAULT_BUNDLE_OPTIONS = {
|
|
2978
996
|
includeDisabled: true,
|
|
2979
997
|
log: true
|
|
@@ -2981,13 +999,13 @@ var DEFAULT_BUNDLE_OPTIONS = {
|
|
|
2981
999
|
async function bundleMockFiles(options) {
|
|
2982
1000
|
const opts = { ...DEFAULT_BUNDLE_OPTIONS, ...options };
|
|
2983
1001
|
const { mockDir, log } = opts;
|
|
2984
|
-
if (!
|
|
1002
|
+
if (!import_fs_extra4.default.existsSync(mockDir)) {
|
|
2985
1003
|
if (log) {
|
|
2986
1004
|
console.log(`[mock-bundler] Mock directory does not exist: ${mockDir}`);
|
|
2987
1005
|
}
|
|
2988
1006
|
return {};
|
|
2989
1007
|
}
|
|
2990
|
-
const mockFileMap = buildMockIndex(mockDir);
|
|
1008
|
+
const mockFileMap = await buildMockIndex(mockDir);
|
|
2991
1009
|
const bundle = {};
|
|
2992
1010
|
let bundledCount = 0;
|
|
2993
1011
|
let skippedCount = 0;
|
|
@@ -3021,32 +1039,18 @@ async function bundleMockFiles(options) {
|
|
|
3021
1039
|
return bundle;
|
|
3022
1040
|
}
|
|
3023
1041
|
function writeMockBundle(bundle, outputPath) {
|
|
3024
|
-
const outputDir =
|
|
3025
|
-
if (!
|
|
3026
|
-
|
|
1042
|
+
const outputDir = import_path5.default.dirname(outputPath);
|
|
1043
|
+
if (!import_fs_extra4.default.existsSync(outputDir)) {
|
|
1044
|
+
import_fs_extra4.default.ensureDirSync(outputDir);
|
|
3027
1045
|
}
|
|
3028
|
-
|
|
1046
|
+
import_fs_extra4.default.writeFileSync(
|
|
3029
1047
|
outputPath,
|
|
3030
1048
|
JSON.stringify(bundle, null, 2),
|
|
3031
1049
|
"utf-8"
|
|
3032
1050
|
);
|
|
3033
1051
|
}
|
|
3034
1052
|
|
|
3035
|
-
// src/index.ts
|
|
3036
|
-
init_mockFileUtils();
|
|
3037
|
-
init_mockFileUtils();
|
|
3038
|
-
|
|
3039
1053
|
// src/client/interceptor.ts
|
|
3040
|
-
var import_meta = {};
|
|
3041
|
-
function getEnvVar(name) {
|
|
3042
|
-
if (typeof import_meta !== "undefined" && import_meta.env) {
|
|
3043
|
-
return import_meta.env[name];
|
|
3044
|
-
}
|
|
3045
|
-
if (typeof process !== "undefined" && process.env) {
|
|
3046
|
-
return process.env[name];
|
|
3047
|
-
}
|
|
3048
|
-
return void 0;
|
|
3049
|
-
}
|
|
3050
1054
|
var MockInterceptor = class {
|
|
3051
1055
|
options;
|
|
3052
1056
|
constructor(options) {
|
|
@@ -3156,17 +1160,12 @@ async function loadMockData() {
|
|
|
3156
1160
|
return {};
|
|
3157
1161
|
}
|
|
3158
1162
|
}
|
|
3159
|
-
async function
|
|
1163
|
+
async function createInterceptorFromBundle() {
|
|
3160
1164
|
const mockData = await loadMockData();
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
"[MockInterceptor] axiosInstance is required. Please provide an axios instance."
|
|
3164
|
-
);
|
|
3165
|
-
}
|
|
3166
|
-
const isEnvEnabled = getEnvVar("VITE_USE_MOCK") === "true";
|
|
3167
|
-
const interceptor = createMockInterceptor({
|
|
1165
|
+
const isEnabled = typeof __AUTOMOCK_ENABLED__ !== "undefined" && __AUTOMOCK_ENABLED__;
|
|
1166
|
+
return new MockInterceptor({
|
|
3168
1167
|
mockData,
|
|
3169
|
-
enabled:
|
|
1168
|
+
enabled: isEnabled,
|
|
3170
1169
|
onMockHit: (url, method) => {
|
|
3171
1170
|
console.log(`[MOCK HIT] ${method} ${url}`);
|
|
3172
1171
|
},
|
|
@@ -3174,6 +1173,14 @@ async function initMockInterceptor(axiosInstance) {
|
|
|
3174
1173
|
console.log(`[MOCK BYPASS] ${method} ${url} - ${reason}`);
|
|
3175
1174
|
}
|
|
3176
1175
|
});
|
|
1176
|
+
}
|
|
1177
|
+
async function initMockInterceptor(axiosInstance) {
|
|
1178
|
+
if (!axiosInstance) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"[MockInterceptor] axiosInstance is required. Please provide an axios instance."
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
const interceptor = await createInterceptorFromBundle();
|
|
3177
1184
|
interceptor.setupAxios(axiosInstance);
|
|
3178
1185
|
}
|
|
3179
1186
|
function setMockEnabled(enabled) {
|
|
@@ -3192,18 +1199,7 @@ async function initMockInterceptorForPureHttp() {
|
|
|
3192
1199
|
"[MockInterceptor] http instance not registered. Call registerHttpInstance(http) first."
|
|
3193
1200
|
);
|
|
3194
1201
|
}
|
|
3195
|
-
const
|
|
3196
|
-
const isEnvEnabled = getEnvVar("VITE_USE_MOCK") === "true";
|
|
3197
|
-
const interceptor = createMockInterceptor({
|
|
3198
|
-
mockData,
|
|
3199
|
-
enabled: isEnvEnabled,
|
|
3200
|
-
onMockHit: (url, method) => {
|
|
3201
|
-
console.log(`[MOCK HIT] ${method} ${url}`);
|
|
3202
|
-
},
|
|
3203
|
-
onBypass: (url, method, reason) => {
|
|
3204
|
-
console.log(`[MOCK BYPASS] ${method} ${url} - ${reason}`);
|
|
3205
|
-
}
|
|
3206
|
-
});
|
|
1202
|
+
const interceptor = await createInterceptorFromBundle();
|
|
3207
1203
|
interceptor.setupAxios(httpInstanceRef.constructor.axiosInstance);
|
|
3208
1204
|
}
|
|
3209
1205
|
|
|
@@ -3216,25 +1212,40 @@ function automock2(options = {}) {
|
|
|
3216
1212
|
} = options;
|
|
3217
1213
|
const basePlugin = automock(pluginOptions);
|
|
3218
1214
|
let cachedBundle = null;
|
|
1215
|
+
const ensureBundleExists = () => {
|
|
1216
|
+
const outputPath = import_path6.default.join(process.cwd(), bundleOutputPath);
|
|
1217
|
+
if (!import_fs_extra5.default.existsSync(outputPath) && cachedBundle) {
|
|
1218
|
+
console.log("[automock] Re-writing mock bundle...");
|
|
1219
|
+
writeMockBundle(cachedBundle, outputPath);
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
3219
1222
|
return {
|
|
3220
1223
|
...basePlugin,
|
|
3221
1224
|
name: "vite-plugin-automock-with-bundle",
|
|
1225
|
+
config() {
|
|
1226
|
+
const enabled = pluginOptions.productionMock !== false;
|
|
1227
|
+
return {
|
|
1228
|
+
define: {
|
|
1229
|
+
__AUTOMOCK_ENABLED__: JSON.stringify(enabled)
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
},
|
|
3222
1233
|
buildEnd: async () => {
|
|
3223
1234
|
if (bundleMockData && pluginOptions.productionMock !== false) {
|
|
3224
1235
|
try {
|
|
3225
|
-
const mockDir = pluginOptions.mockDir ||
|
|
3226
|
-
if (!
|
|
1236
|
+
const mockDir = pluginOptions.mockDir || import_path6.default.join(process.cwd(), "mock");
|
|
1237
|
+
if (!import_fs_extra5.default.existsSync(mockDir)) {
|
|
3227
1238
|
console.log("[automock] Mock directory not found, skipping bundle generation");
|
|
3228
1239
|
console.log("[automock] Using existing mock-data.json if present");
|
|
3229
1240
|
return;
|
|
3230
1241
|
}
|
|
3231
|
-
const mockFileMap = buildMockIndex(mockDir);
|
|
1242
|
+
const mockFileMap = await buildMockIndex(mockDir);
|
|
3232
1243
|
if (mockFileMap.size === 0) {
|
|
3233
1244
|
console.log("[automock] No mock files found, skipping bundle generation");
|
|
3234
1245
|
return;
|
|
3235
1246
|
}
|
|
3236
1247
|
cachedBundle = await bundleMockFiles({ mockDir });
|
|
3237
|
-
const outputPath =
|
|
1248
|
+
const outputPath = import_path6.default.join(process.cwd(), bundleOutputPath);
|
|
3238
1249
|
writeMockBundle(cachedBundle, outputPath);
|
|
3239
1250
|
console.log(`[automock] Mock bundle written to: ${outputPath}`);
|
|
3240
1251
|
} catch (error) {
|
|
@@ -3242,20 +1253,8 @@ function automock2(options = {}) {
|
|
|
3242
1253
|
}
|
|
3243
1254
|
}
|
|
3244
1255
|
},
|
|
3245
|
-
writeBundle:
|
|
3246
|
-
|
|
3247
|
-
if (!import_fs_extra4.default.existsSync(outputPath) && cachedBundle) {
|
|
3248
|
-
console.log("[automock] Re-writing mock bundle in writeBundle...");
|
|
3249
|
-
writeMockBundle(cachedBundle, outputPath);
|
|
3250
|
-
}
|
|
3251
|
-
},
|
|
3252
|
-
closeBundle: async () => {
|
|
3253
|
-
const outputPath = import_path5.default.join(process.cwd(), bundleOutputPath);
|
|
3254
|
-
if (!import_fs_extra4.default.existsSync(outputPath) && cachedBundle) {
|
|
3255
|
-
console.log("[automock] Re-writing mock bundle in closeBundle...");
|
|
3256
|
-
writeMockBundle(cachedBundle, outputPath);
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
1256
|
+
writeBundle: ensureBundleExists,
|
|
1257
|
+
closeBundle: ensureBundleExists
|
|
3259
1258
|
};
|
|
3260
1259
|
}
|
|
3261
1260
|
// Annotate the CommonJS export names for ESM import in node:
|