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