@mokup/server 1.1.2 → 1.1.4
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/connect.cjs +5 -5
- package/dist/connect.d.cts +13 -2
- package/dist/connect.d.mts +13 -2
- package/dist/connect.d.ts +13 -2
- package/dist/connect.mjs +1 -1
- package/dist/express.cjs +1 -1
- package/dist/express.d.cts +13 -2
- package/dist/express.d.mts +13 -2
- package/dist/express.d.ts +13 -2
- package/dist/express.mjs +1 -1
- package/dist/fastify.cjs +5 -5
- package/dist/fastify.d.cts +13 -2
- package/dist/fastify.d.mts +13 -2
- package/dist/fastify.d.ts +13 -2
- package/dist/fastify.mjs +1 -1
- package/dist/fetch-server.cjs +599 -447
- package/dist/fetch-server.d.cts +81 -27
- package/dist/fetch-server.d.mts +81 -27
- package/dist/fetch-server.d.ts +81 -27
- package/dist/fetch-server.mjs +601 -449
- package/dist/fetch.cjs +5 -5
- package/dist/fetch.d.cts +12 -1
- package/dist/fetch.d.mts +12 -1
- package/dist/fetch.d.ts +12 -1
- package/dist/fetch.mjs +1 -1
- package/dist/hono.cjs +1 -1
- package/dist/hono.d.cts +12 -1
- package/dist/hono.d.mts +12 -1
- package/dist/hono.d.ts +12 -1
- package/dist/hono.mjs +1 -1
- package/dist/index.cjs +34 -2
- package/dist/index.d.cts +40 -1
- package/dist/index.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.mjs +36 -1
- package/dist/koa.cjs +5 -5
- package/dist/koa.d.cts +13 -2
- package/dist/koa.d.mts +13 -2
- package/dist/koa.d.ts +13 -2
- package/dist/koa.mjs +1 -1
- package/dist/node.cjs +3 -3
- package/dist/node.d.cts +3 -2
- package/dist/node.d.mts +3 -2
- package/dist/node.d.ts +3 -2
- package/dist/node.mjs +3 -3
- package/dist/shared/server.CyVIKPsp.d.cts +214 -0
- package/dist/shared/server.CyVIKPsp.d.mts +214 -0
- package/dist/shared/server.CyVIKPsp.d.ts +214 -0
- package/dist/shared/server.D0gAciOr.d.cts +46 -0
- package/dist/shared/server.D0gAciOr.d.mts +46 -0
- package/dist/shared/server.D0gAciOr.d.ts +46 -0
- package/dist/shared/server.DkerfsA-.d.cts +73 -0
- package/dist/shared/server.DkerfsA-.d.mts +73 -0
- package/dist/shared/server.DkerfsA-.d.ts +73 -0
- package/dist/shared/{server.tZ4R8aB2.mjs → server.LbftO9Jh.mjs} +57 -54
- package/dist/shared/{server.3GcmR3Ev.cjs → server.aaygIV2Q.cjs} +57 -54
- package/dist/worker-node.cjs +1 -1
- package/dist/worker-node.d.cts +29 -1
- package/dist/worker-node.d.mts +29 -1
- package/dist/worker-node.d.ts +29 -1
- package/dist/worker-node.mjs +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.d.cts +23 -1
- package/dist/worker.d.mts +23 -1
- package/dist/worker.d.ts +23 -1
- package/dist/worker.mjs +1 -1
- package/package.json +4 -4
- package/dist/shared/server.B82hrXoo.d.cts +0 -15
- package/dist/shared/server.B82hrXoo.d.mts +0 -15
- package/dist/shared/server.B82hrXoo.d.ts +0 -15
- package/dist/shared/server.Cb2eiCU2.d.cts +0 -17
- package/dist/shared/server.Cb2eiCU2.d.mts +0 -17
- package/dist/shared/server.Cb2eiCU2.d.ts +0 -17
package/dist/fetch-server.cjs
CHANGED
|
@@ -1,284 +1,78 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const process = require('node:process');
|
|
4
|
-
const hono = require('@mokup/shared/hono');
|
|
5
3
|
const pathe = require('@mokup/shared/pathe');
|
|
6
4
|
const node_fs = require('node:fs');
|
|
7
5
|
const node_module = require('node:module');
|
|
6
|
+
const process = require('node:process');
|
|
8
7
|
const runtime = require('@mokup/runtime');
|
|
9
8
|
const node_buffer = require('node:buffer');
|
|
10
9
|
const node_url = require('node:url');
|
|
11
10
|
const esbuild = require('@mokup/shared/esbuild');
|
|
12
11
|
const jsoncParser = require('@mokup/shared/jsonc-parser');
|
|
12
|
+
const hono = require('@mokup/shared/hono');
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
16
16
|
|
|
17
17
|
const process__default = /*#__PURE__*/_interopDefaultCompat(process);
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]);
|
|
28
|
-
const methodSuffixSet = new Set(
|
|
29
|
-
Array.from(methodSet, (method) => method.toLowerCase())
|
|
30
|
-
);
|
|
31
|
-
const supportedExtensions = /* @__PURE__ */ new Set([
|
|
32
|
-
".json",
|
|
33
|
-
".jsonc",
|
|
34
|
-
".ts",
|
|
35
|
-
".js",
|
|
36
|
-
".mjs",
|
|
37
|
-
".cjs"
|
|
38
|
-
]);
|
|
39
|
-
const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
|
|
40
|
-
|
|
41
|
-
function normalizePrefix(prefix) {
|
|
42
|
-
if (!prefix) {
|
|
43
|
-
return "";
|
|
44
|
-
}
|
|
45
|
-
const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
46
|
-
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
47
|
-
}
|
|
48
|
-
function resolveDirs(dir, root) {
|
|
49
|
-
const raw = typeof dir === "function" ? dir(root) : dir;
|
|
50
|
-
const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
|
|
51
|
-
const normalized = resolved.map(
|
|
52
|
-
(entry) => pathe.isAbsolute(entry) ? entry : pathe.resolve(root, entry)
|
|
53
|
-
);
|
|
54
|
-
return Array.from(new Set(normalized));
|
|
55
|
-
}
|
|
56
|
-
function createDebouncer(delayMs, fn) {
|
|
57
|
-
let timer = null;
|
|
58
|
-
return () => {
|
|
59
|
-
if (timer) {
|
|
60
|
-
clearTimeout(timer);
|
|
61
|
-
}
|
|
62
|
-
timer = setTimeout(() => {
|
|
63
|
-
timer = null;
|
|
64
|
-
fn();
|
|
65
|
-
}, delayMs);
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function toPosix(value) {
|
|
69
|
-
return value.replace(/\\/g, "/");
|
|
70
|
-
}
|
|
71
|
-
function isInDirs(file, dirs) {
|
|
72
|
-
const normalized = toPosix(file);
|
|
73
|
-
return dirs.some((dir) => {
|
|
74
|
-
const normalizedDir = toPosix(dir).replace(/\/$/, "");
|
|
75
|
-
return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function testPatterns(patterns, value) {
|
|
79
|
-
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
80
|
-
return list.some((pattern) => pattern.test(value));
|
|
81
|
-
}
|
|
82
|
-
function matchesFilter(file, include, exclude) {
|
|
83
|
-
const normalized = toPosix(file);
|
|
84
|
-
if (exclude && testPatterns(exclude, normalized)) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
if (include) {
|
|
88
|
-
return testPatterns(include, normalized);
|
|
89
|
-
}
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
function normalizeIgnorePrefix(value, fallback = ["."]) {
|
|
93
|
-
const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
|
|
94
|
-
return list.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
95
|
-
}
|
|
96
|
-
function hasIgnoredPrefix(file, rootDir, prefixes) {
|
|
97
|
-
if (prefixes.length === 0) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const relativePath = toPosix(pathe.relative(rootDir, file));
|
|
101
|
-
const segments = relativePath.split("/");
|
|
102
|
-
return segments.some(
|
|
103
|
-
(segment) => prefixes.some((prefix) => segment.startsWith(prefix))
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
function delay(ms) {
|
|
107
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function toHonoPath(route) {
|
|
111
|
-
if (!route.tokens || route.tokens.length === 0) {
|
|
112
|
-
return "/";
|
|
113
|
-
}
|
|
114
|
-
const segments = route.tokens.map((token) => {
|
|
115
|
-
if (token.type === "static") {
|
|
116
|
-
return token.value;
|
|
117
|
-
}
|
|
118
|
-
if (token.type === "param") {
|
|
119
|
-
return `:${token.name}`;
|
|
120
|
-
}
|
|
121
|
-
if (token.type === "catchall") {
|
|
122
|
-
return `:${token.name}{.+}`;
|
|
123
|
-
}
|
|
124
|
-
return `:${token.name}{.+}?`;
|
|
125
|
-
});
|
|
126
|
-
return `/${segments.join("/")}`;
|
|
127
|
-
}
|
|
128
|
-
function isValidStatus(status) {
|
|
129
|
-
return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
|
|
130
|
-
}
|
|
131
|
-
function resolveStatus(routeStatus, responseStatus) {
|
|
132
|
-
if (isValidStatus(routeStatus)) {
|
|
133
|
-
return routeStatus;
|
|
134
|
-
}
|
|
135
|
-
if (isValidStatus(responseStatus)) {
|
|
136
|
-
return responseStatus;
|
|
137
|
-
}
|
|
138
|
-
return 200;
|
|
139
|
-
}
|
|
140
|
-
function applyRouteOverrides(response, route) {
|
|
141
|
-
const headers = new Headers(response.headers);
|
|
142
|
-
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
143
|
-
if (route.headers) {
|
|
144
|
-
for (const [key, value] of Object.entries(route.headers)) {
|
|
145
|
-
headers.set(key, value);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const status = resolveStatus(route.status, response.status);
|
|
149
|
-
if (status === response.status && !hasHeaders) {
|
|
150
|
-
return response;
|
|
151
|
-
}
|
|
152
|
-
return new Response(response.body, { status, headers });
|
|
153
|
-
}
|
|
154
|
-
function resolveResponse(value, fallback) {
|
|
155
|
-
if (value instanceof Response) {
|
|
156
|
-
return value;
|
|
157
|
-
}
|
|
158
|
-
if (value && typeof value === "object" && "res" in value) {
|
|
159
|
-
const resolved = value.res;
|
|
160
|
-
if (resolved instanceof Response) {
|
|
161
|
-
return resolved;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return fallback;
|
|
165
|
-
}
|
|
166
|
-
function normalizeHandlerValue(c, value) {
|
|
167
|
-
if (value instanceof Response) {
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
if (typeof value === "undefined") {
|
|
171
|
-
const response = c.body(null);
|
|
172
|
-
if (response.status === 200) {
|
|
173
|
-
return new Response(response.body, {
|
|
174
|
-
status: 204,
|
|
175
|
-
headers: response.headers
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return response;
|
|
179
|
-
}
|
|
180
|
-
if (typeof value === "string") {
|
|
181
|
-
return c.text(value);
|
|
182
|
-
}
|
|
183
|
-
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
184
|
-
if (!c.res.headers.get("content-type")) {
|
|
185
|
-
c.header("content-type", "application/octet-stream");
|
|
186
|
-
}
|
|
187
|
-
const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
|
|
188
|
-
return c.body(data);
|
|
189
|
-
}
|
|
190
|
-
return c.json(value);
|
|
191
|
-
}
|
|
192
|
-
function createRouteHandler(route) {
|
|
193
|
-
return async (c) => {
|
|
194
|
-
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
195
|
-
return normalizeHandlerValue(c, value);
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function createFinalizeMiddleware(route, onResponse) {
|
|
199
|
-
return async (c, next) => {
|
|
200
|
-
const response = await next();
|
|
201
|
-
const resolved = resolveResponse(response, c.res);
|
|
202
|
-
if (route.delay && route.delay > 0) {
|
|
203
|
-
await delay(route.delay);
|
|
204
|
-
}
|
|
205
|
-
const overridden = applyRouteOverrides(resolved, route);
|
|
206
|
-
c.res = overridden;
|
|
207
|
-
if (onResponse) {
|
|
208
|
-
try {
|
|
209
|
-
const result = onResponse(route, overridden);
|
|
210
|
-
if (result instanceof Promise) {
|
|
211
|
-
result.catch(() => void 0);
|
|
212
|
-
}
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return overridden;
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function wrapMiddleware(handler) {
|
|
220
|
-
return async (c, next) => {
|
|
221
|
-
const response = await handler(c, next);
|
|
222
|
-
return resolveResponse(response, c.res);
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
function createHonoApp(routes, options = {}) {
|
|
226
|
-
const app = new hono.Hono({ router: new hono.PatternRouter(), strict: false });
|
|
227
|
-
for (const route of routes) {
|
|
228
|
-
const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
|
|
229
|
-
app.on(
|
|
230
|
-
route.method,
|
|
231
|
-
toHonoPath(route),
|
|
232
|
-
createFinalizeMiddleware(route, options.onResponse),
|
|
233
|
-
...middlewares,
|
|
234
|
-
createRouteHandler(route)
|
|
235
|
-
);
|
|
19
|
+
const silentLogger$1 = {
|
|
20
|
+
info: () => {
|
|
21
|
+
},
|
|
22
|
+
warn: () => {
|
|
23
|
+
},
|
|
24
|
+
error: () => {
|
|
25
|
+
},
|
|
26
|
+
log: () => {
|
|
236
27
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
28
|
+
};
|
|
29
|
+
function createLogger(options = true) {
|
|
30
|
+
const resolvedOptions = typeof options === "boolean" ? { enabled: options } : options;
|
|
31
|
+
const enabled = resolvedOptions?.enabled ?? true;
|
|
32
|
+
if (!enabled) {
|
|
33
|
+
return silentLogger$1;
|
|
34
|
+
}
|
|
35
|
+
const tag = resolvedOptions?.tag ?? "mokup";
|
|
36
|
+
const prefix = tag ? `[${tag}]` : "";
|
|
37
|
+
const withTag = (args) => prefix ? [prefix, ...args] : args;
|
|
241
38
|
return {
|
|
242
39
|
info: (...args) => {
|
|
243
|
-
if (
|
|
244
|
-
|
|
40
|
+
if (args.length === 0) {
|
|
41
|
+
return;
|
|
245
42
|
}
|
|
43
|
+
console.info(...withTag(args));
|
|
246
44
|
},
|
|
247
45
|
warn: (...args) => {
|
|
248
|
-
if (
|
|
249
|
-
|
|
46
|
+
if (args.length === 0) {
|
|
47
|
+
return;
|
|
250
48
|
}
|
|
49
|
+
console.warn(...withTag(args));
|
|
251
50
|
},
|
|
252
51
|
error: (...args) => {
|
|
253
|
-
if (
|
|
254
|
-
|
|
52
|
+
if (args.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.error(...withTag(args));
|
|
56
|
+
},
|
|
57
|
+
log: (...args) => {
|
|
58
|
+
if (args.length === 0) {
|
|
59
|
+
return;
|
|
255
60
|
}
|
|
61
|
+
console.log(...withTag(args));
|
|
256
62
|
}
|
|
257
63
|
};
|
|
258
64
|
}
|
|
259
65
|
|
|
260
|
-
const mimeTypes = {
|
|
261
|
-
".html": "text/html; charset=utf-8",
|
|
262
|
-
".css": "text/css; charset=utf-8",
|
|
263
|
-
".js": "text/javascript; charset=utf-8",
|
|
264
|
-
".map": "application/json; charset=utf-8",
|
|
265
|
-
".json": "application/json; charset=utf-8",
|
|
266
|
-
".svg": "image/svg+xml",
|
|
267
|
-
".png": "image/png",
|
|
268
|
-
".jpg": "image/jpeg",
|
|
269
|
-
".jpeg": "image/jpeg",
|
|
270
|
-
".ico": "image/x-icon"
|
|
271
|
-
};
|
|
272
66
|
function normalizePlaygroundPath(value) {
|
|
273
67
|
if (!value) {
|
|
274
|
-
return "/
|
|
68
|
+
return "/__mokup";
|
|
275
69
|
}
|
|
276
70
|
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
277
71
|
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
278
72
|
}
|
|
279
73
|
function resolvePlaygroundOptions(playground) {
|
|
280
74
|
if (playground === false) {
|
|
281
|
-
return { enabled: false, path: "/
|
|
75
|
+
return { enabled: false, path: "/__mokup" };
|
|
282
76
|
}
|
|
283
77
|
if (playground && typeof playground === "object") {
|
|
284
78
|
return {
|
|
@@ -286,13 +80,43 @@ function resolvePlaygroundOptions(playground) {
|
|
|
286
80
|
path: normalizePlaygroundPath(playground.path)
|
|
287
81
|
};
|
|
288
82
|
}
|
|
289
|
-
return { enabled: true, path: "/
|
|
83
|
+
return { enabled: true, path: "/__mokup" };
|
|
290
84
|
}
|
|
85
|
+
|
|
86
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('fetch-server.cjs', document.baseURI).href)));
|
|
87
|
+
const mimeTypes = {
|
|
88
|
+
".html": "text/html; charset=utf-8",
|
|
89
|
+
".css": "text/css; charset=utf-8",
|
|
90
|
+
".js": "text/javascript; charset=utf-8",
|
|
91
|
+
".map": "application/json; charset=utf-8",
|
|
92
|
+
".json": "application/json; charset=utf-8",
|
|
93
|
+
".svg": "image/svg+xml",
|
|
94
|
+
".png": "image/png",
|
|
95
|
+
".jpg": "image/jpeg",
|
|
96
|
+
".jpeg": "image/jpeg",
|
|
97
|
+
".ico": "image/x-icon"
|
|
98
|
+
};
|
|
291
99
|
function resolvePlaygroundDist() {
|
|
292
|
-
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('fetch-server.cjs', document.baseURI).href)));
|
|
293
100
|
const pkgPath = require$1.resolve("@mokup/playground/package.json");
|
|
294
101
|
return pathe.join(pkgPath, "..", "dist");
|
|
295
102
|
}
|
|
103
|
+
async function readPlaygroundIndex(indexPath) {
|
|
104
|
+
const html = await node_fs.promises.readFile(indexPath, "utf8");
|
|
105
|
+
const contentType = mimeTypes[".html"];
|
|
106
|
+
return new Response(html, { headers: { "Content-Type": contentType } });
|
|
107
|
+
}
|
|
108
|
+
async function readPlaygroundAsset(distDir, relPath) {
|
|
109
|
+
if (relPath.includes("..")) {
|
|
110
|
+
return new Response("Invalid path.", { status: 400 });
|
|
111
|
+
}
|
|
112
|
+
const normalizedPath = pathe.normalize(relPath);
|
|
113
|
+
const filePath = pathe.join(distDir, normalizedPath);
|
|
114
|
+
const content = await node_fs.promises.readFile(filePath);
|
|
115
|
+
const ext = pathe.extname(filePath);
|
|
116
|
+
const contentType = mimeTypes[ext] ?? "application/octet-stream";
|
|
117
|
+
return new Response(content, { headers: { "Content-Type": contentType } });
|
|
118
|
+
}
|
|
119
|
+
|
|
296
120
|
function toPosixPath(value) {
|
|
297
121
|
return value.replace(/\\/g, "/");
|
|
298
122
|
}
|
|
@@ -334,41 +158,6 @@ function resolveGroupRoot(dirs, serverRoot) {
|
|
|
334
158
|
}
|
|
335
159
|
return common;
|
|
336
160
|
}
|
|
337
|
-
const disabledReasonSet = /* @__PURE__ */ new Set([
|
|
338
|
-
"disabled",
|
|
339
|
-
"disabled-dir",
|
|
340
|
-
"exclude",
|
|
341
|
-
"ignore-prefix",
|
|
342
|
-
"include",
|
|
343
|
-
"unknown"
|
|
344
|
-
]);
|
|
345
|
-
const ignoredReasonSet = /* @__PURE__ */ new Set([
|
|
346
|
-
"unsupported",
|
|
347
|
-
"invalid-route",
|
|
348
|
-
"unknown"
|
|
349
|
-
]);
|
|
350
|
-
function normalizeDisabledReason(reason) {
|
|
351
|
-
if (reason && disabledReasonSet.has(reason)) {
|
|
352
|
-
return reason;
|
|
353
|
-
}
|
|
354
|
-
return "unknown";
|
|
355
|
-
}
|
|
356
|
-
function normalizeIgnoredReason(reason) {
|
|
357
|
-
if (reason && ignoredReasonSet.has(reason)) {
|
|
358
|
-
return reason;
|
|
359
|
-
}
|
|
360
|
-
return "unknown";
|
|
361
|
-
}
|
|
362
|
-
function formatRouteFile(file, root) {
|
|
363
|
-
if (!root) {
|
|
364
|
-
return toPosixPath(file);
|
|
365
|
-
}
|
|
366
|
-
const rel = toPosixPath(pathe.relative(root, file));
|
|
367
|
-
if (!rel || rel.startsWith("..")) {
|
|
368
|
-
return toPosixPath(file);
|
|
369
|
-
}
|
|
370
|
-
return rel;
|
|
371
|
-
}
|
|
372
161
|
function resolveGroups(dirs, root) {
|
|
373
162
|
const groups = [];
|
|
374
163
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -401,11 +190,54 @@ function resolveRouteGroup(routeFile, groups) {
|
|
|
401
190
|
}
|
|
402
191
|
}
|
|
403
192
|
}
|
|
404
|
-
return matched;
|
|
193
|
+
return matched;
|
|
194
|
+
}
|
|
195
|
+
function formatRouteFile(file, root) {
|
|
196
|
+
if (!root) {
|
|
197
|
+
return toPosixPath(file);
|
|
198
|
+
}
|
|
199
|
+
const rel = toPosixPath(pathe.relative(root, file));
|
|
200
|
+
if (!rel || rel.startsWith("..")) {
|
|
201
|
+
return toPosixPath(file);
|
|
202
|
+
}
|
|
203
|
+
return rel;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const disabledReasonSet = /* @__PURE__ */ new Set([
|
|
207
|
+
"disabled",
|
|
208
|
+
"disabled-dir",
|
|
209
|
+
"exclude",
|
|
210
|
+
"ignore-prefix",
|
|
211
|
+
"include",
|
|
212
|
+
"unknown"
|
|
213
|
+
]);
|
|
214
|
+
const ignoredReasonSet = /* @__PURE__ */ new Set([
|
|
215
|
+
"unsupported",
|
|
216
|
+
"invalid-route",
|
|
217
|
+
"unknown"
|
|
218
|
+
]);
|
|
219
|
+
function normalizeDisabledReason(reason) {
|
|
220
|
+
if (reason && disabledReasonSet.has(reason)) {
|
|
221
|
+
return reason;
|
|
222
|
+
}
|
|
223
|
+
return "unknown";
|
|
224
|
+
}
|
|
225
|
+
function normalizeIgnoredReason(reason) {
|
|
226
|
+
if (reason && ignoredReasonSet.has(reason)) {
|
|
227
|
+
return reason;
|
|
228
|
+
}
|
|
229
|
+
return "unknown";
|
|
405
230
|
}
|
|
406
231
|
function toPlaygroundRoute(route, root, groups) {
|
|
407
232
|
const matchedGroup = resolveRouteGroup(route.file, groups);
|
|
408
|
-
const
|
|
233
|
+
const preSources = route.middlewares?.filter((entry) => entry.position === "pre").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
234
|
+
const postSources = route.middlewares?.filter((entry) => entry.position === "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
235
|
+
const normalSources = route.middlewares?.filter((entry) => entry.position !== "pre" && entry.position !== "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
236
|
+
const combinedSources = [
|
|
237
|
+
...preSources,
|
|
238
|
+
...normalSources,
|
|
239
|
+
...postSources
|
|
240
|
+
];
|
|
409
241
|
return {
|
|
410
242
|
method: route.method,
|
|
411
243
|
url: route.template,
|
|
@@ -413,8 +245,14 @@ function toPlaygroundRoute(route, root, groups) {
|
|
|
413
245
|
type: typeof route.handler === "function" ? "handler" : "static",
|
|
414
246
|
status: route.status,
|
|
415
247
|
delay: route.delay,
|
|
416
|
-
middlewareCount:
|
|
417
|
-
middlewares:
|
|
248
|
+
middlewareCount: combinedSources.length,
|
|
249
|
+
middlewares: combinedSources,
|
|
250
|
+
preMiddlewareCount: preSources.length,
|
|
251
|
+
normalMiddlewareCount: normalSources.length,
|
|
252
|
+
postMiddlewareCount: postSources.length,
|
|
253
|
+
preMiddlewares: preSources,
|
|
254
|
+
normalMiddlewares: normalSources,
|
|
255
|
+
postMiddlewares: postSources,
|
|
418
256
|
groupKey: matchedGroup?.key,
|
|
419
257
|
group: matchedGroup?.label
|
|
420
258
|
};
|
|
@@ -460,6 +298,7 @@ function toPlaygroundConfigFile(entry, root, groups) {
|
|
|
460
298
|
}
|
|
461
299
|
return configFile;
|
|
462
300
|
}
|
|
301
|
+
|
|
463
302
|
function registerPlaygroundRoutes(params) {
|
|
464
303
|
if (!params.config.enabled) {
|
|
465
304
|
return;
|
|
@@ -469,9 +308,7 @@ function registerPlaygroundRoutes(params) {
|
|
|
469
308
|
const indexPath = pathe.join(distDir, "index.html");
|
|
470
309
|
const serveIndex = async () => {
|
|
471
310
|
try {
|
|
472
|
-
|
|
473
|
-
const contentType = mimeTypes[".html"] ?? "text/html; charset=utf-8";
|
|
474
|
-
return new Response(html, { headers: { "Content-Type": contentType } });
|
|
311
|
+
return await readPlaygroundIndex(indexPath);
|
|
475
312
|
} catch (error) {
|
|
476
313
|
params.logger.error("Failed to load playground index:", error);
|
|
477
314
|
return new Response("Playground is not available.", { status: 500 });
|
|
@@ -512,22 +349,105 @@ function registerPlaygroundRoutes(params) {
|
|
|
512
349
|
if (!relPath || relPath === "/") {
|
|
513
350
|
return serveIndex();
|
|
514
351
|
}
|
|
515
|
-
if (relPath.includes("..")) {
|
|
516
|
-
return new Response("Invalid path.", { status: 400 });
|
|
517
|
-
}
|
|
518
|
-
const normalizedPath = pathe.normalize(relPath);
|
|
519
|
-
const filePath = pathe.join(distDir, normalizedPath);
|
|
520
352
|
try {
|
|
521
|
-
|
|
522
|
-
const ext = pathe.extname(filePath);
|
|
523
|
-
const contentType = mimeTypes[ext] ?? "application/octet-stream";
|
|
524
|
-
return new Response(content, { headers: { "Content-Type": contentType } });
|
|
353
|
+
return await readPlaygroundAsset(distDir, relPath);
|
|
525
354
|
} catch {
|
|
526
355
|
return c.notFound();
|
|
527
356
|
}
|
|
528
357
|
});
|
|
529
358
|
}
|
|
530
359
|
|
|
360
|
+
const methodSet = /* @__PURE__ */ new Set([
|
|
361
|
+
"GET",
|
|
362
|
+
"POST",
|
|
363
|
+
"PUT",
|
|
364
|
+
"PATCH",
|
|
365
|
+
"DELETE",
|
|
366
|
+
"OPTIONS",
|
|
367
|
+
"HEAD"
|
|
368
|
+
]);
|
|
369
|
+
const methodSuffixSet = new Set(
|
|
370
|
+
Array.from(methodSet, (method) => method.toLowerCase())
|
|
371
|
+
);
|
|
372
|
+
const supportedExtensions = /* @__PURE__ */ new Set([
|
|
373
|
+
".json",
|
|
374
|
+
".jsonc",
|
|
375
|
+
".ts",
|
|
376
|
+
".js",
|
|
377
|
+
".mjs",
|
|
378
|
+
".cjs"
|
|
379
|
+
]);
|
|
380
|
+
const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
|
|
381
|
+
|
|
382
|
+
function normalizePrefix(prefix) {
|
|
383
|
+
if (!prefix) {
|
|
384
|
+
return "";
|
|
385
|
+
}
|
|
386
|
+
const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
387
|
+
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
388
|
+
}
|
|
389
|
+
function resolveDirs(dir, root) {
|
|
390
|
+
const raw = typeof dir === "function" ? dir(root) : dir;
|
|
391
|
+
const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
|
|
392
|
+
const normalized = resolved.map(
|
|
393
|
+
(entry) => pathe.isAbsolute(entry) ? entry : pathe.resolve(root, entry)
|
|
394
|
+
);
|
|
395
|
+
return Array.from(new Set(normalized));
|
|
396
|
+
}
|
|
397
|
+
function createDebouncer(delayMs, fn) {
|
|
398
|
+
let timer = null;
|
|
399
|
+
return () => {
|
|
400
|
+
if (timer) {
|
|
401
|
+
clearTimeout(timer);
|
|
402
|
+
}
|
|
403
|
+
timer = setTimeout(() => {
|
|
404
|
+
timer = null;
|
|
405
|
+
fn();
|
|
406
|
+
}, delayMs);
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function toPosix(value) {
|
|
410
|
+
return value.replace(/\\/g, "/");
|
|
411
|
+
}
|
|
412
|
+
function isInDirs(file, dirs) {
|
|
413
|
+
const normalized = toPosix(file);
|
|
414
|
+
return dirs.some((dir) => {
|
|
415
|
+
const normalizedDir = toPosix(dir).replace(/\/$/, "");
|
|
416
|
+
return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function testPatterns(patterns, value) {
|
|
420
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
421
|
+
return list.some((pattern) => pattern.test(value));
|
|
422
|
+
}
|
|
423
|
+
function matchesFilter(file, include, exclude) {
|
|
424
|
+
const normalized = toPosix(file);
|
|
425
|
+
if (exclude && testPatterns(exclude, normalized)) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (include) {
|
|
429
|
+
return testPatterns(include, normalized);
|
|
430
|
+
}
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
function normalizeIgnorePrefix(value, fallback = ["."]) {
|
|
434
|
+
const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
|
|
435
|
+
return list.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
436
|
+
}
|
|
437
|
+
function hasIgnoredPrefix(file, rootDir, prefixes) {
|
|
438
|
+
if (prefixes.length === 0) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
const relativePath = toPosix(pathe.relative(rootDir, file));
|
|
442
|
+
const segments = relativePath.split("/");
|
|
443
|
+
return segments.some(
|
|
444
|
+
(segment) => prefixes.some((prefix) => segment.startsWith(prefix))
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
function delay(ms) {
|
|
448
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
449
|
+
}
|
|
450
|
+
|
|
531
451
|
const jsonExtensions = /* @__PURE__ */ new Set([".json", ".jsonc"]);
|
|
532
452
|
function resolveTemplate(template, prefix) {
|
|
533
453
|
const normalized = template.startsWith("/") ? template : `/${template}`;
|
|
@@ -680,6 +600,7 @@ async function ensureTsxRegister(logger) {
|
|
|
680
600
|
return registerPromise;
|
|
681
601
|
}
|
|
682
602
|
|
|
603
|
+
const middlewareSymbol = Symbol.for("mokup.config.middlewares");
|
|
683
604
|
function isUnknownFileExtensionError$1(error) {
|
|
684
605
|
if (!error || typeof error !== "object") {
|
|
685
606
|
return false;
|
|
@@ -766,7 +687,7 @@ async function loadConfig(file, logger) {
|
|
|
766
687
|
}
|
|
767
688
|
return value;
|
|
768
689
|
}
|
|
769
|
-
function normalizeMiddlewares(value, source, logger) {
|
|
690
|
+
function normalizeMiddlewares(value, source, logger, position) {
|
|
770
691
|
if (!value) {
|
|
771
692
|
return [];
|
|
772
693
|
}
|
|
@@ -777,10 +698,27 @@ function normalizeMiddlewares(value, source, logger) {
|
|
|
777
698
|
logger.warn(`Invalid middleware in ${source}`);
|
|
778
699
|
continue;
|
|
779
700
|
}
|
|
780
|
-
middlewares.push({
|
|
701
|
+
middlewares.push({
|
|
702
|
+
handle: entry,
|
|
703
|
+
source,
|
|
704
|
+
index,
|
|
705
|
+
position
|
|
706
|
+
});
|
|
781
707
|
}
|
|
782
708
|
return middlewares;
|
|
783
709
|
}
|
|
710
|
+
function readMiddlewareMeta(config) {
|
|
711
|
+
const value = config[middlewareSymbol];
|
|
712
|
+
if (!value || typeof value !== "object") {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const meta = value;
|
|
716
|
+
return {
|
|
717
|
+
pre: Array.isArray(meta.pre) ? meta.pre : [],
|
|
718
|
+
normal: Array.isArray(meta.normal) ? meta.normal : [],
|
|
719
|
+
post: Array.isArray(meta.post) ? meta.post : []
|
|
720
|
+
};
|
|
721
|
+
}
|
|
784
722
|
async function resolveDirectoryConfig(params) {
|
|
785
723
|
const { file, rootDir, logger, configCache, fileCache } = params;
|
|
786
724
|
const resolvedRoot = pathe.normalize(rootDir);
|
|
@@ -799,7 +737,10 @@ async function resolveDirectoryConfig(params) {
|
|
|
799
737
|
current = parent;
|
|
800
738
|
}
|
|
801
739
|
chain.reverse();
|
|
802
|
-
const merged = {
|
|
740
|
+
const merged = {};
|
|
741
|
+
const preMiddlewares = [];
|
|
742
|
+
const normalMiddlewares = [];
|
|
743
|
+
const postMiddlewares = [];
|
|
803
744
|
for (const dir of chain) {
|
|
804
745
|
const configPath = await findConfigFile(dir, fileCache);
|
|
805
746
|
if (!configPath) {
|
|
@@ -835,12 +776,48 @@ async function resolveDirectoryConfig(params) {
|
|
|
835
776
|
if (typeof config.exclude !== "undefined") {
|
|
836
777
|
merged.exclude = config.exclude;
|
|
837
778
|
}
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
779
|
+
const meta = readMiddlewareMeta(config);
|
|
780
|
+
const normalizedPre = normalizeMiddlewares(
|
|
781
|
+
meta?.pre,
|
|
782
|
+
configPath,
|
|
783
|
+
logger,
|
|
784
|
+
"pre"
|
|
785
|
+
);
|
|
786
|
+
const normalizedNormal = normalizeMiddlewares(
|
|
787
|
+
meta?.normal,
|
|
788
|
+
configPath,
|
|
789
|
+
logger,
|
|
790
|
+
"normal"
|
|
791
|
+
);
|
|
792
|
+
const normalizedLegacy = normalizeMiddlewares(
|
|
793
|
+
config.middleware,
|
|
794
|
+
configPath,
|
|
795
|
+
logger,
|
|
796
|
+
"normal"
|
|
797
|
+
);
|
|
798
|
+
const normalizedPost = normalizeMiddlewares(
|
|
799
|
+
meta?.post,
|
|
800
|
+
configPath,
|
|
801
|
+
logger,
|
|
802
|
+
"post"
|
|
803
|
+
);
|
|
804
|
+
if (normalizedPre.length > 0) {
|
|
805
|
+
preMiddlewares.push(...normalizedPre);
|
|
806
|
+
}
|
|
807
|
+
if (normalizedNormal.length > 0) {
|
|
808
|
+
normalMiddlewares.push(...normalizedNormal);
|
|
809
|
+
}
|
|
810
|
+
if (normalizedLegacy.length > 0) {
|
|
811
|
+
normalMiddlewares.push(...normalizedLegacy);
|
|
812
|
+
}
|
|
813
|
+
if (normalizedPost.length > 0) {
|
|
814
|
+
postMiddlewares.push(...normalizedPost);
|
|
841
815
|
}
|
|
842
816
|
}
|
|
843
|
-
return
|
|
817
|
+
return {
|
|
818
|
+
...merged,
|
|
819
|
+
middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares]
|
|
820
|
+
};
|
|
844
821
|
}
|
|
845
822
|
|
|
846
823
|
async function walkDir(dir, rootDir, files) {
|
|
@@ -1009,6 +986,8 @@ const silentLogger = {
|
|
|
1009
986
|
warn: () => {
|
|
1010
987
|
},
|
|
1011
988
|
error: () => {
|
|
989
|
+
},
|
|
990
|
+
log: () => {
|
|
1012
991
|
}
|
|
1013
992
|
};
|
|
1014
993
|
function resolveSkipRoute(params) {
|
|
@@ -1136,55 +1115,228 @@ async function scanRoutes(params) {
|
|
|
1136
1115
|
});
|
|
1137
1116
|
params.onSkip?.(buildSkipInfo(fileInfo.file, "disabled", resolved2));
|
|
1138
1117
|
}
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
const ruleValue = rule;
|
|
1142
|
-
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
1143
|
-
(key2) => key2 in ruleValue
|
|
1144
|
-
);
|
|
1145
|
-
if (unsupportedKeys.length > 0) {
|
|
1146
|
-
params.logger.warn(
|
|
1147
|
-
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
1148
|
-
);
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
if (typeof rule.handler === "undefined") {
|
|
1152
|
-
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
|
-
const resolved = resolveRule({
|
|
1156
|
-
rule,
|
|
1157
|
-
derivedTemplate: derived.template,
|
|
1158
|
-
derivedMethod: derived.method,
|
|
1159
|
-
prefix: params.prefix,
|
|
1160
|
-
file: fileInfo.file,
|
|
1161
|
-
logger: params.logger
|
|
1162
|
-
});
|
|
1163
|
-
if (!resolved) {
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
resolved.ruleIndex = index;
|
|
1167
|
-
if (config.headers) {
|
|
1168
|
-
resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
|
|
1169
|
-
}
|
|
1170
|
-
if (typeof resolved.status === "undefined" && typeof config.status === "number") {
|
|
1171
|
-
resolved.status = config.status;
|
|
1172
|
-
}
|
|
1173
|
-
if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
|
|
1174
|
-
resolved.delay = config.delay;
|
|
1175
|
-
}
|
|
1176
|
-
if (config.middlewares.length > 0) {
|
|
1177
|
-
resolved.middlewares = config.middlewares;
|
|
1178
|
-
}
|
|
1179
|
-
const key = `${resolved.method} ${resolved.template}`;
|
|
1180
|
-
if (seen.has(key)) {
|
|
1181
|
-
params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const ruleValue = rule;
|
|
1121
|
+
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
1122
|
+
(key2) => key2 in ruleValue
|
|
1123
|
+
);
|
|
1124
|
+
if (unsupportedKeys.length > 0) {
|
|
1125
|
+
params.logger.warn(
|
|
1126
|
+
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
1127
|
+
);
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (typeof rule.handler === "undefined") {
|
|
1131
|
+
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const resolved = resolveRule({
|
|
1135
|
+
rule,
|
|
1136
|
+
derivedTemplate: derived.template,
|
|
1137
|
+
derivedMethod: derived.method,
|
|
1138
|
+
prefix: params.prefix,
|
|
1139
|
+
file: fileInfo.file,
|
|
1140
|
+
logger: params.logger
|
|
1141
|
+
});
|
|
1142
|
+
if (!resolved) {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
resolved.ruleIndex = index;
|
|
1146
|
+
if (config.headers) {
|
|
1147
|
+
resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
|
|
1148
|
+
}
|
|
1149
|
+
if (typeof resolved.status === "undefined" && typeof config.status === "number") {
|
|
1150
|
+
resolved.status = config.status;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
|
|
1153
|
+
resolved.delay = config.delay;
|
|
1154
|
+
}
|
|
1155
|
+
if (config.middlewares.length > 0) {
|
|
1156
|
+
resolved.middlewares = config.middlewares;
|
|
1157
|
+
}
|
|
1158
|
+
const key = `${resolved.method} ${resolved.template}`;
|
|
1159
|
+
if (seen.has(key)) {
|
|
1160
|
+
params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
|
|
1161
|
+
}
|
|
1162
|
+
seen.add(key);
|
|
1163
|
+
routes.push(resolved);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return sortRoutes(routes);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function toHonoPath(route) {
|
|
1170
|
+
if (!route.tokens || route.tokens.length === 0) {
|
|
1171
|
+
return "/";
|
|
1172
|
+
}
|
|
1173
|
+
const segments = route.tokens.map((token) => {
|
|
1174
|
+
if (token.type === "static") {
|
|
1175
|
+
return token.value;
|
|
1176
|
+
}
|
|
1177
|
+
if (token.type === "param") {
|
|
1178
|
+
return `:${token.name}`;
|
|
1179
|
+
}
|
|
1180
|
+
if (token.type === "catchall") {
|
|
1181
|
+
return `:${token.name}{.+}`;
|
|
1182
|
+
}
|
|
1183
|
+
return `:${token.name}{.+}?`;
|
|
1184
|
+
});
|
|
1185
|
+
return `/${segments.join("/")}`;
|
|
1186
|
+
}
|
|
1187
|
+
function isValidStatus(status) {
|
|
1188
|
+
return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
|
|
1189
|
+
}
|
|
1190
|
+
function resolveStatus(routeStatus, responseStatus) {
|
|
1191
|
+
if (isValidStatus(routeStatus)) {
|
|
1192
|
+
return routeStatus;
|
|
1193
|
+
}
|
|
1194
|
+
if (isValidStatus(responseStatus)) {
|
|
1195
|
+
return responseStatus;
|
|
1196
|
+
}
|
|
1197
|
+
return 200;
|
|
1198
|
+
}
|
|
1199
|
+
function applyRouteOverrides(response, route) {
|
|
1200
|
+
const headers = new Headers(response.headers);
|
|
1201
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
1202
|
+
if (route.headers) {
|
|
1203
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
1204
|
+
headers.set(key, value);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const status = resolveStatus(route.status, response.status);
|
|
1208
|
+
if (status === response.status && !hasHeaders) {
|
|
1209
|
+
return response;
|
|
1210
|
+
}
|
|
1211
|
+
return new Response(response.body, { status, headers });
|
|
1212
|
+
}
|
|
1213
|
+
function resolveResponse(value, fallback) {
|
|
1214
|
+
if (value instanceof Response) {
|
|
1215
|
+
return value;
|
|
1216
|
+
}
|
|
1217
|
+
if (value && typeof value === "object" && "res" in value) {
|
|
1218
|
+
const resolved = value.res;
|
|
1219
|
+
if (resolved instanceof Response) {
|
|
1220
|
+
return resolved;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return fallback;
|
|
1224
|
+
}
|
|
1225
|
+
function normalizeHandlerValue(c, value) {
|
|
1226
|
+
if (value instanceof Response) {
|
|
1227
|
+
return value;
|
|
1228
|
+
}
|
|
1229
|
+
if (typeof value === "undefined") {
|
|
1230
|
+
const response = c.body(null);
|
|
1231
|
+
if (response.status === 200) {
|
|
1232
|
+
return new Response(response.body, {
|
|
1233
|
+
status: 204,
|
|
1234
|
+
headers: response.headers
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
return response;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof value === "string") {
|
|
1240
|
+
return c.text(value);
|
|
1241
|
+
}
|
|
1242
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
1243
|
+
if (!c.res.headers.get("content-type")) {
|
|
1244
|
+
c.header("content-type", "application/octet-stream");
|
|
1245
|
+
}
|
|
1246
|
+
const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
|
|
1247
|
+
return c.body(data);
|
|
1248
|
+
}
|
|
1249
|
+
return c.json(value);
|
|
1250
|
+
}
|
|
1251
|
+
function createRouteHandler(route) {
|
|
1252
|
+
return async (c) => {
|
|
1253
|
+
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
1254
|
+
return normalizeHandlerValue(c, value);
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
function createFinalizeMiddleware(route, onResponse) {
|
|
1258
|
+
return async (c, next) => {
|
|
1259
|
+
const response = await next();
|
|
1260
|
+
const resolved = resolveResponse(response, c.res);
|
|
1261
|
+
if (route.delay && route.delay > 0) {
|
|
1262
|
+
await delay(route.delay);
|
|
1263
|
+
}
|
|
1264
|
+
const overridden = applyRouteOverrides(resolved, route);
|
|
1265
|
+
c.res = overridden;
|
|
1266
|
+
if (onResponse) {
|
|
1267
|
+
try {
|
|
1268
|
+
const result = onResponse(route, overridden);
|
|
1269
|
+
if (result instanceof Promise) {
|
|
1270
|
+
result.catch(() => void 0);
|
|
1271
|
+
}
|
|
1272
|
+
} catch {
|
|
1182
1273
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1274
|
+
}
|
|
1275
|
+
return overridden;
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function wrapMiddleware(handler) {
|
|
1279
|
+
return async (c, next) => {
|
|
1280
|
+
const response = await handler(c, next);
|
|
1281
|
+
return resolveResponse(response, c.res);
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function splitRouteMiddlewares(route) {
|
|
1285
|
+
const before = [];
|
|
1286
|
+
const normal = [];
|
|
1287
|
+
const after = [];
|
|
1288
|
+
for (const entry of route.middlewares ?? []) {
|
|
1289
|
+
const wrapped = wrapMiddleware(entry.handle);
|
|
1290
|
+
if (entry.position === "post") {
|
|
1291
|
+
after.push(wrapped);
|
|
1292
|
+
} else if (entry.position === "pre") {
|
|
1293
|
+
before.push(wrapped);
|
|
1294
|
+
} else {
|
|
1295
|
+
normal.push(wrapped);
|
|
1185
1296
|
}
|
|
1186
1297
|
}
|
|
1187
|
-
return
|
|
1298
|
+
return { before, normal, after };
|
|
1299
|
+
}
|
|
1300
|
+
function createHonoApp(routes, options = {}) {
|
|
1301
|
+
const app = new hono.Hono({ router: new hono.PatternRouter(), strict: false });
|
|
1302
|
+
for (const route of routes) {
|
|
1303
|
+
const { before, normal, after } = splitRouteMiddlewares(route);
|
|
1304
|
+
app.on(
|
|
1305
|
+
route.method,
|
|
1306
|
+
toHonoPath(route),
|
|
1307
|
+
createFinalizeMiddleware(route, options.onResponse),
|
|
1308
|
+
...before,
|
|
1309
|
+
...normal,
|
|
1310
|
+
...after,
|
|
1311
|
+
createRouteHandler(route)
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
return app;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function buildFetchServerApp(params) {
|
|
1318
|
+
const app = new hono.Hono({ strict: false });
|
|
1319
|
+
registerPlaygroundRoutes({
|
|
1320
|
+
app,
|
|
1321
|
+
routes: params.routes,
|
|
1322
|
+
disabledRoutes: params.disabledRoutes,
|
|
1323
|
+
ignoredRoutes: params.ignoredRoutes,
|
|
1324
|
+
configFiles: params.configFiles,
|
|
1325
|
+
disabledConfigFiles: params.disabledConfigFiles,
|
|
1326
|
+
dirs: params.dirs,
|
|
1327
|
+
logger: params.logger,
|
|
1328
|
+
config: params.playground,
|
|
1329
|
+
root: params.root
|
|
1330
|
+
});
|
|
1331
|
+
if (params.wsHandler && params.playground.enabled) {
|
|
1332
|
+
app.get(`${params.playground.path}/ws`, params.wsHandler);
|
|
1333
|
+
}
|
|
1334
|
+
if (params.routes.length > 0) {
|
|
1335
|
+
const mockAppOptions = params.onResponse ? { onResponse: params.onResponse } : {};
|
|
1336
|
+
const mockApp = createHonoApp(params.routes, mockAppOptions);
|
|
1337
|
+
app.route("/", mockApp);
|
|
1338
|
+
}
|
|
1339
|
+
return app;
|
|
1188
1340
|
}
|
|
1189
1341
|
|
|
1190
1342
|
function normalizeEntries(entries) {
|
|
@@ -1231,30 +1383,82 @@ function resolveAllDirs(list, root) {
|
|
|
1231
1383
|
}
|
|
1232
1384
|
return dirs;
|
|
1233
1385
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
dirs: params.dirs,
|
|
1244
|
-
logger: params.logger,
|
|
1245
|
-
config: params.playground,
|
|
1246
|
-
root: params.root
|
|
1247
|
-
});
|
|
1248
|
-
if (params.wsHandler && params.playground.enabled) {
|
|
1249
|
-
app.get(`${params.playground.path}/ws`, params.wsHandler);
|
|
1386
|
+
|
|
1387
|
+
function createPlaygroundWs(playground) {
|
|
1388
|
+
const routeCounts = {};
|
|
1389
|
+
const wsClients = /* @__PURE__ */ new Set();
|
|
1390
|
+
let totalCount = 0;
|
|
1391
|
+
let wsHandler;
|
|
1392
|
+
let injectWebSocket;
|
|
1393
|
+
function getRouteKey(route) {
|
|
1394
|
+
return `${route.method} ${route.template}`;
|
|
1250
1395
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1396
|
+
function buildSnapshot() {
|
|
1397
|
+
return {
|
|
1398
|
+
type: "snapshot",
|
|
1399
|
+
total: totalCount,
|
|
1400
|
+
perRoute: { ...routeCounts }
|
|
1401
|
+
};
|
|
1255
1402
|
}
|
|
1256
|
-
|
|
1403
|
+
function broadcast(payload) {
|
|
1404
|
+
if (wsClients.size === 0) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const message = JSON.stringify(payload);
|
|
1408
|
+
for (const client of wsClients) {
|
|
1409
|
+
try {
|
|
1410
|
+
client.send(message);
|
|
1411
|
+
} catch {
|
|
1412
|
+
wsClients.delete(client);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
function registerWsClient(client) {
|
|
1417
|
+
wsClients.add(client);
|
|
1418
|
+
try {
|
|
1419
|
+
client.send(JSON.stringify(buildSnapshot()));
|
|
1420
|
+
} catch {
|
|
1421
|
+
wsClients.delete(client);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function handleRouteResponse(route) {
|
|
1425
|
+
const routeKey = getRouteKey(route);
|
|
1426
|
+
routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
|
|
1427
|
+
totalCount += 1;
|
|
1428
|
+
broadcast({ type: "increment", routeKey, total: totalCount });
|
|
1429
|
+
}
|
|
1430
|
+
async function setupPlaygroundWebSocket(app) {
|
|
1431
|
+
if (!playground.enabled) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
try {
|
|
1435
|
+
const mod = await import('@hono/node-ws');
|
|
1436
|
+
const { createNodeWebSocket } = mod;
|
|
1437
|
+
const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app });
|
|
1438
|
+
wsHandler = upgradeWebSocket(() => ({
|
|
1439
|
+
onOpen: (_event, ws) => {
|
|
1440
|
+
registerWsClient(ws);
|
|
1441
|
+
},
|
|
1442
|
+
onClose: (_event, ws) => {
|
|
1443
|
+
wsClients.delete(ws);
|
|
1444
|
+
},
|
|
1445
|
+
onMessage: () => {
|
|
1446
|
+
}
|
|
1447
|
+
}));
|
|
1448
|
+
injectWebSocket = (server) => {
|
|
1449
|
+
inject(server);
|
|
1450
|
+
};
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
handleRouteResponse,
|
|
1456
|
+
setupPlaygroundWebSocket,
|
|
1457
|
+
getWsHandler: () => wsHandler,
|
|
1458
|
+
getInjectWebSocket: () => injectWebSocket
|
|
1459
|
+
};
|
|
1257
1460
|
}
|
|
1461
|
+
|
|
1258
1462
|
async function createDenoWatcher(params) {
|
|
1259
1463
|
const deno = globalThis.Deno;
|
|
1260
1464
|
if (!deno?.watchFs) {
|
|
@@ -1329,6 +1533,7 @@ async function createWatcher(params) {
|
|
|
1329
1533
|
params.logger.warn("Watcher is not available in this runtime; file watching disabled.");
|
|
1330
1534
|
return null;
|
|
1331
1535
|
}
|
|
1536
|
+
|
|
1332
1537
|
async function createFetchServer(options = {}) {
|
|
1333
1538
|
const normalized = normalizeOptions(options);
|
|
1334
1539
|
const optionList = normalized.entries;
|
|
@@ -1338,78 +1543,13 @@ async function createFetchServer(options = {}) {
|
|
|
1338
1543
|
const logger = createLogger(logEnabled);
|
|
1339
1544
|
const playgroundConfig = resolvePlaygroundOptions(normalized.playground);
|
|
1340
1545
|
const dirs = resolveAllDirs(optionList, root);
|
|
1341
|
-
const
|
|
1342
|
-
const wsClients = /* @__PURE__ */ new Set();
|
|
1343
|
-
let totalCount = 0;
|
|
1344
|
-
let wsHandler;
|
|
1345
|
-
let injectWebSocket;
|
|
1346
|
-
function getRouteKey(route) {
|
|
1347
|
-
return `${route.method} ${route.template}`;
|
|
1348
|
-
}
|
|
1349
|
-
function buildSnapshot() {
|
|
1350
|
-
return {
|
|
1351
|
-
type: "snapshot",
|
|
1352
|
-
total: totalCount,
|
|
1353
|
-
perRoute: { ...routeCounts }
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
function broadcast(payload) {
|
|
1357
|
-
if (wsClients.size === 0) {
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
const message = JSON.stringify(payload);
|
|
1361
|
-
for (const client of wsClients) {
|
|
1362
|
-
try {
|
|
1363
|
-
client.send(message);
|
|
1364
|
-
} catch {
|
|
1365
|
-
wsClients.delete(client);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
function registerWsClient(client) {
|
|
1370
|
-
wsClients.add(client);
|
|
1371
|
-
try {
|
|
1372
|
-
client.send(JSON.stringify(buildSnapshot()));
|
|
1373
|
-
} catch {
|
|
1374
|
-
wsClients.delete(client);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
function handleRouteResponse(route) {
|
|
1378
|
-
const routeKey = getRouteKey(route);
|
|
1379
|
-
routeCounts[routeKey] = (routeCounts[routeKey] ?? 0) + 1;
|
|
1380
|
-
totalCount += 1;
|
|
1381
|
-
broadcast({ type: "increment", routeKey, total: totalCount });
|
|
1382
|
-
}
|
|
1383
|
-
async function setupPlaygroundWebSocket(app2) {
|
|
1384
|
-
if (!playgroundConfig.enabled) {
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
try {
|
|
1388
|
-
const mod = await import('@hono/node-ws');
|
|
1389
|
-
const { createNodeWebSocket } = mod;
|
|
1390
|
-
const { upgradeWebSocket, injectWebSocket: inject } = createNodeWebSocket({ app: app2 });
|
|
1391
|
-
wsHandler = upgradeWebSocket(() => ({
|
|
1392
|
-
onOpen: (_event, ws) => {
|
|
1393
|
-
registerWsClient(ws);
|
|
1394
|
-
},
|
|
1395
|
-
onClose: (_event, ws) => {
|
|
1396
|
-
wsClients.delete(ws);
|
|
1397
|
-
},
|
|
1398
|
-
onMessage: () => {
|
|
1399
|
-
}
|
|
1400
|
-
}));
|
|
1401
|
-
injectWebSocket = (server2) => {
|
|
1402
|
-
inject(server2);
|
|
1403
|
-
};
|
|
1404
|
-
} catch {
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1546
|
+
const playgroundWs = createPlaygroundWs(playgroundConfig);
|
|
1407
1547
|
let routes = [];
|
|
1408
1548
|
let disabledRoutes = [];
|
|
1409
1549
|
let ignoredRoutes = [];
|
|
1410
1550
|
let configFiles = [];
|
|
1411
1551
|
let disabledConfigFiles = [];
|
|
1412
|
-
|
|
1552
|
+
const appParams = {
|
|
1413
1553
|
routes,
|
|
1414
1554
|
disabledRoutes,
|
|
1415
1555
|
ignoredRoutes,
|
|
@@ -1419,9 +1559,13 @@ async function createFetchServer(options = {}) {
|
|
|
1419
1559
|
playground: playgroundConfig,
|
|
1420
1560
|
root,
|
|
1421
1561
|
logger,
|
|
1422
|
-
onResponse: handleRouteResponse
|
|
1423
|
-
|
|
1424
|
-
|
|
1562
|
+
onResponse: playgroundWs.handleRouteResponse
|
|
1563
|
+
};
|
|
1564
|
+
const initialWsHandler = playgroundWs.getWsHandler();
|
|
1565
|
+
if (initialWsHandler) {
|
|
1566
|
+
appParams.wsHandler = initialWsHandler;
|
|
1567
|
+
}
|
|
1568
|
+
let app = buildFetchServerApp(appParams);
|
|
1425
1569
|
const refreshRoutes = async () => {
|
|
1426
1570
|
try {
|
|
1427
1571
|
const collected = [];
|
|
@@ -1457,7 +1601,7 @@ async function createFetchServer(options = {}) {
|
|
|
1457
1601
|
const resolvedConfigs = Array.from(configMap.values());
|
|
1458
1602
|
configFiles = resolvedConfigs.filter((entry) => entry.enabled);
|
|
1459
1603
|
disabledConfigFiles = resolvedConfigs.filter((entry) => !entry.enabled);
|
|
1460
|
-
|
|
1604
|
+
const refreshedParams = {
|
|
1461
1605
|
routes,
|
|
1462
1606
|
disabledRoutes,
|
|
1463
1607
|
ignoredRoutes,
|
|
@@ -1467,16 +1611,21 @@ async function createFetchServer(options = {}) {
|
|
|
1467
1611
|
playground: playgroundConfig,
|
|
1468
1612
|
root,
|
|
1469
1613
|
logger,
|
|
1470
|
-
onResponse: handleRouteResponse
|
|
1471
|
-
|
|
1472
|
-
|
|
1614
|
+
onResponse: playgroundWs.handleRouteResponse
|
|
1615
|
+
};
|
|
1616
|
+
const refreshedWsHandler = playgroundWs.getWsHandler();
|
|
1617
|
+
if (refreshedWsHandler) {
|
|
1618
|
+
refreshedParams.wsHandler = refreshedWsHandler;
|
|
1619
|
+
}
|
|
1620
|
+
app = buildFetchServerApp(refreshedParams);
|
|
1473
1621
|
logger.info(`Loaded ${routes.length} mock routes.`);
|
|
1474
1622
|
} catch (error) {
|
|
1475
1623
|
logger.error("Failed to scan mock routes:", error);
|
|
1476
1624
|
}
|
|
1477
1625
|
};
|
|
1478
1626
|
await refreshRoutes();
|
|
1479
|
-
await setupPlaygroundWebSocket(app);
|
|
1627
|
+
await playgroundWs.setupPlaygroundWebSocket(app);
|
|
1628
|
+
const wsHandler = playgroundWs.getWsHandler();
|
|
1480
1629
|
if (wsHandler && playgroundConfig.enabled) {
|
|
1481
1630
|
app.get(`${playgroundConfig.path}/ws`, wsHandler);
|
|
1482
1631
|
}
|
|
@@ -1493,9 +1642,12 @@ async function createFetchServer(options = {}) {
|
|
|
1493
1642
|
const server = {
|
|
1494
1643
|
fetch,
|
|
1495
1644
|
refresh: refreshRoutes,
|
|
1496
|
-
getRoutes: () => routes
|
|
1497
|
-
...injectWebSocket ? { injectWebSocket } : {}
|
|
1645
|
+
getRoutes: () => routes
|
|
1498
1646
|
};
|
|
1647
|
+
const injectWebSocket = playgroundWs.getInjectWebSocket();
|
|
1648
|
+
if (injectWebSocket) {
|
|
1649
|
+
server.injectWebSocket = injectWebSocket;
|
|
1650
|
+
}
|
|
1499
1651
|
if (watcher) {
|
|
1500
1652
|
server.close = async () => {
|
|
1501
1653
|
await watcher.close();
|