mokup 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-bin.cjs +3 -1
- package/dist/cli-bin.mjs +3 -1
- package/dist/index.cjs +39 -0
- package/dist/index.d.cts +62 -44
- package/dist/index.d.mts +62 -44
- package/dist/index.d.ts +64 -44
- package/dist/index.mjs +37 -0
- package/dist/server/worker.d.ts +2 -0
- package/dist/shared/mokup.CWQ8woZc.d.cts +302 -0
- package/dist/shared/mokup.CWQ8woZc.d.mts +302 -0
- package/dist/shared/mokup.CWQ8woZc.d.ts +302 -0
- package/dist/shared/{mokup.B-yfMz5B.cjs → mokup.To7knvSq.cjs} +1631 -1220
- package/dist/shared/{mokup.jeVwRMia.mjs → mokup.ghra3mzH.mjs} +1631 -1220
- package/dist/sw.cjs +5 -2
- package/dist/sw.d.cts +63 -0
- package/dist/sw.d.mts +63 -0
- package/dist/sw.d.ts +63 -0
- package/dist/sw.mjs +5 -2
- package/dist/types/virtual.d.ts +9 -0
- package/dist/vite.cjs +694 -346
- package/dist/vite.d.cts +20 -2
- package/dist/vite.d.mts +20 -2
- package/dist/vite.d.ts +22 -2
- package/dist/vite.mjs +687 -340
- package/dist/webpack.cjs +276 -204
- package/dist/webpack.d.cts +36 -4
- package/dist/webpack.d.mts +37 -3
- package/dist/webpack.d.ts +36 -4
- package/dist/webpack.mjs +272 -197
- package/package.json +7 -6
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { promises } from 'node:fs';
|
|
1
|
+
import { promises, existsSync } from 'node:fs';
|
|
2
|
+
import { resolve, isAbsolute, relative, join, dirname, normalize, extname, basename } from '@mokup/shared/pathe';
|
|
4
3
|
import { createRequire } from 'node:module';
|
|
5
4
|
import { cwd } from 'node:process';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { Buffer } from 'node:buffer';
|
|
6
|
+
import { Hono, PatternRouter } from '@mokup/shared/hono';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { build } from '@mokup/shared/esbuild';
|
|
9
9
|
import { parse } from '@mokup/shared/jsonc-parser';
|
|
10
10
|
import { compareRouteScore, parseRouteTemplate } from '@mokup/runtime';
|
|
11
11
|
|
|
12
|
-
function createLogger(enabled) {
|
|
13
|
-
return {
|
|
14
|
-
info: (...args) => {
|
|
15
|
-
if (enabled) {
|
|
16
|
-
console.info("[mokup]", ...args);
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
warn: (...args) => {
|
|
20
|
-
if (enabled) {
|
|
21
|
-
console.warn("[mokup]", ...args);
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
error: (...args) => {
|
|
25
|
-
if (enabled) {
|
|
26
|
-
console.error("[mokup]", ...args);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
12
|
const methodSet = /* @__PURE__ */ new Set([
|
|
33
13
|
"GET",
|
|
34
14
|
"POST",
|
|
@@ -98,20 +78,6 @@ function isInDirs(file, dirs) {
|
|
|
98
78
|
return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
|
|
99
79
|
});
|
|
100
80
|
}
|
|
101
|
-
function testPatterns(patterns, value) {
|
|
102
|
-
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
103
|
-
return list.some((pattern) => pattern.test(value));
|
|
104
|
-
}
|
|
105
|
-
function matchesFilter(file, include, exclude) {
|
|
106
|
-
const normalized = toPosix(file);
|
|
107
|
-
if (exclude && testPatterns(exclude, normalized)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (include) {
|
|
111
|
-
return testPatterns(include, normalized);
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
81
|
function normalizeIgnorePrefix(value, fallback = ["."]) {
|
|
116
82
|
const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
|
|
117
83
|
return list.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
@@ -130,222 +96,188 @@ function delay(ms) {
|
|
|
130
96
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
131
97
|
}
|
|
132
98
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
99
|
+
function toViteImportPath(file, root) {
|
|
100
|
+
const absolute = isAbsolute(file) ? file : resolve(root, file);
|
|
101
|
+
const rel = relative(root, absolute);
|
|
102
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
103
|
+
return `/${toPosix(rel)}`;
|
|
136
104
|
}
|
|
137
|
-
|
|
138
|
-
if (token.type === "static") {
|
|
139
|
-
return token.value;
|
|
140
|
-
}
|
|
141
|
-
if (token.type === "param") {
|
|
142
|
-
return `:${token.name}`;
|
|
143
|
-
}
|
|
144
|
-
if (token.type === "catchall") {
|
|
145
|
-
return `:${token.name}{.+}`;
|
|
146
|
-
}
|
|
147
|
-
return `:${token.name}{.+}?`;
|
|
148
|
-
});
|
|
149
|
-
return `/${segments.join("/")}`;
|
|
150
|
-
}
|
|
151
|
-
function isValidStatus(status) {
|
|
152
|
-
return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
|
|
105
|
+
return `/@fs/${toPosix(absolute)}`;
|
|
153
106
|
}
|
|
154
|
-
function
|
|
155
|
-
if (
|
|
156
|
-
return
|
|
107
|
+
function shouldModuleize(handler) {
|
|
108
|
+
if (typeof handler === "function") {
|
|
109
|
+
return true;
|
|
157
110
|
}
|
|
158
|
-
if (
|
|
159
|
-
return
|
|
111
|
+
if (typeof Response !== "undefined" && handler instanceof Response) {
|
|
112
|
+
return true;
|
|
160
113
|
}
|
|
161
|
-
return
|
|
114
|
+
return false;
|
|
162
115
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
for (const [key, value] of Object.entries(route.headers)) {
|
|
168
|
-
headers.set(key, value);
|
|
169
|
-
}
|
|
116
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
117
|
+
function getNodeBuffer() {
|
|
118
|
+
if (typeof globalThis === "undefined") {
|
|
119
|
+
return null;
|
|
170
120
|
}
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
121
|
+
const buffer = globalThis.Buffer;
|
|
122
|
+
return buffer ?? null;
|
|
123
|
+
}
|
|
124
|
+
function getBtoa() {
|
|
125
|
+
if (typeof globalThis === "undefined") {
|
|
126
|
+
return null;
|
|
174
127
|
}
|
|
175
|
-
|
|
128
|
+
const btoaFn = globalThis.btoa;
|
|
129
|
+
return typeof btoaFn === "function" ? btoaFn : null;
|
|
130
|
+
}
|
|
131
|
+
function encodeBase64(bytes) {
|
|
132
|
+
const buffer = getNodeBuffer();
|
|
133
|
+
if (buffer) {
|
|
134
|
+
return buffer.from(bytes).toString("base64");
|
|
135
|
+
}
|
|
136
|
+
const btoaFn = getBtoa();
|
|
137
|
+
if (btoaFn) {
|
|
138
|
+
let binary = "";
|
|
139
|
+
const chunkSize = 32768;
|
|
140
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
141
|
+
const chunk = bytes.subarray(i, i + chunkSize);
|
|
142
|
+
binary += String.fromCharCode(...chunk);
|
|
143
|
+
}
|
|
144
|
+
return btoaFn(binary);
|
|
145
|
+
}
|
|
146
|
+
let output = "";
|
|
147
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
148
|
+
const a = bytes[i] ?? 0;
|
|
149
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] ?? 0 : 0;
|
|
150
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] ?? 0 : 0;
|
|
151
|
+
const triple = a << 16 | b << 8 | c;
|
|
152
|
+
output += BASE64_ALPHABET[triple >> 18 & 63];
|
|
153
|
+
output += BASE64_ALPHABET[triple >> 12 & 63];
|
|
154
|
+
output += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] : "=";
|
|
155
|
+
output += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] : "=";
|
|
156
|
+
}
|
|
157
|
+
return output;
|
|
176
158
|
}
|
|
177
|
-
function
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
159
|
+
function toBinaryBody(handler) {
|
|
160
|
+
if (handler instanceof ArrayBuffer) {
|
|
161
|
+
return encodeBase64(new Uint8Array(handler));
|
|
180
162
|
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
if (resolved instanceof Response) {
|
|
184
|
-
return resolved;
|
|
185
|
-
}
|
|
163
|
+
if (handler instanceof Uint8Array) {
|
|
164
|
+
return encodeBase64(handler);
|
|
186
165
|
}
|
|
187
|
-
return
|
|
166
|
+
return null;
|
|
188
167
|
}
|
|
189
|
-
function
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
status: 204,
|
|
198
|
-
headers: response.headers
|
|
199
|
-
});
|
|
168
|
+
function buildManifestResponse(route, moduleId) {
|
|
169
|
+
if (moduleId) {
|
|
170
|
+
const response = {
|
|
171
|
+
type: "module",
|
|
172
|
+
module: moduleId
|
|
173
|
+
};
|
|
174
|
+
if (typeof route.ruleIndex === "number") {
|
|
175
|
+
response.ruleIndex = route.ruleIndex;
|
|
200
176
|
}
|
|
201
177
|
return response;
|
|
202
178
|
}
|
|
203
|
-
|
|
204
|
-
|
|
179
|
+
const handler = route.handler;
|
|
180
|
+
if (typeof handler === "string") {
|
|
181
|
+
return {
|
|
182
|
+
type: "text",
|
|
183
|
+
body: handler
|
|
184
|
+
};
|
|
205
185
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
186
|
+
const binary = toBinaryBody(handler);
|
|
187
|
+
if (binary) {
|
|
188
|
+
return {
|
|
189
|
+
type: "binary",
|
|
190
|
+
body: binary,
|
|
191
|
+
encoding: "base64"
|
|
192
|
+
};
|
|
212
193
|
}
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return async (c) => {
|
|
217
|
-
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
218
|
-
return normalizeHandlerValue(c, value);
|
|
194
|
+
return {
|
|
195
|
+
type: "json",
|
|
196
|
+
body: handler
|
|
219
197
|
};
|
|
220
198
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
199
|
+
function buildManifestData(params) {
|
|
200
|
+
const { routes, root } = params;
|
|
201
|
+
const resolveModulePath = params.resolveModulePath ?? toViteImportPath;
|
|
202
|
+
const ruleModules = /* @__PURE__ */ new Map();
|
|
203
|
+
const middlewareModules = /* @__PURE__ */ new Map();
|
|
204
|
+
const manifestRoutes = routes.map((route) => {
|
|
205
|
+
const moduleId = shouldModuleize(route.handler) ? resolveModulePath(route.file, root) : null;
|
|
206
|
+
if (moduleId && !ruleModules.has(moduleId)) {
|
|
207
|
+
ruleModules.set(moduleId, { id: moduleId, kind: "rule" });
|
|
227
208
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
209
|
+
const middleware = route.middlewares?.map((entry) => {
|
|
210
|
+
const modulePath = resolveModulePath(entry.source, root);
|
|
211
|
+
if (!middlewareModules.has(modulePath)) {
|
|
212
|
+
middlewareModules.set(modulePath, { id: modulePath, kind: "middleware" });
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
module: modulePath,
|
|
216
|
+
ruleIndex: entry.index
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
const response = buildManifestResponse(route, moduleId);
|
|
220
|
+
const manifestRoute = {
|
|
221
|
+
method: route.method,
|
|
222
|
+
url: route.template,
|
|
223
|
+
...route.tokens ? { tokens: route.tokens } : {},
|
|
224
|
+
...route.score ? { score: route.score } : {},
|
|
225
|
+
...route.status ? { status: route.status } : {},
|
|
226
|
+
...route.headers ? { headers: route.headers } : {},
|
|
227
|
+
...route.delay ? { delay: route.delay } : {},
|
|
228
|
+
...middleware && middleware.length > 0 ? { middleware } : {},
|
|
229
|
+
response
|
|
230
|
+
};
|
|
231
|
+
return manifestRoute;
|
|
232
|
+
});
|
|
233
|
+
const manifest = {
|
|
234
|
+
version: 1,
|
|
235
|
+
routes: manifestRoutes
|
|
231
236
|
};
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
return {
|
|
238
|
+
manifest,
|
|
239
|
+
modules: [
|
|
240
|
+
...ruleModules.values(),
|
|
241
|
+
...middlewareModules.values()
|
|
242
|
+
]
|
|
237
243
|
};
|
|
238
244
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
app.on(
|
|
244
|
-
route.method,
|
|
245
|
-
toHonoPath(route),
|
|
246
|
-
createFinalizeMiddleware(route),
|
|
247
|
-
...middlewares,
|
|
248
|
-
createRouteHandler(route)
|
|
249
|
-
);
|
|
245
|
+
|
|
246
|
+
function normalizePlaygroundPath(value) {
|
|
247
|
+
if (!value) {
|
|
248
|
+
return "/__mokup";
|
|
250
249
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
async function readRawBody(req) {
|
|
254
|
-
return await new Promise((resolve, reject) => {
|
|
255
|
-
const chunks = [];
|
|
256
|
-
req.on("data", (chunk) => {
|
|
257
|
-
if (typeof chunk === "string") {
|
|
258
|
-
chunks.push(Buffer.from(chunk));
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (chunk instanceof Uint8Array) {
|
|
262
|
-
chunks.push(chunk);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
chunks.push(Buffer.from(String(chunk)));
|
|
266
|
-
});
|
|
267
|
-
req.on("end", () => {
|
|
268
|
-
if (chunks.length === 0) {
|
|
269
|
-
resolve(null);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
resolve(Buffer.concat(chunks));
|
|
273
|
-
});
|
|
274
|
-
req.on("error", reject);
|
|
275
|
-
});
|
|
250
|
+
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
251
|
+
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
276
252
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (typeof value === "undefined") {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
if (Array.isArray(value)) {
|
|
284
|
-
result.set(key, value.join(","));
|
|
285
|
-
} else {
|
|
286
|
-
result.set(key, value);
|
|
287
|
-
}
|
|
253
|
+
function normalizeBase(base) {
|
|
254
|
+
if (!base || base === "/") {
|
|
255
|
+
return "";
|
|
288
256
|
}
|
|
289
|
-
return
|
|
257
|
+
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
290
258
|
}
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const rawBody = await readRawBody(req);
|
|
297
|
-
if (rawBody && method !== "GET" && method !== "HEAD") {
|
|
298
|
-
init.body = rawBody;
|
|
259
|
+
function resolvePlaygroundRequestPath(base, playgroundPath) {
|
|
260
|
+
const normalizedBase = normalizeBase(base);
|
|
261
|
+
const normalizedPath = normalizePlaygroundPath(playgroundPath);
|
|
262
|
+
if (!normalizedBase) {
|
|
263
|
+
return normalizedPath;
|
|
299
264
|
}
|
|
300
|
-
|
|
265
|
+
if (normalizedPath.startsWith(normalizedBase)) {
|
|
266
|
+
return normalizedPath;
|
|
267
|
+
}
|
|
268
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
301
269
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
res.setHeader(key, value);
|
|
306
|
-
});
|
|
307
|
-
if (!response.body) {
|
|
308
|
-
res.end();
|
|
309
|
-
return;
|
|
270
|
+
function resolvePlaygroundOptions(playground) {
|
|
271
|
+
if (playground === false) {
|
|
272
|
+
return { enabled: false, path: "/__mokup" };
|
|
310
273
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return
|
|
318
|
-
}
|
|
319
|
-
function createMiddleware(getApp, logger) {
|
|
320
|
-
return async (req, res, next) => {
|
|
321
|
-
const app = getApp();
|
|
322
|
-
if (!app) {
|
|
323
|
-
return next();
|
|
324
|
-
}
|
|
325
|
-
const url = req.url ?? "/";
|
|
326
|
-
const parsedUrl = new URL(url, "http://mokup.local");
|
|
327
|
-
const pathname = parsedUrl.pathname;
|
|
328
|
-
const method = normalizeMethod(req.method) ?? "GET";
|
|
329
|
-
if (!hasMatch(app, method, pathname)) {
|
|
330
|
-
return next();
|
|
331
|
-
}
|
|
332
|
-
const startedAt = Date.now();
|
|
333
|
-
try {
|
|
334
|
-
const response = await app.fetch(await toRequest(req));
|
|
335
|
-
if (res.writableEnded) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
await sendResponse(res, response);
|
|
339
|
-
logger.info(`${method} ${pathname} ${Date.now() - startedAt}ms`);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
if (!res.headersSent) {
|
|
342
|
-
res.statusCode = 500;
|
|
343
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
344
|
-
}
|
|
345
|
-
res.end("Mock handler error");
|
|
346
|
-
logger.error("Mock handler failed:", error);
|
|
347
|
-
}
|
|
348
|
-
};
|
|
274
|
+
if (playground && typeof playground === "object") {
|
|
275
|
+
return {
|
|
276
|
+
enabled: playground.enabled !== false,
|
|
277
|
+
path: normalizePlaygroundPath(playground.path)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return { enabled: true, path: "/__mokup" };
|
|
349
281
|
}
|
|
350
282
|
|
|
351
283
|
const require$1 = createRequire(import.meta.url);
|
|
@@ -361,96 +293,6 @@ const mimeTypes = {
|
|
|
361
293
|
".jpeg": "image/jpeg",
|
|
362
294
|
".ico": "image/x-icon"
|
|
363
295
|
};
|
|
364
|
-
function normalizePlaygroundPath(value) {
|
|
365
|
-
if (!value) {
|
|
366
|
-
return "/_mokup";
|
|
367
|
-
}
|
|
368
|
-
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
369
|
-
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
370
|
-
}
|
|
371
|
-
function normalizeBase(base) {
|
|
372
|
-
if (!base || base === "/") {
|
|
373
|
-
return "";
|
|
374
|
-
}
|
|
375
|
-
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
376
|
-
}
|
|
377
|
-
function resolvePlaygroundRequestPath(base, playgroundPath) {
|
|
378
|
-
const normalizedBase = normalizeBase(base);
|
|
379
|
-
const normalizedPath = normalizePlaygroundPath(playgroundPath);
|
|
380
|
-
if (!normalizedBase) {
|
|
381
|
-
return normalizedPath;
|
|
382
|
-
}
|
|
383
|
-
if (normalizedPath.startsWith(normalizedBase)) {
|
|
384
|
-
return normalizedPath;
|
|
385
|
-
}
|
|
386
|
-
return `${normalizedBase}${normalizedPath}`;
|
|
387
|
-
}
|
|
388
|
-
function injectPlaygroundHmr(html, base) {
|
|
389
|
-
if (html.includes("mokup-playground-hmr")) {
|
|
390
|
-
return html;
|
|
391
|
-
}
|
|
392
|
-
const normalizedBase = normalizeBase(base);
|
|
393
|
-
const clientPath = `${normalizedBase}/@vite/client`;
|
|
394
|
-
const snippet = [
|
|
395
|
-
'<script type="module" id="mokup-playground-hmr">',
|
|
396
|
-
`import('${clientPath}').then(({ createHotContext }) => {`,
|
|
397
|
-
" const hot = createHotContext('/@mokup/playground')",
|
|
398
|
-
" hot.on('mokup:routes-changed', () => {",
|
|
399
|
-
" const api = window.__MOKUP_PLAYGROUND__",
|
|
400
|
-
" if (api && typeof api.reloadRoutes === 'function') {",
|
|
401
|
-
" api.reloadRoutes()",
|
|
402
|
-
" return",
|
|
403
|
-
" }",
|
|
404
|
-
" window.location.reload()",
|
|
405
|
-
" })",
|
|
406
|
-
"}).catch(() => {})",
|
|
407
|
-
"<\/script>"
|
|
408
|
-
].join("\n");
|
|
409
|
-
if (html.includes("</body>")) {
|
|
410
|
-
return html.replace("</body>", `${snippet}
|
|
411
|
-
</body>`);
|
|
412
|
-
}
|
|
413
|
-
return `${html}
|
|
414
|
-
${snippet}`;
|
|
415
|
-
}
|
|
416
|
-
function injectPlaygroundSw(html, script) {
|
|
417
|
-
if (!script) {
|
|
418
|
-
return html;
|
|
419
|
-
}
|
|
420
|
-
if (html.includes("mokup-playground-sw")) {
|
|
421
|
-
return html;
|
|
422
|
-
}
|
|
423
|
-
const snippet = [
|
|
424
|
-
'<script type="module" id="mokup-playground-sw">',
|
|
425
|
-
script,
|
|
426
|
-
"<\/script>"
|
|
427
|
-
].join("\n");
|
|
428
|
-
if (html.includes("</head>")) {
|
|
429
|
-
return html.replace("</head>", `${snippet}
|
|
430
|
-
</head>`);
|
|
431
|
-
}
|
|
432
|
-
if (html.includes("</body>")) {
|
|
433
|
-
return html.replace("</body>", `${snippet}
|
|
434
|
-
</body>`);
|
|
435
|
-
}
|
|
436
|
-
return `${html}
|
|
437
|
-
${snippet}`;
|
|
438
|
-
}
|
|
439
|
-
function isViteDevServer(server) {
|
|
440
|
-
return !!server && "ws" in server;
|
|
441
|
-
}
|
|
442
|
-
function resolvePlaygroundOptions(playground) {
|
|
443
|
-
if (playground === false) {
|
|
444
|
-
return { enabled: false, path: "/_mokup" };
|
|
445
|
-
}
|
|
446
|
-
if (playground && typeof playground === "object") {
|
|
447
|
-
return {
|
|
448
|
-
enabled: playground.enabled !== false,
|
|
449
|
-
path: normalizePlaygroundPath(playground.path)
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
return { enabled: true, path: "/_mokup" };
|
|
453
|
-
}
|
|
454
296
|
function resolvePlaygroundDist() {
|
|
455
297
|
const pkgPath = require$1.resolve("@mokup/playground/package.json");
|
|
456
298
|
return join(pkgPath, "..", "dist");
|
|
@@ -465,6 +307,7 @@ function sendFile(res, content, contentType) {
|
|
|
465
307
|
res.setHeader("Content-Type", contentType);
|
|
466
308
|
res.end(content);
|
|
467
309
|
}
|
|
310
|
+
|
|
468
311
|
function toPosixPath(value) {
|
|
469
312
|
return value.replace(/\\/g, "/");
|
|
470
313
|
}
|
|
@@ -506,41 +349,6 @@ function resolveGroupRoot(dirs, serverRoot) {
|
|
|
506
349
|
}
|
|
507
350
|
return common;
|
|
508
351
|
}
|
|
509
|
-
const disabledReasonSet = /* @__PURE__ */ new Set([
|
|
510
|
-
"disabled",
|
|
511
|
-
"disabled-dir",
|
|
512
|
-
"exclude",
|
|
513
|
-
"ignore-prefix",
|
|
514
|
-
"include",
|
|
515
|
-
"unknown"
|
|
516
|
-
]);
|
|
517
|
-
const ignoredReasonSet = /* @__PURE__ */ new Set([
|
|
518
|
-
"unsupported",
|
|
519
|
-
"invalid-route",
|
|
520
|
-
"unknown"
|
|
521
|
-
]);
|
|
522
|
-
function normalizeDisabledReason(reason) {
|
|
523
|
-
if (reason && disabledReasonSet.has(reason)) {
|
|
524
|
-
return reason;
|
|
525
|
-
}
|
|
526
|
-
return "unknown";
|
|
527
|
-
}
|
|
528
|
-
function normalizeIgnoredReason(reason) {
|
|
529
|
-
if (reason && ignoredReasonSet.has(reason)) {
|
|
530
|
-
return reason;
|
|
531
|
-
}
|
|
532
|
-
return "unknown";
|
|
533
|
-
}
|
|
534
|
-
function formatRouteFile(file, root) {
|
|
535
|
-
if (!root) {
|
|
536
|
-
return toPosixPath(file);
|
|
537
|
-
}
|
|
538
|
-
const rel = toPosixPath(relative(root, file));
|
|
539
|
-
if (!rel || rel.startsWith("..")) {
|
|
540
|
-
return toPosixPath(file);
|
|
541
|
-
}
|
|
542
|
-
return rel;
|
|
543
|
-
}
|
|
544
352
|
function resolveGroups(dirs, root) {
|
|
545
353
|
const groups = [];
|
|
546
354
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -575,64 +383,205 @@ function resolveRouteGroup(routeFile, groups) {
|
|
|
575
383
|
}
|
|
576
384
|
return matched;
|
|
577
385
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
return {
|
|
582
|
-
method: route.method,
|
|
583
|
-
url: route.template,
|
|
584
|
-
file: formatRouteFile(route.file, root),
|
|
585
|
-
type: typeof route.handler === "function" ? "handler" : "static",
|
|
586
|
-
status: route.status,
|
|
587
|
-
delay: route.delay,
|
|
588
|
-
middlewareCount: middlewareSources?.length ?? 0,
|
|
589
|
-
middlewares: middlewareSources,
|
|
590
|
-
groupKey: matchedGroup?.key,
|
|
591
|
-
group: matchedGroup?.label
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
function toPlaygroundDisabledRoute(route, root, groups) {
|
|
595
|
-
const matchedGroup = resolveRouteGroup(route.file, groups);
|
|
596
|
-
const disabled = {
|
|
597
|
-
file: formatRouteFile(route.file, root),
|
|
598
|
-
reason: normalizeDisabledReason(route.reason)
|
|
599
|
-
};
|
|
600
|
-
if (typeof route.method !== "undefined") {
|
|
601
|
-
disabled.method = route.method;
|
|
602
|
-
}
|
|
603
|
-
if (typeof route.url !== "undefined") {
|
|
604
|
-
disabled.url = route.url;
|
|
386
|
+
function formatRouteFile(file, root) {
|
|
387
|
+
if (!root) {
|
|
388
|
+
return toPosixPath(file);
|
|
605
389
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
390
|
+
const rel = toPosixPath(relative(root, file));
|
|
391
|
+
if (!rel || rel.startsWith("..")) {
|
|
392
|
+
return toPosixPath(file);
|
|
609
393
|
}
|
|
610
|
-
return
|
|
394
|
+
return rel;
|
|
611
395
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
reason: normalizeIgnoredReason(route.reason)
|
|
617
|
-
};
|
|
618
|
-
if (matchedGroup) {
|
|
619
|
-
ignored.groupKey = matchedGroup.key;
|
|
620
|
-
ignored.group = matchedGroup.label;
|
|
396
|
+
|
|
397
|
+
function injectPlaygroundHmr(html, base) {
|
|
398
|
+
if (html.includes("mokup-playground-hmr")) {
|
|
399
|
+
return html;
|
|
621
400
|
}
|
|
622
|
-
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
401
|
+
const normalizedBase = normalizeBase(base);
|
|
402
|
+
const clientPath = `${normalizedBase}/@vite/client`;
|
|
403
|
+
const snippet = [
|
|
404
|
+
'<script type="module" id="mokup-playground-hmr">',
|
|
405
|
+
`import('${clientPath}').then(({ createHotContext }) => {`,
|
|
406
|
+
" const hot = createHotContext('/@mokup/playground')",
|
|
407
|
+
" hot.on('mokup:routes-changed', () => {",
|
|
408
|
+
" const api = window.__MOKUP_PLAYGROUND__",
|
|
409
|
+
" if (api && typeof api.reloadRoutes === 'function') {",
|
|
410
|
+
" api.reloadRoutes()",
|
|
411
|
+
" return",
|
|
412
|
+
" }",
|
|
413
|
+
" window.location.reload()",
|
|
414
|
+
" })",
|
|
415
|
+
"}).catch(() => {})",
|
|
416
|
+
"<\/script>"
|
|
417
|
+
].join("\n");
|
|
418
|
+
if (html.includes("</body>")) {
|
|
419
|
+
return html.replace("</body>", `${snippet}
|
|
420
|
+
</body>`);
|
|
632
421
|
}
|
|
633
|
-
return
|
|
422
|
+
return `${html}
|
|
423
|
+
${snippet}`;
|
|
634
424
|
}
|
|
635
|
-
function
|
|
425
|
+
function injectPlaygroundSw(html, script) {
|
|
426
|
+
if (!script) {
|
|
427
|
+
return html;
|
|
428
|
+
}
|
|
429
|
+
if (html.includes("mokup-playground-sw")) {
|
|
430
|
+
return html;
|
|
431
|
+
}
|
|
432
|
+
const snippet = [
|
|
433
|
+
'<script type="module" id="mokup-playground-sw">',
|
|
434
|
+
script,
|
|
435
|
+
"<\/script>"
|
|
436
|
+
].join("\n");
|
|
437
|
+
if (html.includes("</head>")) {
|
|
438
|
+
return html.replace("</head>", `${snippet}
|
|
439
|
+
</head>`);
|
|
440
|
+
}
|
|
441
|
+
if (html.includes("</body>")) {
|
|
442
|
+
return html.replace("</body>", `${snippet}
|
|
443
|
+
</body>`);
|
|
444
|
+
}
|
|
445
|
+
return `${html}
|
|
446
|
+
${snippet}`;
|
|
447
|
+
}
|
|
448
|
+
function isViteDevServer(server) {
|
|
449
|
+
return !!server && "ws" in server;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const disabledReasonSet = /* @__PURE__ */ new Set([
|
|
453
|
+
"disabled",
|
|
454
|
+
"disabled-dir",
|
|
455
|
+
"exclude",
|
|
456
|
+
"ignore-prefix",
|
|
457
|
+
"include",
|
|
458
|
+
"unknown"
|
|
459
|
+
]);
|
|
460
|
+
const ignoredReasonSet = /* @__PURE__ */ new Set([
|
|
461
|
+
"unsupported",
|
|
462
|
+
"invalid-route",
|
|
463
|
+
"unknown"
|
|
464
|
+
]);
|
|
465
|
+
function normalizeDisabledReason(reason) {
|
|
466
|
+
if (reason && disabledReasonSet.has(reason)) {
|
|
467
|
+
return reason;
|
|
468
|
+
}
|
|
469
|
+
return "unknown";
|
|
470
|
+
}
|
|
471
|
+
function normalizeIgnoredReason(reason) {
|
|
472
|
+
if (reason && ignoredReasonSet.has(reason)) {
|
|
473
|
+
return reason;
|
|
474
|
+
}
|
|
475
|
+
return "unknown";
|
|
476
|
+
}
|
|
477
|
+
function toPlaygroundRoute(route, root, groups) {
|
|
478
|
+
const matchedGroup = resolveRouteGroup(route.file, groups);
|
|
479
|
+
const preSources = route.middlewares?.filter((entry) => entry.position === "pre").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
480
|
+
const postSources = route.middlewares?.filter((entry) => entry.position === "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
481
|
+
const normalSources = route.middlewares?.filter((entry) => entry.position !== "pre" && entry.position !== "post").map((entry) => formatRouteFile(entry.source, root)) ?? [];
|
|
482
|
+
const combinedSources = [
|
|
483
|
+
...preSources,
|
|
484
|
+
...normalSources,
|
|
485
|
+
...postSources
|
|
486
|
+
];
|
|
487
|
+
const configChain = route.configChain?.map((entry) => formatRouteFile(entry, root)) ?? [];
|
|
488
|
+
return {
|
|
489
|
+
method: route.method,
|
|
490
|
+
url: route.template,
|
|
491
|
+
file: formatRouteFile(route.file, root),
|
|
492
|
+
type: typeof route.handler === "function" ? "handler" : "static",
|
|
493
|
+
status: route.status,
|
|
494
|
+
delay: route.delay,
|
|
495
|
+
middlewareCount: combinedSources.length,
|
|
496
|
+
middlewares: combinedSources,
|
|
497
|
+
preMiddlewareCount: preSources.length,
|
|
498
|
+
normalMiddlewareCount: normalSources.length,
|
|
499
|
+
postMiddlewareCount: postSources.length,
|
|
500
|
+
preMiddlewares: preSources,
|
|
501
|
+
normalMiddlewares: normalSources,
|
|
502
|
+
postMiddlewares: postSources,
|
|
503
|
+
configChain: configChain.length > 0 ? configChain : void 0,
|
|
504
|
+
groupKey: matchedGroup?.key,
|
|
505
|
+
group: matchedGroup?.label
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function toPlaygroundDisabledRoute(route, root, groups) {
|
|
509
|
+
const matchedGroup = resolveRouteGroup(route.file, groups);
|
|
510
|
+
const disabled = {
|
|
511
|
+
file: formatRouteFile(route.file, root),
|
|
512
|
+
reason: normalizeDisabledReason(route.reason)
|
|
513
|
+
};
|
|
514
|
+
if (typeof route.method !== "undefined") {
|
|
515
|
+
disabled.method = route.method;
|
|
516
|
+
}
|
|
517
|
+
if (typeof route.url !== "undefined") {
|
|
518
|
+
disabled.url = route.url;
|
|
519
|
+
}
|
|
520
|
+
if (route.configChain && route.configChain.length > 0) {
|
|
521
|
+
disabled.configChain = route.configChain.map((entry) => formatRouteFile(entry, root));
|
|
522
|
+
}
|
|
523
|
+
if (route.decisionChain && route.decisionChain.length > 0) {
|
|
524
|
+
disabled.decisionChain = formatDecisionChain(route.decisionChain, root);
|
|
525
|
+
}
|
|
526
|
+
if (route.effectiveConfig && Object.keys(route.effectiveConfig).length > 0) {
|
|
527
|
+
disabled.effectiveConfig = route.effectiveConfig;
|
|
528
|
+
}
|
|
529
|
+
if (matchedGroup) {
|
|
530
|
+
disabled.groupKey = matchedGroup.key;
|
|
531
|
+
disabled.group = matchedGroup.label;
|
|
532
|
+
}
|
|
533
|
+
return disabled;
|
|
534
|
+
}
|
|
535
|
+
function toPlaygroundIgnoredRoute(route, root, groups) {
|
|
536
|
+
const matchedGroup = resolveRouteGroup(route.file, groups);
|
|
537
|
+
const ignored = {
|
|
538
|
+
file: formatRouteFile(route.file, root),
|
|
539
|
+
reason: normalizeIgnoredReason(route.reason)
|
|
540
|
+
};
|
|
541
|
+
if (matchedGroup) {
|
|
542
|
+
ignored.groupKey = matchedGroup.key;
|
|
543
|
+
ignored.group = matchedGroup.label;
|
|
544
|
+
}
|
|
545
|
+
if (route.configChain && route.configChain.length > 0) {
|
|
546
|
+
ignored.configChain = route.configChain.map((entry) => formatRouteFile(entry, root));
|
|
547
|
+
}
|
|
548
|
+
if (route.decisionChain && route.decisionChain.length > 0) {
|
|
549
|
+
ignored.decisionChain = formatDecisionChain(route.decisionChain, root);
|
|
550
|
+
}
|
|
551
|
+
if (route.effectiveConfig && Object.keys(route.effectiveConfig).length > 0) {
|
|
552
|
+
ignored.effectiveConfig = route.effectiveConfig;
|
|
553
|
+
}
|
|
554
|
+
return ignored;
|
|
555
|
+
}
|
|
556
|
+
function toPlaygroundConfigFile(entry, root, groups) {
|
|
557
|
+
const matchedGroup = resolveRouteGroup(entry.file, groups);
|
|
558
|
+
const configFile = {
|
|
559
|
+
file: formatRouteFile(entry.file, root)
|
|
560
|
+
};
|
|
561
|
+
if (matchedGroup) {
|
|
562
|
+
configFile.groupKey = matchedGroup.key;
|
|
563
|
+
configFile.group = matchedGroup.label;
|
|
564
|
+
}
|
|
565
|
+
return configFile;
|
|
566
|
+
}
|
|
567
|
+
function formatDecisionChain(chain, root) {
|
|
568
|
+
return chain.map((entry) => {
|
|
569
|
+
const formatted = {
|
|
570
|
+
step: entry.step,
|
|
571
|
+
result: entry.result
|
|
572
|
+
};
|
|
573
|
+
if (typeof entry.detail !== "undefined") {
|
|
574
|
+
formatted.detail = entry.detail;
|
|
575
|
+
}
|
|
576
|
+
if (typeof entry.source !== "undefined") {
|
|
577
|
+
const source = entry.source;
|
|
578
|
+
formatted.source = source && isAbsolute(source) ? formatRouteFile(source, root) : source;
|
|
579
|
+
}
|
|
580
|
+
return formatted;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function createPlaygroundMiddleware(params) {
|
|
636
585
|
const distDir = resolvePlaygroundDist();
|
|
637
586
|
const playgroundPath = params.config.path;
|
|
638
587
|
const indexPath = join(distDir, "index.html");
|
|
@@ -715,932 +664,1394 @@ function createPlaygroundMiddleware(params) {
|
|
|
715
664
|
};
|
|
716
665
|
}
|
|
717
666
|
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (!
|
|
722
|
-
return
|
|
667
|
+
const defaultSwPath = "/mokup-sw.js";
|
|
668
|
+
const defaultSwScope = "/";
|
|
669
|
+
function normalizeSwPath(path) {
|
|
670
|
+
if (!path) {
|
|
671
|
+
return defaultSwPath;
|
|
723
672
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
673
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
674
|
+
}
|
|
675
|
+
function normalizeSwScope(scope) {
|
|
676
|
+
if (!scope) {
|
|
677
|
+
return defaultSwScope;
|
|
727
678
|
}
|
|
728
|
-
|
|
729
|
-
|
|
679
|
+
return scope.startsWith("/") ? scope : `/${scope}`;
|
|
680
|
+
}
|
|
681
|
+
function normalizeBasePath(value) {
|
|
682
|
+
if (!value) {
|
|
683
|
+
return "/";
|
|
730
684
|
}
|
|
731
|
-
|
|
732
|
-
|
|
685
|
+
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
686
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
687
|
+
return normalized.slice(0, -1);
|
|
733
688
|
}
|
|
734
|
-
return
|
|
689
|
+
return normalized;
|
|
735
690
|
}
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
691
|
+
function resolveSwConfigFromEntries(entries, logger) {
|
|
692
|
+
let path = defaultSwPath;
|
|
693
|
+
let scope = defaultSwScope;
|
|
694
|
+
let register = true;
|
|
695
|
+
let unregister = false;
|
|
696
|
+
const basePaths = [];
|
|
697
|
+
let hasPath = false;
|
|
698
|
+
let hasScope = false;
|
|
699
|
+
let hasRegister = false;
|
|
700
|
+
let hasUnregister = false;
|
|
701
|
+
for (const entry of entries) {
|
|
702
|
+
const config = entry.sw;
|
|
703
|
+
if (config?.path) {
|
|
704
|
+
const next = normalizeSwPath(config.path);
|
|
705
|
+
if (!hasPath) {
|
|
706
|
+
path = next;
|
|
707
|
+
hasPath = true;
|
|
708
|
+
} else if (path !== next) {
|
|
709
|
+
logger.warn(`SW path "${next}" ignored; using "${path}".`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (config?.scope) {
|
|
713
|
+
const next = normalizeSwScope(config.scope);
|
|
714
|
+
if (!hasScope) {
|
|
715
|
+
scope = next;
|
|
716
|
+
hasScope = true;
|
|
717
|
+
} else if (scope !== next) {
|
|
718
|
+
logger.warn(`SW scope "${next}" ignored; using "${scope}".`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (typeof config?.register === "boolean") {
|
|
722
|
+
if (!hasRegister) {
|
|
723
|
+
register = config.register;
|
|
724
|
+
hasRegister = true;
|
|
725
|
+
} else if (register !== config.register) {
|
|
726
|
+
logger.warn(
|
|
727
|
+
`SW register="${String(config.register)}" ignored; using "${String(register)}".`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (typeof config?.unregister === "boolean") {
|
|
732
|
+
if (!hasUnregister) {
|
|
733
|
+
unregister = config.unregister;
|
|
734
|
+
hasUnregister = true;
|
|
735
|
+
} else if (unregister !== config.unregister) {
|
|
736
|
+
logger.warn(
|
|
737
|
+
`SW unregister="${String(config.unregister)}" ignored; using "${String(unregister)}".`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (typeof config?.basePath !== "undefined") {
|
|
742
|
+
const values = Array.isArray(config.basePath) ? config.basePath : [config.basePath];
|
|
743
|
+
for (const value of values) {
|
|
744
|
+
basePaths.push(normalizeBasePath(value));
|
|
745
|
+
}
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
const normalizedPrefix = normalizePrefix(entry.prefix ?? "");
|
|
749
|
+
if (normalizedPrefix) {
|
|
750
|
+
basePaths.push(normalizedPrefix);
|
|
751
|
+
}
|
|
745
752
|
}
|
|
746
753
|
return {
|
|
747
|
-
|
|
748
|
-
|
|
754
|
+
path,
|
|
755
|
+
scope,
|
|
756
|
+
register,
|
|
757
|
+
unregister,
|
|
758
|
+
basePaths: Array.from(new Set(basePaths))
|
|
749
759
|
};
|
|
750
760
|
}
|
|
751
|
-
function
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
const withoutExt = rel.slice(0, rel.length - ext.length);
|
|
755
|
-
const dir = dirname(withoutExt);
|
|
756
|
-
const base = basename(withoutExt);
|
|
757
|
-
const { name, method } = stripMethodSuffix(base);
|
|
758
|
-
const resolvedMethod = method ?? (jsonExtensions.has(ext) ? "GET" : void 0);
|
|
759
|
-
if (!resolvedMethod) {
|
|
760
|
-
logger.warn(`Skip mock without method suffix: ${file}`);
|
|
761
|
-
return null;
|
|
762
|
-
}
|
|
763
|
-
if (!name) {
|
|
764
|
-
logger.warn(`Skip mock with empty route name: ${file}`);
|
|
761
|
+
function resolveSwConfig(options, logger) {
|
|
762
|
+
const swEntries = options.filter((entry) => entry.mode === "sw");
|
|
763
|
+
if (swEntries.length === 0) {
|
|
765
764
|
return null;
|
|
766
765
|
}
|
|
767
|
-
|
|
768
|
-
const segments = toPosix(joined).split("/");
|
|
769
|
-
if (segments.at(-1) === "index") {
|
|
770
|
-
segments.pop();
|
|
771
|
-
}
|
|
772
|
-
const template = segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
773
|
-
const parsed = parseRouteTemplate(template);
|
|
774
|
-
if (parsed.errors.length > 0) {
|
|
775
|
-
for (const error of parsed.errors) {
|
|
776
|
-
logger.warn(`${error} in ${file}`);
|
|
777
|
-
}
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
for (const warning of parsed.warnings) {
|
|
781
|
-
logger.warn(`${warning} in ${file}`);
|
|
782
|
-
}
|
|
783
|
-
return {
|
|
784
|
-
template: parsed.template,
|
|
785
|
-
method: resolvedMethod,
|
|
786
|
-
tokens: parsed.tokens,
|
|
787
|
-
score: parsed.score
|
|
788
|
-
};
|
|
766
|
+
return resolveSwConfigFromEntries(swEntries, logger);
|
|
789
767
|
}
|
|
790
|
-
function
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
768
|
+
function resolveSwUnregisterConfig(options, logger) {
|
|
769
|
+
return resolveSwConfigFromEntries(options, logger);
|
|
770
|
+
}
|
|
771
|
+
function buildSwScript(params) {
|
|
772
|
+
const { routes, root } = params;
|
|
773
|
+
const runtimeImportPath = params.runtimeImportPath ?? "mokup/runtime";
|
|
774
|
+
const loggerImportPath = params.loggerImportPath ?? "@mokup/shared/logger";
|
|
775
|
+
const basePaths = params.basePaths ?? [];
|
|
776
|
+
const resolveModulePath = params.resolveModulePath ?? toViteImportPath;
|
|
777
|
+
const { manifest, modules } = buildManifestData({
|
|
778
|
+
routes,
|
|
779
|
+
root,
|
|
780
|
+
resolveModulePath
|
|
781
|
+
});
|
|
782
|
+
const imports = [
|
|
783
|
+
`import { createLogger } from ${JSON.stringify(loggerImportPath)}`,
|
|
784
|
+
`import { createRuntimeApp, handle } from ${JSON.stringify(runtimeImportPath)}`
|
|
785
|
+
];
|
|
786
|
+
const moduleEntries = [];
|
|
787
|
+
let moduleIndex = 0;
|
|
788
|
+
for (const entry of modules) {
|
|
789
|
+
const name = `module${moduleIndex++}`;
|
|
790
|
+
imports.push(`import * as ${name} from '${entry.id}'`);
|
|
791
|
+
moduleEntries.push({ id: entry.id, name, kind: entry.kind });
|
|
795
792
|
}
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
793
|
+
const lines = [];
|
|
794
|
+
lines.push(...imports, "");
|
|
795
|
+
lines.push(
|
|
796
|
+
"const logger = createLogger()",
|
|
797
|
+
"",
|
|
798
|
+
"const resolveModuleExport = (mod) => mod?.default ?? mod",
|
|
799
|
+
"",
|
|
800
|
+
"const toRuntimeRule = (value) => {",
|
|
801
|
+
" if (typeof value === 'undefined') {",
|
|
802
|
+
" return null",
|
|
803
|
+
" }",
|
|
804
|
+
" if (typeof value === 'function') {",
|
|
805
|
+
" return { response: value }",
|
|
806
|
+
" }",
|
|
807
|
+
" if (value === null) {",
|
|
808
|
+
" return { response: null }",
|
|
809
|
+
" }",
|
|
810
|
+
" if (typeof value === 'object') {",
|
|
811
|
+
" if ('response' in value) {",
|
|
812
|
+
" return value",
|
|
813
|
+
" }",
|
|
814
|
+
" if ('handler' in value) {",
|
|
815
|
+
" const handlerRule = value",
|
|
816
|
+
" return {",
|
|
817
|
+
" response: handlerRule.handler,",
|
|
818
|
+
" ...(typeof handlerRule.status === 'number' ? { status: handlerRule.status } : {}),",
|
|
819
|
+
" ...(handlerRule.headers ? { headers: handlerRule.headers } : {}),",
|
|
820
|
+
" ...(typeof handlerRule.delay === 'number' ? { delay: handlerRule.delay } : {}),",
|
|
821
|
+
" }",
|
|
822
|
+
" }",
|
|
823
|
+
" return { response: value }",
|
|
824
|
+
" }",
|
|
825
|
+
" return { response: value }",
|
|
826
|
+
"}",
|
|
827
|
+
"",
|
|
828
|
+
"const toRuntimeRules = (value) => {",
|
|
829
|
+
" if (typeof value === 'undefined') {",
|
|
830
|
+
" return []",
|
|
831
|
+
" }",
|
|
832
|
+
" if (Array.isArray(value)) {",
|
|
833
|
+
" return value.map(toRuntimeRule).filter(Boolean)",
|
|
834
|
+
" }",
|
|
835
|
+
" const rule = toRuntimeRule(value)",
|
|
836
|
+
" return rule ? [rule] : []",
|
|
837
|
+
"}",
|
|
838
|
+
""
|
|
839
|
+
);
|
|
840
|
+
lines.push(
|
|
841
|
+
`const manifest = ${JSON.stringify(manifest, null, 2)}`,
|
|
842
|
+
""
|
|
843
|
+
);
|
|
844
|
+
if (moduleEntries.length > 0) {
|
|
845
|
+
lines.push("const moduleMap = {");
|
|
846
|
+
for (const entry of moduleEntries) {
|
|
847
|
+
if (entry.kind === "rule") {
|
|
848
|
+
lines.push(
|
|
849
|
+
` ${JSON.stringify(entry.id)}: { default: toRuntimeRules(resolveModuleExport(${entry.name})) },`
|
|
850
|
+
);
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
lines.push(
|
|
854
|
+
` ${JSON.stringify(entry.id)}: ${entry.name},`
|
|
855
|
+
);
|
|
801
856
|
}
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
for (const warning of parsed.warnings) {
|
|
805
|
-
params.logger.warn(`${warning} in ${params.file}`);
|
|
806
|
-
}
|
|
807
|
-
const route = {
|
|
808
|
-
file: params.file,
|
|
809
|
-
template: parsed.template,
|
|
810
|
-
method,
|
|
811
|
-
tokens: parsed.tokens,
|
|
812
|
-
score: parsed.score,
|
|
813
|
-
handler: params.rule.handler
|
|
814
|
-
};
|
|
815
|
-
if (typeof params.rule.status === "number") {
|
|
816
|
-
route.status = params.rule.status;
|
|
817
|
-
}
|
|
818
|
-
if (params.rule.headers) {
|
|
819
|
-
route.headers = params.rule.headers;
|
|
820
|
-
}
|
|
821
|
-
if (typeof params.rule.delay === "number") {
|
|
822
|
-
route.delay = params.rule.delay;
|
|
857
|
+
lines.push("}", "");
|
|
823
858
|
}
|
|
824
|
-
|
|
859
|
+
const runtimeOptions = moduleEntries.length > 0 ? "{ manifest, moduleMap }" : "{ manifest }";
|
|
860
|
+
lines.push(
|
|
861
|
+
`const basePaths = ${JSON.stringify(basePaths)}`,
|
|
862
|
+
"",
|
|
863
|
+
"self.addEventListener('install', () => {",
|
|
864
|
+
" self.skipWaiting()",
|
|
865
|
+
"})",
|
|
866
|
+
"",
|
|
867
|
+
"self.addEventListener('activate', (event) => {",
|
|
868
|
+
" event.waitUntil(self.clients.claim())",
|
|
869
|
+
"})",
|
|
870
|
+
"",
|
|
871
|
+
"const shouldHandle = (request) => {",
|
|
872
|
+
" if (!basePaths || basePaths.length === 0) {",
|
|
873
|
+
" return true",
|
|
874
|
+
" }",
|
|
875
|
+
" const pathname = new URL(request.url).pathname",
|
|
876
|
+
" return basePaths.some((basePath) => {",
|
|
877
|
+
" if (basePath === '/') {",
|
|
878
|
+
" return true",
|
|
879
|
+
" }",
|
|
880
|
+
" return pathname === basePath || pathname.startsWith(basePath + '/')",
|
|
881
|
+
" })",
|
|
882
|
+
"}",
|
|
883
|
+
"",
|
|
884
|
+
"const registerHandler = async () => {",
|
|
885
|
+
` const app = await createRuntimeApp(${runtimeOptions})`,
|
|
886
|
+
" const handler = handle(app)",
|
|
887
|
+
" self.addEventListener('fetch', (event) => {",
|
|
888
|
+
" if (!shouldHandle(event.request)) {",
|
|
889
|
+
" return",
|
|
890
|
+
" }",
|
|
891
|
+
" handler(event)",
|
|
892
|
+
" })",
|
|
893
|
+
"}",
|
|
894
|
+
"",
|
|
895
|
+
"registerHandler().catch((error) => {",
|
|
896
|
+
" logger.error('Failed to build service worker app:', error)",
|
|
897
|
+
"})",
|
|
898
|
+
""
|
|
899
|
+
);
|
|
900
|
+
return lines.join("\n");
|
|
825
901
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
902
|
+
|
|
903
|
+
function toHonoPath(route) {
|
|
904
|
+
if (!route.tokens || route.tokens.length === 0) {
|
|
905
|
+
return "/";
|
|
906
|
+
}
|
|
907
|
+
const segments = route.tokens.map((token) => {
|
|
908
|
+
if (token.type === "static") {
|
|
909
|
+
return token.value;
|
|
830
910
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return scoreCompare;
|
|
911
|
+
if (token.type === "param") {
|
|
912
|
+
return `:${token.name}`;
|
|
834
913
|
}
|
|
835
|
-
|
|
914
|
+
if (token.type === "catchall") {
|
|
915
|
+
return `:${token.name}{.+}`;
|
|
916
|
+
}
|
|
917
|
+
return `:${token.name}{.+}?`;
|
|
836
918
|
});
|
|
919
|
+
return `/${segments.join("/")}`;
|
|
837
920
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return require(file);
|
|
845
|
-
}
|
|
846
|
-
if (ext === ".js" || ext === ".mjs") {
|
|
847
|
-
return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
|
|
921
|
+
function isValidStatus(status) {
|
|
922
|
+
return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
|
|
923
|
+
}
|
|
924
|
+
function resolveStatus(routeStatus, responseStatus) {
|
|
925
|
+
if (isValidStatus(routeStatus)) {
|
|
926
|
+
return routeStatus;
|
|
848
927
|
}
|
|
849
|
-
if (
|
|
850
|
-
|
|
851
|
-
entryPoints: [file],
|
|
852
|
-
bundle: true,
|
|
853
|
-
format: "esm",
|
|
854
|
-
platform: "node",
|
|
855
|
-
sourcemap: "inline",
|
|
856
|
-
target: "es2020",
|
|
857
|
-
write: false
|
|
858
|
-
});
|
|
859
|
-
const output = result.outputFiles[0];
|
|
860
|
-
const code = output?.text ?? "";
|
|
861
|
-
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString(
|
|
862
|
-
"base64"
|
|
863
|
-
)}`;
|
|
864
|
-
return import(`${dataUrl}#${Date.now()}`);
|
|
928
|
+
if (isValidStatus(responseStatus)) {
|
|
929
|
+
return responseStatus;
|
|
865
930
|
}
|
|
866
|
-
return
|
|
931
|
+
return 200;
|
|
867
932
|
}
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
933
|
+
function applyRouteOverrides(response, route) {
|
|
934
|
+
const headers = new Headers(response.headers);
|
|
935
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
936
|
+
if (route.headers) {
|
|
937
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
938
|
+
headers.set(key, value);
|
|
874
939
|
}
|
|
875
|
-
return asDevServer.ssrLoadModule(file);
|
|
876
|
-
}
|
|
877
|
-
return loadModule$1(file);
|
|
878
|
-
}
|
|
879
|
-
function getConfigFileCandidates(dir) {
|
|
880
|
-
return configExtensions.map((extension) => join(dir, `index.config${extension}`));
|
|
881
|
-
}
|
|
882
|
-
async function findConfigFile(dir, cache) {
|
|
883
|
-
const cached = cache.get(dir);
|
|
884
|
-
if (cached !== void 0) {
|
|
885
|
-
return cached;
|
|
886
940
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
cache.set(dir, candidate);
|
|
891
|
-
return candidate;
|
|
892
|
-
} catch {
|
|
893
|
-
continue;
|
|
894
|
-
}
|
|
941
|
+
const status = resolveStatus(route.status, response.status);
|
|
942
|
+
if (status === response.status && !hasHeaders) {
|
|
943
|
+
return response;
|
|
895
944
|
}
|
|
896
|
-
|
|
897
|
-
return null;
|
|
945
|
+
return new Response(response.body, { status, headers });
|
|
898
946
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return null;
|
|
947
|
+
function resolveResponse(value, fallback) {
|
|
948
|
+
if (value instanceof Response) {
|
|
949
|
+
return value;
|
|
903
950
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
951
|
+
if (value && typeof value === "object" && "res" in value) {
|
|
952
|
+
const resolved = value.res;
|
|
953
|
+
if (resolved instanceof Response) {
|
|
954
|
+
return resolved;
|
|
955
|
+
}
|
|
907
956
|
}
|
|
908
|
-
return
|
|
957
|
+
return fallback;
|
|
909
958
|
}
|
|
910
|
-
function
|
|
911
|
-
if (
|
|
912
|
-
return
|
|
959
|
+
function normalizeHandlerValue(c, value) {
|
|
960
|
+
if (value instanceof Response) {
|
|
961
|
+
return value;
|
|
913
962
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
963
|
+
if (typeof value === "undefined") {
|
|
964
|
+
const response = c.body(null);
|
|
965
|
+
if (response.status === 200) {
|
|
966
|
+
return new Response(response.body, {
|
|
967
|
+
status: 204,
|
|
968
|
+
headers: response.headers
|
|
969
|
+
});
|
|
920
970
|
}
|
|
921
|
-
|
|
971
|
+
return response;
|
|
922
972
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
async function resolveDirectoryConfig(params) {
|
|
926
|
-
const { file, rootDir, server, logger, configCache, fileCache } = params;
|
|
927
|
-
const resolvedRoot = normalize(rootDir);
|
|
928
|
-
const resolvedFileDir = normalize(dirname(file));
|
|
929
|
-
const chain = [];
|
|
930
|
-
let current = resolvedFileDir;
|
|
931
|
-
while (true) {
|
|
932
|
-
chain.push(current);
|
|
933
|
-
if (current === resolvedRoot) {
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
|
-
const parent = dirname(current);
|
|
937
|
-
if (parent === current) {
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
current = parent;
|
|
973
|
+
if (typeof value === "string") {
|
|
974
|
+
return c.text(value);
|
|
941
975
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
const configPath = await findConfigFile(dir, fileCache);
|
|
946
|
-
if (!configPath) {
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
let config = configCache.get(configPath);
|
|
950
|
-
if (config === void 0) {
|
|
951
|
-
config = await loadConfig(configPath, server);
|
|
952
|
-
configCache.set(configPath, config);
|
|
953
|
-
}
|
|
954
|
-
if (!config) {
|
|
955
|
-
logger.warn(`Invalid config in ${configPath}`);
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
if (config.headers) {
|
|
959
|
-
merged.headers = { ...merged.headers ?? {}, ...config.headers };
|
|
960
|
-
}
|
|
961
|
-
if (typeof config.status === "number") {
|
|
962
|
-
merged.status = config.status;
|
|
963
|
-
}
|
|
964
|
-
if (typeof config.delay === "number") {
|
|
965
|
-
merged.delay = config.delay;
|
|
966
|
-
}
|
|
967
|
-
if (typeof config.enabled === "boolean") {
|
|
968
|
-
merged.enabled = config.enabled;
|
|
969
|
-
}
|
|
970
|
-
if (typeof config.ignorePrefix !== "undefined") {
|
|
971
|
-
merged.ignorePrefix = config.ignorePrefix;
|
|
972
|
-
}
|
|
973
|
-
if (typeof config.include !== "undefined") {
|
|
974
|
-
merged.include = config.include;
|
|
975
|
-
}
|
|
976
|
-
if (typeof config.exclude !== "undefined") {
|
|
977
|
-
merged.exclude = config.exclude;
|
|
978
|
-
}
|
|
979
|
-
const normalized = normalizeMiddlewares(config.middleware, configPath, logger);
|
|
980
|
-
if (normalized.length > 0) {
|
|
981
|
-
merged.middlewares.push(...normalized);
|
|
976
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
977
|
+
if (!c.res.headers.get("content-type")) {
|
|
978
|
+
c.header("content-type", "application/octet-stream");
|
|
982
979
|
}
|
|
980
|
+
const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
|
|
981
|
+
return c.body(data);
|
|
983
982
|
}
|
|
984
|
-
return
|
|
983
|
+
return c.json(value);
|
|
985
984
|
}
|
|
986
|
-
|
|
987
|
-
async
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
985
|
+
function createRouteHandler(route) {
|
|
986
|
+
return async (c) => {
|
|
987
|
+
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
988
|
+
return normalizeHandlerValue(c, value);
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function createFinalizeMiddleware(route) {
|
|
992
|
+
return async (c, next) => {
|
|
993
|
+
const response = await next();
|
|
994
|
+
const resolved = resolveResponse(response, c.res);
|
|
995
|
+
if (route.delay && route.delay > 0) {
|
|
996
|
+
await delay(route.delay);
|
|
997
997
|
}
|
|
998
|
-
|
|
999
|
-
|
|
998
|
+
const overridden = applyRouteOverrides(resolved, route);
|
|
999
|
+
c.res = overridden;
|
|
1000
|
+
return overridden;
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function wrapMiddleware(handler) {
|
|
1004
|
+
return async (c, next) => {
|
|
1005
|
+
const response = await handler(c, next);
|
|
1006
|
+
return resolveResponse(response, c.res);
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
function splitRouteMiddlewares(route) {
|
|
1010
|
+
const before = [];
|
|
1011
|
+
const normal = [];
|
|
1012
|
+
const after = [];
|
|
1013
|
+
for (const entry of route.middlewares ?? []) {
|
|
1014
|
+
const wrapped = wrapMiddleware(entry.handle);
|
|
1015
|
+
if (entry.position === "post") {
|
|
1016
|
+
after.push(wrapped);
|
|
1017
|
+
} else if (entry.position === "pre") {
|
|
1018
|
+
before.push(wrapped);
|
|
1019
|
+
} else {
|
|
1020
|
+
normal.push(wrapped);
|
|
1000
1021
|
}
|
|
1001
1022
|
}
|
|
1023
|
+
return { before, normal, after };
|
|
1002
1024
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1025
|
+
function createHonoApp(routes) {
|
|
1026
|
+
const app = new Hono({ router: new PatternRouter(), strict: false });
|
|
1027
|
+
for (const route of routes) {
|
|
1028
|
+
const { before, normal, after } = splitRouteMiddlewares(route);
|
|
1029
|
+
app.on(
|
|
1030
|
+
route.method,
|
|
1031
|
+
toHonoPath(route),
|
|
1032
|
+
createFinalizeMiddleware(route),
|
|
1033
|
+
...before,
|
|
1034
|
+
...normal,
|
|
1035
|
+
...after,
|
|
1036
|
+
createRouteHandler(route)
|
|
1037
|
+
);
|
|
1009
1038
|
}
|
|
1039
|
+
return app;
|
|
1010
1040
|
}
|
|
1011
|
-
async function
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1041
|
+
async function readRawBody(req) {
|
|
1042
|
+
return await new Promise((resolve, reject) => {
|
|
1043
|
+
const chunks = [];
|
|
1044
|
+
req.on("data", (chunk) => {
|
|
1045
|
+
if (typeof chunk === "string") {
|
|
1046
|
+
chunks.push(Buffer.from(chunk));
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (chunk instanceof Uint8Array) {
|
|
1050
|
+
chunks.push(chunk);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
chunks.push(Buffer.from(String(chunk)));
|
|
1054
|
+
});
|
|
1055
|
+
req.on("end", () => {
|
|
1056
|
+
if (chunks.length === 0) {
|
|
1057
|
+
resolve(null);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
resolve(Buffer.concat(chunks));
|
|
1061
|
+
});
|
|
1062
|
+
req.on("error", reject);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function buildHeaders(headers) {
|
|
1066
|
+
const result = new Headers();
|
|
1067
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1068
|
+
if (typeof value === "undefined") {
|
|
1015
1069
|
continue;
|
|
1016
1070
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (file.endsWith(".d.ts")) {
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
if (isConfigFile(file)) {
|
|
1026
|
-
return false;
|
|
1071
|
+
if (Array.isArray(value)) {
|
|
1072
|
+
result.set(key, value.join(","));
|
|
1073
|
+
} else {
|
|
1074
|
+
result.set(key, value);
|
|
1075
|
+
}
|
|
1027
1076
|
}
|
|
1028
|
-
|
|
1029
|
-
return supportedExtensions.has(ext);
|
|
1077
|
+
return result;
|
|
1030
1078
|
}
|
|
1031
|
-
function
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1079
|
+
async function toRequest(req) {
|
|
1080
|
+
const url = new URL(req.url ?? "/", "http://mokup.local");
|
|
1081
|
+
const method = req.method ?? "GET";
|
|
1082
|
+
const headers = buildHeaders(req.headers);
|
|
1083
|
+
const init = { method, headers };
|
|
1084
|
+
const rawBody = await readRawBody(req);
|
|
1085
|
+
if (rawBody && method !== "GET" && method !== "HEAD") {
|
|
1086
|
+
init.body = rawBody;
|
|
1038
1087
|
}
|
|
1039
|
-
|
|
1040
|
-
return configExtensions.includes(ext);
|
|
1088
|
+
return new Request(url.toString(), init);
|
|
1041
1089
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (ext === ".js" || ext === ".mjs") {
|
|
1051
|
-
return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
|
|
1052
|
-
}
|
|
1053
|
-
if (ext === ".ts") {
|
|
1054
|
-
const result = await build({
|
|
1055
|
-
entryPoints: [file],
|
|
1056
|
-
bundle: true,
|
|
1057
|
-
format: "esm",
|
|
1058
|
-
platform: "node",
|
|
1059
|
-
sourcemap: "inline",
|
|
1060
|
-
target: "es2020",
|
|
1061
|
-
write: false
|
|
1062
|
-
});
|
|
1063
|
-
const output = result.outputFiles[0];
|
|
1064
|
-
const code = output?.text ?? "";
|
|
1065
|
-
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString(
|
|
1066
|
-
"base64"
|
|
1067
|
-
)}`;
|
|
1068
|
-
return import(`${dataUrl}#${Date.now()}`);
|
|
1090
|
+
async function sendResponse(res, response) {
|
|
1091
|
+
res.statusCode = response.status;
|
|
1092
|
+
response.headers.forEach((value, key) => {
|
|
1093
|
+
res.setHeader(key, value);
|
|
1094
|
+
});
|
|
1095
|
+
if (!response.body) {
|
|
1096
|
+
res.end();
|
|
1097
|
+
return;
|
|
1069
1098
|
}
|
|
1070
|
-
|
|
1099
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
1100
|
+
res.end(buffer);
|
|
1071
1101
|
}
|
|
1072
|
-
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
if (moduleNode) {
|
|
1077
|
-
asDevServer.moduleGraph.invalidateModule(moduleNode);
|
|
1078
|
-
}
|
|
1079
|
-
return asDevServer.ssrLoadModule(file);
|
|
1080
|
-
}
|
|
1081
|
-
return loadModule(file);
|
|
1102
|
+
function hasMatch(app, method, pathname) {
|
|
1103
|
+
const matchMethod = method === "HEAD" ? "GET" : method;
|
|
1104
|
+
const match = app.router.match(matchMethod, pathname);
|
|
1105
|
+
return !!match && match[0].length > 0;
|
|
1082
1106
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
allowTrailingComma: true,
|
|
1089
|
-
disallowComments: false
|
|
1090
|
-
});
|
|
1091
|
-
if (errors.length > 0) {
|
|
1092
|
-
logger.warn(`Invalid JSONC in ${file}`);
|
|
1093
|
-
return void 0;
|
|
1107
|
+
function createMiddleware(getApp, logger) {
|
|
1108
|
+
return async (req, res, next) => {
|
|
1109
|
+
const app = getApp();
|
|
1110
|
+
if (!app) {
|
|
1111
|
+
return next();
|
|
1094
1112
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
async function loadRules(file, server, logger) {
|
|
1102
|
-
const ext = extname(file).toLowerCase();
|
|
1103
|
-
if (ext === ".json" || ext === ".jsonc") {
|
|
1104
|
-
const json = await readJsonFile(file, logger);
|
|
1105
|
-
if (typeof json === "undefined") {
|
|
1106
|
-
return [];
|
|
1113
|
+
const url = req.url ?? "/";
|
|
1114
|
+
const parsedUrl = new URL(url, "http://mokup.local");
|
|
1115
|
+
const pathname = parsedUrl.pathname;
|
|
1116
|
+
const method = normalizeMethod(req.method) ?? "GET";
|
|
1117
|
+
if (!hasMatch(app, method, pathname)) {
|
|
1118
|
+
return next();
|
|
1107
1119
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1120
|
+
const startedAt = Date.now();
|
|
1121
|
+
try {
|
|
1122
|
+
const response = await app.fetch(await toRequest(req));
|
|
1123
|
+
if (res.writableEnded) {
|
|
1124
|
+
return;
|
|
1111
1125
|
}
|
|
1112
|
-
|
|
1126
|
+
await sendResponse(res, response);
|
|
1127
|
+
logger.info(`${method} ${pathname} ${Date.now() - startedAt}ms`);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
if (!res.headersSent) {
|
|
1130
|
+
res.statusCode = 500;
|
|
1131
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
1132
|
+
}
|
|
1133
|
+
res.end("Mock handler error");
|
|
1134
|
+
logger.error("Mock handler failed:", error);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const jsonExtensions = /* @__PURE__ */ new Set([".json", ".jsonc"]);
|
|
1140
|
+
function resolveTemplate(template, prefix) {
|
|
1141
|
+
const normalized = template.startsWith("/") ? template : `/${template}`;
|
|
1142
|
+
if (!prefix) {
|
|
1143
|
+
return normalized;
|
|
1113
1144
|
}
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
return [];
|
|
1145
|
+
const normalizedPrefix = normalizePrefix(prefix);
|
|
1146
|
+
if (!normalizedPrefix) {
|
|
1147
|
+
return normalized;
|
|
1118
1148
|
}
|
|
1119
|
-
if (
|
|
1120
|
-
return
|
|
1149
|
+
if (normalized === normalizedPrefix || normalized.startsWith(`${normalizedPrefix}/`)) {
|
|
1150
|
+
return normalized;
|
|
1121
1151
|
}
|
|
1122
|
-
if (
|
|
1123
|
-
return
|
|
1124
|
-
{
|
|
1125
|
-
handler: value
|
|
1126
|
-
}
|
|
1127
|
-
];
|
|
1152
|
+
if (normalized === "/") {
|
|
1153
|
+
return `${normalizedPrefix}/`;
|
|
1128
1154
|
}
|
|
1129
|
-
return
|
|
1155
|
+
return `${normalizedPrefix}${normalized}`;
|
|
1130
1156
|
}
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1157
|
+
function stripMethodSuffix(base) {
|
|
1158
|
+
const segments = base.split(".");
|
|
1159
|
+
const last = segments.at(-1);
|
|
1160
|
+
if (last && methodSuffixSet.has(last.toLowerCase())) {
|
|
1161
|
+
segments.pop();
|
|
1162
|
+
return {
|
|
1163
|
+
name: segments.join("."),
|
|
1164
|
+
method: last.toUpperCase()
|
|
1165
|
+
};
|
|
1138
1166
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1167
|
+
return {
|
|
1168
|
+
name: base,
|
|
1169
|
+
method: void 0
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function deriveRouteFromFile(file, rootDir, logger) {
|
|
1173
|
+
const rel = toPosix(relative(rootDir, file));
|
|
1174
|
+
const ext = extname(rel);
|
|
1175
|
+
const withoutExt = rel.slice(0, rel.length - ext.length);
|
|
1176
|
+
const dir = dirname(withoutExt);
|
|
1177
|
+
const base = basename(withoutExt);
|
|
1178
|
+
const { name, method } = stripMethodSuffix(base);
|
|
1179
|
+
const resolvedMethod = method ?? (jsonExtensions.has(ext) ? "GET" : void 0);
|
|
1180
|
+
if (!resolvedMethod) {
|
|
1181
|
+
logger.warn(`Skip mock without method suffix: ${file}`);
|
|
1143
1182
|
return null;
|
|
1144
1183
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1184
|
+
if (!name) {
|
|
1185
|
+
logger.warn(`Skip mock with empty route name: ${file}`);
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
const joined = dir === "." ? name : join(dir, name);
|
|
1189
|
+
const segments = toPosix(joined).split("/");
|
|
1190
|
+
if (segments.at(-1) === "index") {
|
|
1191
|
+
segments.pop();
|
|
1192
|
+
}
|
|
1193
|
+
const template = segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
1194
|
+
const parsed = parseRouteTemplate(template);
|
|
1195
|
+
if (parsed.errors.length > 0) {
|
|
1196
|
+
for (const error of parsed.errors) {
|
|
1197
|
+
logger.warn(`${error} in ${file}`);
|
|
1198
|
+
}
|
|
1154
1199
|
return null;
|
|
1155
1200
|
}
|
|
1201
|
+
for (const warning of parsed.warnings) {
|
|
1202
|
+
logger.warn(`${warning} in ${file}`);
|
|
1203
|
+
}
|
|
1156
1204
|
return {
|
|
1157
|
-
|
|
1158
|
-
|
|
1205
|
+
template: parsed.template,
|
|
1206
|
+
method: resolvedMethod,
|
|
1207
|
+
tokens: parsed.tokens,
|
|
1208
|
+
score: parsed.score
|
|
1159
1209
|
};
|
|
1160
1210
|
}
|
|
1161
|
-
function
|
|
1162
|
-
const
|
|
1163
|
-
if (
|
|
1164
|
-
|
|
1165
|
-
|
|
1211
|
+
function resolveRule(params) {
|
|
1212
|
+
const method = params.derivedMethod;
|
|
1213
|
+
if (!method) {
|
|
1214
|
+
params.logger.warn(`Skip mock without method suffix: ${params.file}`);
|
|
1215
|
+
return null;
|
|
1166
1216
|
}
|
|
1167
|
-
|
|
1217
|
+
const template = resolveTemplate(params.derivedTemplate, params.prefix);
|
|
1218
|
+
const parsed = parseRouteTemplate(template);
|
|
1219
|
+
if (parsed.errors.length > 0) {
|
|
1220
|
+
for (const error of parsed.errors) {
|
|
1221
|
+
params.logger.warn(`${error} in ${params.file}`);
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
for (const warning of parsed.warnings) {
|
|
1226
|
+
params.logger.warn(`${warning} in ${params.file}`);
|
|
1227
|
+
}
|
|
1228
|
+
const route = {
|
|
1229
|
+
file: params.file,
|
|
1230
|
+
template: parsed.template,
|
|
1231
|
+
method,
|
|
1232
|
+
tokens: parsed.tokens,
|
|
1233
|
+
score: parsed.score,
|
|
1234
|
+
handler: params.rule.handler
|
|
1235
|
+
};
|
|
1236
|
+
if (typeof params.rule.status === "number") {
|
|
1237
|
+
route.status = params.rule.status;
|
|
1238
|
+
}
|
|
1239
|
+
if (params.rule.headers) {
|
|
1240
|
+
route.headers = params.rule.headers;
|
|
1241
|
+
}
|
|
1242
|
+
if (typeof params.rule.delay === "number") {
|
|
1243
|
+
route.delay = params.rule.delay;
|
|
1244
|
+
}
|
|
1245
|
+
return route;
|
|
1168
1246
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const globalIgnorePrefix = normalizeIgnorePrefix(params.ignorePrefix);
|
|
1174
|
-
const configCache = /* @__PURE__ */ new Map();
|
|
1175
|
-
const fileCache = /* @__PURE__ */ new Map();
|
|
1176
|
-
const shouldCollectSkip = typeof params.onSkip === "function";
|
|
1177
|
-
const shouldCollectIgnore = typeof params.onIgnore === "function";
|
|
1178
|
-
const shouldCollectConfig = typeof params.onConfig === "function";
|
|
1179
|
-
for (const fileInfo of files) {
|
|
1180
|
-
if (isConfigFile(fileInfo.file)) {
|
|
1181
|
-
if (shouldCollectConfig) {
|
|
1182
|
-
const config2 = await resolveDirectoryConfig({
|
|
1183
|
-
file: fileInfo.file,
|
|
1184
|
-
rootDir: fileInfo.rootDir,
|
|
1185
|
-
server: params.server,
|
|
1186
|
-
logger: params.logger,
|
|
1187
|
-
configCache,
|
|
1188
|
-
fileCache
|
|
1189
|
-
});
|
|
1190
|
-
params.onConfig?.({ file: fileInfo.file, enabled: config2.enabled !== false });
|
|
1191
|
-
}
|
|
1192
|
-
continue;
|
|
1247
|
+
function sortRoutes(routes) {
|
|
1248
|
+
return routes.sort((a, b) => {
|
|
1249
|
+
if (a.method !== b.method) {
|
|
1250
|
+
return a.method.localeCompare(b.method);
|
|
1193
1251
|
}
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
logger: params.logger,
|
|
1198
|
-
configCache,
|
|
1199
|
-
fileCache
|
|
1200
|
-
};
|
|
1201
|
-
if (params.server) {
|
|
1202
|
-
configParams.server = params.server;
|
|
1252
|
+
const scoreCompare = compareRouteScore(a.score, b.score);
|
|
1253
|
+
if (scoreCompare !== 0) {
|
|
1254
|
+
return scoreCompare;
|
|
1203
1255
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
params.onSkip?.(buildSkipInfo(fileInfo.file, "disabled-dir", resolved));
|
|
1213
|
-
}
|
|
1256
|
+
return a.template.localeCompare(b.template);
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function walkDir(dir, rootDir, files) {
|
|
1261
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
1262
|
+
for (const entry of entries) {
|
|
1263
|
+
if (entry.name === "node_modules" || entry.name === ".git") {
|
|
1214
1264
|
continue;
|
|
1215
1265
|
}
|
|
1216
|
-
const
|
|
1217
|
-
if (
|
|
1218
|
-
|
|
1219
|
-
const resolved = resolveSkipRoute({
|
|
1220
|
-
file: fileInfo.file,
|
|
1221
|
-
rootDir: fileInfo.rootDir,
|
|
1222
|
-
prefix: params.prefix
|
|
1223
|
-
});
|
|
1224
|
-
params.onSkip?.(buildSkipInfo(fileInfo.file, "ignore-prefix", resolved));
|
|
1225
|
-
}
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
if (!isSupportedFile(fileInfo.file)) {
|
|
1229
|
-
if (shouldCollectIgnore) {
|
|
1230
|
-
params.onIgnore?.({ file: fileInfo.file, reason: "unsupported" });
|
|
1231
|
-
}
|
|
1266
|
+
const fullPath = join(dir, entry.name);
|
|
1267
|
+
if (entry.isDirectory()) {
|
|
1268
|
+
await walkDir(fullPath, rootDir, files);
|
|
1232
1269
|
continue;
|
|
1233
1270
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
if (!matchesFilter(fileInfo.file, effectiveInclude, effectiveExclude)) {
|
|
1237
|
-
if (shouldCollectSkip) {
|
|
1238
|
-
const resolved = resolveSkipRoute({
|
|
1239
|
-
file: fileInfo.file,
|
|
1240
|
-
rootDir: fileInfo.rootDir,
|
|
1241
|
-
prefix: params.prefix
|
|
1242
|
-
});
|
|
1243
|
-
const reason = effectiveExclude && matchesFilter(fileInfo.file, void 0, effectiveExclude) ? "exclude" : "include";
|
|
1244
|
-
params.onSkip?.(buildSkipInfo(fileInfo.file, reason, resolved));
|
|
1245
|
-
}
|
|
1246
|
-
continue;
|
|
1271
|
+
if (entry.isFile()) {
|
|
1272
|
+
files.push({ file: fullPath, rootDir });
|
|
1247
1273
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
async function exists(path) {
|
|
1277
|
+
try {
|
|
1278
|
+
await promises.stat(path);
|
|
1279
|
+
return true;
|
|
1280
|
+
} catch {
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
async function collectFiles(dirs) {
|
|
1285
|
+
const files = [];
|
|
1286
|
+
for (const dir of dirs) {
|
|
1287
|
+
if (!await exists(dir)) {
|
|
1253
1288
|
continue;
|
|
1254
1289
|
}
|
|
1255
|
-
|
|
1256
|
-
for (const [index, rule] of rules.entries()) {
|
|
1257
|
-
if (!rule || typeof rule !== "object") {
|
|
1258
|
-
continue;
|
|
1259
|
-
}
|
|
1260
|
-
if (rule.enabled === false) {
|
|
1261
|
-
if (shouldCollectSkip) {
|
|
1262
|
-
const resolved2 = resolveSkipRoute({
|
|
1263
|
-
file: fileInfo.file,
|
|
1264
|
-
rootDir: fileInfo.rootDir,
|
|
1265
|
-
prefix: params.prefix,
|
|
1266
|
-
derived
|
|
1267
|
-
});
|
|
1268
|
-
params.onSkip?.(buildSkipInfo(fileInfo.file, "disabled", resolved2));
|
|
1269
|
-
}
|
|
1270
|
-
continue;
|
|
1271
|
-
}
|
|
1272
|
-
const ruleValue = rule;
|
|
1273
|
-
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
1274
|
-
(key2) => key2 in ruleValue
|
|
1275
|
-
);
|
|
1276
|
-
if (unsupportedKeys.length > 0) {
|
|
1277
|
-
params.logger.warn(
|
|
1278
|
-
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
1279
|
-
);
|
|
1280
|
-
continue;
|
|
1281
|
-
}
|
|
1282
|
-
if (typeof rule.handler === "undefined") {
|
|
1283
|
-
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
const resolved = resolveRule({
|
|
1287
|
-
rule,
|
|
1288
|
-
derivedTemplate: derived.template,
|
|
1289
|
-
derivedMethod: derived.method,
|
|
1290
|
-
prefix: params.prefix,
|
|
1291
|
-
file: fileInfo.file,
|
|
1292
|
-
logger: params.logger
|
|
1293
|
-
});
|
|
1294
|
-
if (!resolved) {
|
|
1295
|
-
continue;
|
|
1296
|
-
}
|
|
1297
|
-
resolved.ruleIndex = index;
|
|
1298
|
-
if (config.headers) {
|
|
1299
|
-
resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
|
|
1300
|
-
}
|
|
1301
|
-
if (typeof resolved.status === "undefined" && typeof config.status === "number") {
|
|
1302
|
-
resolved.status = config.status;
|
|
1303
|
-
}
|
|
1304
|
-
if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
|
|
1305
|
-
resolved.delay = config.delay;
|
|
1306
|
-
}
|
|
1307
|
-
if (config.middlewares.length > 0) {
|
|
1308
|
-
resolved.middlewares = config.middlewares;
|
|
1309
|
-
}
|
|
1310
|
-
const key = `${resolved.method} ${resolved.template}`;
|
|
1311
|
-
if (seen.has(key)) {
|
|
1312
|
-
params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
|
|
1313
|
-
}
|
|
1314
|
-
seen.add(key);
|
|
1315
|
-
routes.push(resolved);
|
|
1316
|
-
}
|
|
1290
|
+
await walkDir(dir, dir, files);
|
|
1317
1291
|
}
|
|
1318
|
-
return
|
|
1292
|
+
return files;
|
|
1319
1293
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
function normalizeSwPath(path) {
|
|
1324
|
-
if (!path) {
|
|
1325
|
-
return defaultSwPath;
|
|
1294
|
+
function isSupportedFile(file) {
|
|
1295
|
+
if (file.endsWith(".d.ts")) {
|
|
1296
|
+
return false;
|
|
1326
1297
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
function normalizeSwScope(scope) {
|
|
1330
|
-
if (!scope) {
|
|
1331
|
-
return defaultSwScope;
|
|
1298
|
+
if (isConfigFile(file)) {
|
|
1299
|
+
return false;
|
|
1332
1300
|
}
|
|
1333
|
-
|
|
1301
|
+
const ext = extname(file).toLowerCase();
|
|
1302
|
+
return supportedExtensions.has(ext);
|
|
1334
1303
|
}
|
|
1335
|
-
function
|
|
1336
|
-
if (
|
|
1337
|
-
return
|
|
1304
|
+
function isConfigFile(file) {
|
|
1305
|
+
if (file.endsWith(".d.ts")) {
|
|
1306
|
+
return false;
|
|
1338
1307
|
}
|
|
1339
|
-
const
|
|
1340
|
-
if (
|
|
1341
|
-
return
|
|
1308
|
+
const base = basename(file);
|
|
1309
|
+
if (!base.startsWith("index.config.")) {
|
|
1310
|
+
return false;
|
|
1342
1311
|
}
|
|
1343
|
-
|
|
1312
|
+
const ext = extname(file).toLowerCase();
|
|
1313
|
+
return configExtensions.includes(ext);
|
|
1344
1314
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
path = next;
|
|
1361
|
-
hasPath = true;
|
|
1362
|
-
} else if (path !== next) {
|
|
1363
|
-
logger.warn(`SW path "${next}" ignored; using "${path}".`);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
if (config?.scope) {
|
|
1367
|
-
const next = normalizeSwScope(config.scope);
|
|
1368
|
-
if (!hasScope) {
|
|
1369
|
-
scope = next;
|
|
1370
|
-
hasScope = true;
|
|
1371
|
-
} else if (scope !== next) {
|
|
1372
|
-
logger.warn(`SW scope "${next}" ignored; using "${scope}".`);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
if (typeof config?.register === "boolean") {
|
|
1376
|
-
if (!hasRegister) {
|
|
1377
|
-
register = config.register;
|
|
1378
|
-
hasRegister = true;
|
|
1379
|
-
} else if (register !== config.register) {
|
|
1380
|
-
logger.warn(
|
|
1381
|
-
`SW register="${String(config.register)}" ignored; using "${String(register)}".`
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
if (typeof config?.unregister === "boolean") {
|
|
1386
|
-
if (!hasUnregister) {
|
|
1387
|
-
unregister = config.unregister;
|
|
1388
|
-
hasUnregister = true;
|
|
1389
|
-
} else if (unregister !== config.unregister) {
|
|
1390
|
-
logger.warn(
|
|
1391
|
-
`SW unregister="${String(config.unregister)}" ignored; using "${String(unregister)}".`
|
|
1392
|
-
);
|
|
1315
|
+
|
|
1316
|
+
const sourceRoot = dirname(fileURLToPath(import.meta.url));
|
|
1317
|
+
const mokupSourceEntry = resolve(sourceRoot, "../index.ts");
|
|
1318
|
+
const mokupViteSourceEntry = resolve(sourceRoot, "../vite.ts");
|
|
1319
|
+
const hasMokupSourceEntry = existsSync(mokupSourceEntry);
|
|
1320
|
+
const hasMokupViteSourceEntry = existsSync(mokupViteSourceEntry);
|
|
1321
|
+
function createWorkspaceResolvePlugin() {
|
|
1322
|
+
if (!hasMokupSourceEntry && !hasMokupViteSourceEntry) {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
name: "mokup:resolve-workspace",
|
|
1327
|
+
setup(build) {
|
|
1328
|
+
if (hasMokupSourceEntry) {
|
|
1329
|
+
build.onResolve({ filter: /^mokup$/ }, () => ({ path: mokupSourceEntry }));
|
|
1393
1330
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const values = Array.isArray(config.basePath) ? config.basePath : [config.basePath];
|
|
1397
|
-
for (const value of values) {
|
|
1398
|
-
basePaths.push(normalizeBasePath(value));
|
|
1331
|
+
if (hasMokupViteSourceEntry) {
|
|
1332
|
+
build.onResolve({ filter: /^mokup\/vite$/ }, () => ({ path: mokupViteSourceEntry }));
|
|
1399
1333
|
}
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
const normalizedPrefix = normalizePrefix(entry.prefix ?? "");
|
|
1403
|
-
if (normalizedPrefix) {
|
|
1404
|
-
basePaths.push(normalizedPrefix);
|
|
1405
1334
|
}
|
|
1406
|
-
}
|
|
1407
|
-
return {
|
|
1408
|
-
path,
|
|
1409
|
-
scope,
|
|
1410
|
-
register,
|
|
1411
|
-
unregister,
|
|
1412
|
-
basePaths: Array.from(new Set(basePaths))
|
|
1413
1335
|
};
|
|
1414
1336
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
function resolveSwUnregisterConfig(options, logger) {
|
|
1423
|
-
return resolveSwConfigFromEntries(options, logger);
|
|
1424
|
-
}
|
|
1425
|
-
function toViteImportPath(file, root) {
|
|
1426
|
-
const absolute = isAbsolute(file) ? file : resolve(root, file);
|
|
1427
|
-
const rel = relative(root, absolute);
|
|
1428
|
-
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
1429
|
-
return `/${toPosix(rel)}`;
|
|
1337
|
+
const workspaceResolvePlugin = createWorkspaceResolvePlugin();
|
|
1338
|
+
async function loadModule(file) {
|
|
1339
|
+
const ext = extname(file).toLowerCase();
|
|
1340
|
+
if (ext === ".cjs") {
|
|
1341
|
+
const require = createRequire(import.meta.url);
|
|
1342
|
+
delete require.cache[file];
|
|
1343
|
+
return require(file);
|
|
1430
1344
|
}
|
|
1431
|
-
|
|
1432
|
-
}
|
|
1433
|
-
function shouldModuleize(handler) {
|
|
1434
|
-
if (typeof handler === "function") {
|
|
1435
|
-
return true;
|
|
1345
|
+
if (ext === ".js" || ext === ".mjs") {
|
|
1346
|
+
return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
|
|
1436
1347
|
}
|
|
1437
|
-
if (
|
|
1438
|
-
|
|
1348
|
+
if (ext === ".ts") {
|
|
1349
|
+
const result = await build({
|
|
1350
|
+
entryPoints: [file],
|
|
1351
|
+
bundle: true,
|
|
1352
|
+
format: "esm",
|
|
1353
|
+
platform: "node",
|
|
1354
|
+
sourcemap: "inline",
|
|
1355
|
+
target: "es2020",
|
|
1356
|
+
write: false,
|
|
1357
|
+
...workspaceResolvePlugin ? { plugins: [workspaceResolvePlugin] } : {}
|
|
1358
|
+
});
|
|
1359
|
+
const output = result.outputFiles[0];
|
|
1360
|
+
const code = output?.text ?? "";
|
|
1361
|
+
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString(
|
|
1362
|
+
"base64"
|
|
1363
|
+
)}`;
|
|
1364
|
+
return import(`${dataUrl}#${Date.now()}`);
|
|
1439
1365
|
}
|
|
1440
|
-
return
|
|
1366
|
+
return null;
|
|
1441
1367
|
}
|
|
1442
|
-
function
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1368
|
+
async function loadModuleWithVite(server, file) {
|
|
1369
|
+
const asDevServer = server;
|
|
1370
|
+
if ("ssrLoadModule" in asDevServer) {
|
|
1371
|
+
const moduleNode = asDevServer.moduleGraph.getModuleById(file);
|
|
1372
|
+
if (moduleNode) {
|
|
1373
|
+
asDevServer.moduleGraph.invalidateModule(moduleNode);
|
|
1374
|
+
}
|
|
1375
|
+
return asDevServer.ssrLoadModule(file);
|
|
1376
|
+
}
|
|
1377
|
+
return loadModule(file);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const middlewareSymbol = Symbol.for("mokup.config.middlewares");
|
|
1381
|
+
function getConfigFileCandidates(dir) {
|
|
1382
|
+
return configExtensions.map((extension) => join(dir, `index.config${extension}`));
|
|
1383
|
+
}
|
|
1384
|
+
async function findConfigFile(dir, cache) {
|
|
1385
|
+
const cached = cache.get(dir);
|
|
1386
|
+
if (cached !== void 0) {
|
|
1387
|
+
return cached;
|
|
1448
1388
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1389
|
+
for (const candidate of getConfigFileCandidates(dir)) {
|
|
1390
|
+
try {
|
|
1391
|
+
await promises.stat(candidate);
|
|
1392
|
+
cache.set(dir, candidate);
|
|
1393
|
+
return candidate;
|
|
1394
|
+
} catch {
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1451
1397
|
}
|
|
1398
|
+
cache.set(dir, null);
|
|
1452
1399
|
return null;
|
|
1453
1400
|
}
|
|
1454
|
-
function
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
module: moduleId
|
|
1459
|
-
};
|
|
1460
|
-
if (typeof route.ruleIndex === "number") {
|
|
1461
|
-
response.ruleIndex = route.ruleIndex;
|
|
1462
|
-
}
|
|
1463
|
-
return response;
|
|
1401
|
+
async function loadConfig(file, server) {
|
|
1402
|
+
const mod = server ? await loadModuleWithVite(server, file) : await loadModule(file);
|
|
1403
|
+
if (!mod) {
|
|
1404
|
+
return null;
|
|
1464
1405
|
}
|
|
1465
|
-
const
|
|
1466
|
-
if (typeof
|
|
1467
|
-
return
|
|
1468
|
-
type: "text",
|
|
1469
|
-
body: handler
|
|
1470
|
-
};
|
|
1406
|
+
const value = mod?.default ?? mod;
|
|
1407
|
+
if (!value || typeof value !== "object") {
|
|
1408
|
+
return null;
|
|
1471
1409
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1410
|
+
return value;
|
|
1411
|
+
}
|
|
1412
|
+
function normalizeMiddlewares(value, source, logger, position) {
|
|
1413
|
+
if (!value) {
|
|
1414
|
+
return [];
|
|
1415
|
+
}
|
|
1416
|
+
const list = Array.isArray(value) ? value : [value];
|
|
1417
|
+
const middlewares = [];
|
|
1418
|
+
for (const [index, entry] of list.entries()) {
|
|
1419
|
+
if (typeof entry !== "function") {
|
|
1420
|
+
logger.warn(`Invalid middleware in ${source}`);
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
middlewares.push({
|
|
1424
|
+
handle: entry,
|
|
1425
|
+
source,
|
|
1426
|
+
index,
|
|
1427
|
+
position
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
return middlewares;
|
|
1431
|
+
}
|
|
1432
|
+
function readMiddlewareMeta(config) {
|
|
1433
|
+
const value = config[middlewareSymbol];
|
|
1434
|
+
if (!value || typeof value !== "object") {
|
|
1435
|
+
return null;
|
|
1479
1436
|
}
|
|
1437
|
+
const meta = value;
|
|
1480
1438
|
return {
|
|
1481
|
-
|
|
1482
|
-
|
|
1439
|
+
pre: Array.isArray(meta.pre) ? meta.pre : [],
|
|
1440
|
+
normal: Array.isArray(meta.normal) ? meta.normal : [],
|
|
1441
|
+
post: Array.isArray(meta.post) ? meta.post : []
|
|
1483
1442
|
};
|
|
1484
1443
|
}
|
|
1485
|
-
function
|
|
1486
|
-
const {
|
|
1487
|
-
const
|
|
1488
|
-
const
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
ruleModules.set(moduleId, moduleId);
|
|
1444
|
+
async function resolveDirectoryConfig(params) {
|
|
1445
|
+
const { file, rootDir, server, logger, configCache, fileCache } = params;
|
|
1446
|
+
const resolvedRoot = normalize(rootDir);
|
|
1447
|
+
const resolvedFileDir = normalize(dirname(file));
|
|
1448
|
+
const chain = [];
|
|
1449
|
+
let current = resolvedFileDir;
|
|
1450
|
+
while (true) {
|
|
1451
|
+
chain.push(current);
|
|
1452
|
+
if (current === resolvedRoot) {
|
|
1453
|
+
break;
|
|
1496
1454
|
}
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
ruleIndex: entry.index
|
|
1503
|
-
};
|
|
1504
|
-
});
|
|
1505
|
-
const response = buildManifestResponse(route, moduleId);
|
|
1506
|
-
const manifestRoute = {
|
|
1507
|
-
method: route.method,
|
|
1508
|
-
url: route.template,
|
|
1509
|
-
...route.tokens ? { tokens: route.tokens } : {},
|
|
1510
|
-
...route.score ? { score: route.score } : {},
|
|
1511
|
-
...route.status ? { status: route.status } : {},
|
|
1512
|
-
...route.headers ? { headers: route.headers } : {},
|
|
1513
|
-
...route.delay ? { delay: route.delay } : {},
|
|
1514
|
-
...middleware && middleware.length > 0 ? { middleware } : {},
|
|
1515
|
-
response
|
|
1516
|
-
};
|
|
1517
|
-
return manifestRoute;
|
|
1518
|
-
});
|
|
1519
|
-
const manifest = {
|
|
1520
|
-
version: 1,
|
|
1521
|
-
routes: manifestRoutes
|
|
1522
|
-
};
|
|
1523
|
-
const imports = [
|
|
1524
|
-
`import { createRuntimeApp, handle } from ${JSON.stringify(runtimeImportPath)}`
|
|
1525
|
-
];
|
|
1526
|
-
const moduleEntries = [];
|
|
1527
|
-
let moduleIndex = 0;
|
|
1528
|
-
for (const id of ruleModules.keys()) {
|
|
1529
|
-
const name = `module${moduleIndex++}`;
|
|
1530
|
-
imports.push(`import * as ${name} from '${id}'`);
|
|
1531
|
-
moduleEntries.push({ id, name, kind: "rule" });
|
|
1532
|
-
}
|
|
1533
|
-
for (const id of middlewareModules.keys()) {
|
|
1534
|
-
const name = `module${moduleIndex++}`;
|
|
1535
|
-
imports.push(`import * as ${name} from '${id}'`);
|
|
1536
|
-
moduleEntries.push({ id, name, kind: "middleware" });
|
|
1455
|
+
const parent = dirname(current);
|
|
1456
|
+
if (parent === current) {
|
|
1457
|
+
break;
|
|
1458
|
+
}
|
|
1459
|
+
current = parent;
|
|
1537
1460
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
"
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
"
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1461
|
+
chain.reverse();
|
|
1462
|
+
const merged = {};
|
|
1463
|
+
const preMiddlewares = [];
|
|
1464
|
+
const normalMiddlewares = [];
|
|
1465
|
+
const postMiddlewares = [];
|
|
1466
|
+
const configChain = [];
|
|
1467
|
+
const configSources = {};
|
|
1468
|
+
for (const dir of chain) {
|
|
1469
|
+
const configPath = await findConfigFile(dir, fileCache);
|
|
1470
|
+
if (!configPath) {
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
let config = configCache.get(configPath);
|
|
1474
|
+
if (config === void 0) {
|
|
1475
|
+
config = await loadConfig(configPath, server);
|
|
1476
|
+
configCache.set(configPath, config);
|
|
1477
|
+
}
|
|
1478
|
+
if (!config) {
|
|
1479
|
+
logger.warn(`Invalid config in ${configPath}`);
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
configChain.push(configPath);
|
|
1483
|
+
if (config.headers) {
|
|
1484
|
+
merged.headers = { ...merged.headers ?? {}, ...config.headers };
|
|
1485
|
+
configSources.headers = configPath;
|
|
1486
|
+
}
|
|
1487
|
+
if (typeof config.status === "number") {
|
|
1488
|
+
merged.status = config.status;
|
|
1489
|
+
configSources.status = configPath;
|
|
1490
|
+
}
|
|
1491
|
+
if (typeof config.delay === "number") {
|
|
1492
|
+
merged.delay = config.delay;
|
|
1493
|
+
configSources.delay = configPath;
|
|
1494
|
+
}
|
|
1495
|
+
if (typeof config.enabled === "boolean") {
|
|
1496
|
+
merged.enabled = config.enabled;
|
|
1497
|
+
configSources.enabled = configPath;
|
|
1498
|
+
}
|
|
1499
|
+
if (typeof config.ignorePrefix !== "undefined") {
|
|
1500
|
+
merged.ignorePrefix = config.ignorePrefix;
|
|
1501
|
+
configSources.ignorePrefix = configPath;
|
|
1502
|
+
}
|
|
1503
|
+
if (typeof config.include !== "undefined") {
|
|
1504
|
+
merged.include = config.include;
|
|
1505
|
+
configSources.include = configPath;
|
|
1506
|
+
}
|
|
1507
|
+
if (typeof config.exclude !== "undefined") {
|
|
1508
|
+
merged.exclude = config.exclude;
|
|
1509
|
+
configSources.exclude = configPath;
|
|
1510
|
+
}
|
|
1511
|
+
const meta = readMiddlewareMeta(config);
|
|
1512
|
+
const normalizedPre = normalizeMiddlewares(
|
|
1513
|
+
meta?.pre,
|
|
1514
|
+
configPath,
|
|
1515
|
+
logger,
|
|
1516
|
+
"pre"
|
|
1517
|
+
);
|
|
1518
|
+
const normalizedNormal = normalizeMiddlewares(
|
|
1519
|
+
meta?.normal,
|
|
1520
|
+
configPath,
|
|
1521
|
+
logger,
|
|
1522
|
+
"normal"
|
|
1523
|
+
);
|
|
1524
|
+
const normalizedLegacy = normalizeMiddlewares(
|
|
1525
|
+
config.middleware,
|
|
1526
|
+
configPath,
|
|
1527
|
+
logger,
|
|
1528
|
+
"normal"
|
|
1529
|
+
);
|
|
1530
|
+
const normalizedPost = normalizeMiddlewares(
|
|
1531
|
+
meta?.post,
|
|
1532
|
+
configPath,
|
|
1533
|
+
logger,
|
|
1534
|
+
"post"
|
|
1535
|
+
);
|
|
1536
|
+
if (normalizedPre.length > 0) {
|
|
1537
|
+
preMiddlewares.push(...normalizedPre);
|
|
1538
|
+
}
|
|
1539
|
+
if (normalizedNormal.length > 0) {
|
|
1540
|
+
normalMiddlewares.push(...normalizedNormal);
|
|
1541
|
+
}
|
|
1542
|
+
if (normalizedLegacy.length > 0) {
|
|
1543
|
+
normalMiddlewares.push(...normalizedLegacy);
|
|
1544
|
+
}
|
|
1545
|
+
if (normalizedPost.length > 0) {
|
|
1546
|
+
postMiddlewares.push(...normalizedPost);
|
|
1599
1547
|
}
|
|
1600
|
-
lines.push("}", "");
|
|
1601
1548
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
"
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
"
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1549
|
+
return {
|
|
1550
|
+
...merged,
|
|
1551
|
+
middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares],
|
|
1552
|
+
configChain,
|
|
1553
|
+
configSources
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
async function readJsonFile(file, logger) {
|
|
1558
|
+
try {
|
|
1559
|
+
const content = await promises.readFile(file, "utf8");
|
|
1560
|
+
const errors = [];
|
|
1561
|
+
const data = parse(content, errors, {
|
|
1562
|
+
allowTrailingComma: true,
|
|
1563
|
+
disallowComments: false
|
|
1564
|
+
});
|
|
1565
|
+
if (errors.length > 0) {
|
|
1566
|
+
logger.warn(`Invalid JSONC in ${file}`);
|
|
1567
|
+
return void 0;
|
|
1568
|
+
}
|
|
1569
|
+
return data;
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
logger.warn(`Failed to read ${file}: ${String(error)}`);
|
|
1572
|
+
return void 0;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
async function loadRules(file, server, logger) {
|
|
1576
|
+
const ext = extname(file).toLowerCase();
|
|
1577
|
+
if (ext === ".json" || ext === ".jsonc") {
|
|
1578
|
+
const json = await readJsonFile(file, logger);
|
|
1579
|
+
if (typeof json === "undefined") {
|
|
1580
|
+
return [];
|
|
1581
|
+
}
|
|
1582
|
+
return [
|
|
1583
|
+
{
|
|
1584
|
+
handler: json
|
|
1585
|
+
}
|
|
1586
|
+
];
|
|
1587
|
+
}
|
|
1588
|
+
const mod = server ? await loadModuleWithVite(server, file) : await loadModule(file);
|
|
1589
|
+
const value = mod?.default ?? mod;
|
|
1590
|
+
if (!value) {
|
|
1591
|
+
return [];
|
|
1592
|
+
}
|
|
1593
|
+
if (Array.isArray(value)) {
|
|
1594
|
+
return value;
|
|
1595
|
+
}
|
|
1596
|
+
if (typeof value === "function") {
|
|
1597
|
+
return [
|
|
1598
|
+
{
|
|
1599
|
+
handler: value
|
|
1600
|
+
}
|
|
1601
|
+
];
|
|
1602
|
+
}
|
|
1603
|
+
return [value];
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const silentLogger = {
|
|
1607
|
+
info: () => {
|
|
1608
|
+
},
|
|
1609
|
+
warn: () => {
|
|
1610
|
+
},
|
|
1611
|
+
error: () => {
|
|
1612
|
+
},
|
|
1613
|
+
log: () => {
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
function resolveSkipRoute(params) {
|
|
1617
|
+
const derived = params.derived ?? deriveRouteFromFile(params.file, params.rootDir, silentLogger);
|
|
1618
|
+
if (!derived?.method) {
|
|
1619
|
+
return null;
|
|
1620
|
+
}
|
|
1621
|
+
const resolved = resolveRule({
|
|
1622
|
+
rule: { handler: null },
|
|
1623
|
+
derivedTemplate: derived.template,
|
|
1624
|
+
derivedMethod: derived.method,
|
|
1625
|
+
prefix: params.prefix,
|
|
1626
|
+
file: params.file,
|
|
1627
|
+
logger: silentLogger
|
|
1628
|
+
});
|
|
1629
|
+
if (!resolved) {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
method: resolved.method,
|
|
1634
|
+
url: resolved.template
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function buildSkipInfo(file, reason, resolved, configChain, decisionChain, effectiveConfig) {
|
|
1638
|
+
const info = { file, reason };
|
|
1639
|
+
if (resolved) {
|
|
1640
|
+
info.method = resolved.method;
|
|
1641
|
+
info.url = resolved.url;
|
|
1642
|
+
}
|
|
1643
|
+
if (configChain && configChain.length > 0) {
|
|
1644
|
+
info.configChain = configChain;
|
|
1645
|
+
}
|
|
1646
|
+
if (decisionChain && decisionChain.length > 0) {
|
|
1647
|
+
info.decisionChain = decisionChain;
|
|
1648
|
+
}
|
|
1649
|
+
if (effectiveConfig && Object.keys(effectiveConfig).length > 0) {
|
|
1650
|
+
info.effectiveConfig = effectiveConfig;
|
|
1651
|
+
}
|
|
1652
|
+
return info;
|
|
1653
|
+
}
|
|
1654
|
+
function toFilterStrings(value) {
|
|
1655
|
+
if (!value) {
|
|
1656
|
+
return [];
|
|
1657
|
+
}
|
|
1658
|
+
const list = Array.isArray(value) ? value : [value];
|
|
1659
|
+
return list.filter((entry) => entry instanceof RegExp).map((entry) => entry.toString());
|
|
1660
|
+
}
|
|
1661
|
+
function toStringList(value) {
|
|
1662
|
+
return value.length === 1 ? value[0] : [...value];
|
|
1663
|
+
}
|
|
1664
|
+
function formatList(value) {
|
|
1665
|
+
return value.join(", ");
|
|
1666
|
+
}
|
|
1667
|
+
function testPatterns(patterns, value) {
|
|
1668
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
1669
|
+
return list.some((pattern) => pattern.test(value));
|
|
1670
|
+
}
|
|
1671
|
+
function buildEffectiveConfig(params) {
|
|
1672
|
+
const { config, effectiveInclude, effectiveExclude, effectiveIgnorePrefix } = params;
|
|
1673
|
+
const includeList = toFilterStrings(effectiveInclude);
|
|
1674
|
+
const excludeList = toFilterStrings(effectiveExclude);
|
|
1675
|
+
const effectiveConfig = {};
|
|
1676
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
1677
|
+
effectiveConfig.headers = config.headers;
|
|
1678
|
+
}
|
|
1679
|
+
if (typeof config.status === "number") {
|
|
1680
|
+
effectiveConfig.status = config.status;
|
|
1681
|
+
}
|
|
1682
|
+
if (typeof config.delay === "number") {
|
|
1683
|
+
effectiveConfig.delay = config.delay;
|
|
1684
|
+
}
|
|
1685
|
+
if (typeof config.enabled !== "undefined") {
|
|
1686
|
+
effectiveConfig.enabled = config.enabled;
|
|
1687
|
+
}
|
|
1688
|
+
if (effectiveIgnorePrefix.length > 0) {
|
|
1689
|
+
effectiveConfig.ignorePrefix = toStringList(effectiveIgnorePrefix);
|
|
1690
|
+
}
|
|
1691
|
+
if (includeList.length > 0) {
|
|
1692
|
+
effectiveConfig.include = toStringList(includeList);
|
|
1693
|
+
}
|
|
1694
|
+
if (excludeList.length > 0) {
|
|
1695
|
+
effectiveConfig.exclude = toStringList(excludeList);
|
|
1696
|
+
}
|
|
1697
|
+
return effectiveConfig;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function pushDecisionStep(chain, entry) {
|
|
1701
|
+
const step = {
|
|
1702
|
+
step: entry.step,
|
|
1703
|
+
result: entry.result
|
|
1704
|
+
};
|
|
1705
|
+
if (typeof entry.source !== "undefined") {
|
|
1706
|
+
step.source = entry.source;
|
|
1707
|
+
}
|
|
1708
|
+
if (typeof entry.detail !== "undefined") {
|
|
1709
|
+
step.detail = entry.detail;
|
|
1710
|
+
}
|
|
1711
|
+
chain.push(step);
|
|
1712
|
+
}
|
|
1713
|
+
function runRoutePrechecks(params) {
|
|
1714
|
+
const {
|
|
1715
|
+
fileInfo,
|
|
1716
|
+
prefix,
|
|
1717
|
+
config,
|
|
1718
|
+
configChain,
|
|
1719
|
+
globalIgnorePrefix,
|
|
1720
|
+
include,
|
|
1721
|
+
exclude,
|
|
1722
|
+
shouldCollectSkip,
|
|
1723
|
+
shouldCollectIgnore,
|
|
1724
|
+
onSkip,
|
|
1725
|
+
onIgnore
|
|
1726
|
+
} = params;
|
|
1727
|
+
const configSources = config.configSources ?? {};
|
|
1728
|
+
const decisionChain = [];
|
|
1729
|
+
const isConfigEnabled = config.enabled !== false;
|
|
1730
|
+
pushDecisionStep(decisionChain, {
|
|
1731
|
+
step: "config.enabled",
|
|
1732
|
+
result: isConfigEnabled ? "pass" : "fail",
|
|
1733
|
+
source: configSources.enabled,
|
|
1734
|
+
detail: config.enabled === false ? "enabled=false" : typeof config.enabled === "boolean" ? "enabled=true" : "enabled=true (default)"
|
|
1735
|
+
});
|
|
1736
|
+
const effectiveIgnorePrefix = typeof config.ignorePrefix !== "undefined" ? normalizeIgnorePrefix(config.ignorePrefix, []) : globalIgnorePrefix;
|
|
1737
|
+
const effectiveInclude = typeof config.include !== "undefined" ? config.include : include;
|
|
1738
|
+
const effectiveExclude = typeof config.exclude !== "undefined" ? config.exclude : exclude;
|
|
1739
|
+
const effectiveConfigParams = {
|
|
1740
|
+
config,
|
|
1741
|
+
effectiveIgnorePrefix
|
|
1742
|
+
};
|
|
1743
|
+
if (typeof effectiveInclude !== "undefined") {
|
|
1744
|
+
effectiveConfigParams.effectiveInclude = effectiveInclude;
|
|
1745
|
+
}
|
|
1746
|
+
if (typeof effectiveExclude !== "undefined") {
|
|
1747
|
+
effectiveConfigParams.effectiveExclude = effectiveExclude;
|
|
1748
|
+
}
|
|
1749
|
+
const effectiveConfig = buildEffectiveConfig(effectiveConfigParams);
|
|
1750
|
+
const effectiveConfigValue = Object.keys(effectiveConfig).length > 0 ? effectiveConfig : void 0;
|
|
1751
|
+
if (!isConfigEnabled) {
|
|
1752
|
+
if (shouldCollectSkip && isSupportedFile(fileInfo.file)) {
|
|
1753
|
+
const resolved = resolveSkipRoute({
|
|
1754
|
+
file: fileInfo.file,
|
|
1755
|
+
rootDir: fileInfo.rootDir,
|
|
1756
|
+
prefix
|
|
1757
|
+
});
|
|
1758
|
+
onSkip?.(buildSkipInfo(
|
|
1759
|
+
fileInfo.file,
|
|
1760
|
+
"disabled-dir",
|
|
1761
|
+
resolved,
|
|
1762
|
+
configChain,
|
|
1763
|
+
decisionChain,
|
|
1764
|
+
effectiveConfigValue
|
|
1765
|
+
));
|
|
1766
|
+
}
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
if (effectiveIgnorePrefix.length > 0) {
|
|
1770
|
+
const ignoredByPrefix = hasIgnoredPrefix(fileInfo.file, fileInfo.rootDir, effectiveIgnorePrefix);
|
|
1771
|
+
pushDecisionStep(decisionChain, {
|
|
1772
|
+
step: "ignore-prefix",
|
|
1773
|
+
result: ignoredByPrefix ? "fail" : "pass",
|
|
1774
|
+
source: configSources.ignorePrefix,
|
|
1775
|
+
detail: `prefixes: ${formatList(effectiveIgnorePrefix)}`
|
|
1776
|
+
});
|
|
1777
|
+
if (ignoredByPrefix) {
|
|
1778
|
+
if (shouldCollectSkip && isSupportedFile(fileInfo.file)) {
|
|
1779
|
+
const resolved = resolveSkipRoute({
|
|
1780
|
+
file: fileInfo.file,
|
|
1781
|
+
rootDir: fileInfo.rootDir,
|
|
1782
|
+
prefix
|
|
1783
|
+
});
|
|
1784
|
+
onSkip?.(buildSkipInfo(
|
|
1785
|
+
fileInfo.file,
|
|
1786
|
+
"ignore-prefix",
|
|
1787
|
+
resolved,
|
|
1788
|
+
configChain,
|
|
1789
|
+
decisionChain,
|
|
1790
|
+
effectiveConfigValue
|
|
1791
|
+
));
|
|
1792
|
+
}
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
const supportedFile = isSupportedFile(fileInfo.file);
|
|
1797
|
+
pushDecisionStep(decisionChain, {
|
|
1798
|
+
step: "file.supported",
|
|
1799
|
+
result: supportedFile ? "pass" : "fail",
|
|
1800
|
+
detail: supportedFile ? void 0 : "unsupported file type"
|
|
1801
|
+
});
|
|
1802
|
+
if (!supportedFile) {
|
|
1803
|
+
if (shouldCollectIgnore) {
|
|
1804
|
+
const ignoreInfo = {
|
|
1805
|
+
file: fileInfo.file,
|
|
1806
|
+
reason: "unsupported",
|
|
1807
|
+
configChain,
|
|
1808
|
+
decisionChain
|
|
1809
|
+
};
|
|
1810
|
+
if (effectiveConfigValue) {
|
|
1811
|
+
ignoreInfo.effectiveConfig = effectiveConfigValue;
|
|
1812
|
+
}
|
|
1813
|
+
onIgnore?.(ignoreInfo);
|
|
1814
|
+
}
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
const normalizedFile = toPosix(fileInfo.file);
|
|
1818
|
+
if (typeof effectiveExclude !== "undefined") {
|
|
1819
|
+
const excluded = testPatterns(effectiveExclude, normalizedFile);
|
|
1820
|
+
const patterns = toFilterStrings(effectiveExclude);
|
|
1821
|
+
pushDecisionStep(decisionChain, {
|
|
1822
|
+
step: "filter.exclude",
|
|
1823
|
+
result: excluded ? "fail" : "pass",
|
|
1824
|
+
source: configSources.exclude,
|
|
1825
|
+
detail: patterns.length > 0 ? `${excluded ? "matched" : "no match"}: ${patterns.join(", ")}` : void 0
|
|
1826
|
+
});
|
|
1827
|
+
if (excluded) {
|
|
1828
|
+
if (shouldCollectSkip) {
|
|
1829
|
+
const resolved = resolveSkipRoute({
|
|
1830
|
+
file: fileInfo.file,
|
|
1831
|
+
rootDir: fileInfo.rootDir,
|
|
1832
|
+
prefix
|
|
1833
|
+
});
|
|
1834
|
+
onSkip?.(buildSkipInfo(
|
|
1835
|
+
fileInfo.file,
|
|
1836
|
+
"exclude",
|
|
1837
|
+
resolved,
|
|
1838
|
+
configChain,
|
|
1839
|
+
decisionChain,
|
|
1840
|
+
effectiveConfigValue
|
|
1841
|
+
));
|
|
1842
|
+
}
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
if (typeof effectiveInclude !== "undefined") {
|
|
1847
|
+
const included = testPatterns(effectiveInclude, normalizedFile);
|
|
1848
|
+
const patterns = toFilterStrings(effectiveInclude);
|
|
1849
|
+
pushDecisionStep(decisionChain, {
|
|
1850
|
+
step: "filter.include",
|
|
1851
|
+
result: included ? "pass" : "fail",
|
|
1852
|
+
source: configSources.include,
|
|
1853
|
+
detail: patterns.length > 0 ? `${included ? "matched" : "no match"}: ${patterns.join(", ")}` : void 0
|
|
1854
|
+
});
|
|
1855
|
+
if (!included) {
|
|
1856
|
+
if (shouldCollectSkip) {
|
|
1857
|
+
const resolved = resolveSkipRoute({
|
|
1858
|
+
file: fileInfo.file,
|
|
1859
|
+
rootDir: fileInfo.rootDir,
|
|
1860
|
+
prefix
|
|
1861
|
+
});
|
|
1862
|
+
onSkip?.(buildSkipInfo(
|
|
1863
|
+
fileInfo.file,
|
|
1864
|
+
"include",
|
|
1865
|
+
resolved,
|
|
1866
|
+
configChain,
|
|
1867
|
+
decisionChain,
|
|
1868
|
+
effectiveConfigValue
|
|
1869
|
+
));
|
|
1870
|
+
}
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const result = { decisionChain };
|
|
1875
|
+
if (effectiveConfigValue) {
|
|
1876
|
+
result.effectiveConfigValue = effectiveConfigValue;
|
|
1877
|
+
}
|
|
1878
|
+
return result;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
async function scanRoutes(params) {
|
|
1882
|
+
const routes = [];
|
|
1883
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1884
|
+
const files = await collectFiles(params.dirs);
|
|
1885
|
+
const globalIgnorePrefix = normalizeIgnorePrefix(params.ignorePrefix);
|
|
1886
|
+
const configCache = /* @__PURE__ */ new Map();
|
|
1887
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
1888
|
+
const shouldCollectSkip = typeof params.onSkip === "function";
|
|
1889
|
+
const shouldCollectIgnore = typeof params.onIgnore === "function";
|
|
1890
|
+
const shouldCollectConfig = typeof params.onConfig === "function";
|
|
1891
|
+
for (const fileInfo of files) {
|
|
1892
|
+
if (isConfigFile(fileInfo.file)) {
|
|
1893
|
+
if (shouldCollectConfig) {
|
|
1894
|
+
const configParams2 = {
|
|
1895
|
+
file: fileInfo.file,
|
|
1896
|
+
rootDir: fileInfo.rootDir,
|
|
1897
|
+
logger: params.logger,
|
|
1898
|
+
configCache,
|
|
1899
|
+
fileCache
|
|
1900
|
+
};
|
|
1901
|
+
if (params.server) {
|
|
1902
|
+
configParams2.server = params.server;
|
|
1903
|
+
}
|
|
1904
|
+
const config2 = await resolveDirectoryConfig(configParams2);
|
|
1905
|
+
params.onConfig?.({ file: fileInfo.file, enabled: config2.enabled !== false });
|
|
1906
|
+
}
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
const configParams = {
|
|
1910
|
+
file: fileInfo.file,
|
|
1911
|
+
rootDir: fileInfo.rootDir,
|
|
1912
|
+
logger: params.logger,
|
|
1913
|
+
configCache,
|
|
1914
|
+
fileCache
|
|
1915
|
+
};
|
|
1916
|
+
if (params.server) {
|
|
1917
|
+
configParams.server = params.server;
|
|
1918
|
+
}
|
|
1919
|
+
const config = await resolveDirectoryConfig(configParams);
|
|
1920
|
+
const configChain = config.configChain ?? [];
|
|
1921
|
+
const precheckParams = {
|
|
1922
|
+
fileInfo,
|
|
1923
|
+
prefix: params.prefix,
|
|
1924
|
+
config,
|
|
1925
|
+
configChain,
|
|
1926
|
+
globalIgnorePrefix,
|
|
1927
|
+
shouldCollectSkip,
|
|
1928
|
+
shouldCollectIgnore
|
|
1929
|
+
};
|
|
1930
|
+
if (params.onSkip) {
|
|
1931
|
+
precheckParams.onSkip = params.onSkip;
|
|
1932
|
+
}
|
|
1933
|
+
if (params.onIgnore) {
|
|
1934
|
+
precheckParams.onIgnore = params.onIgnore;
|
|
1935
|
+
}
|
|
1936
|
+
if (params.include) {
|
|
1937
|
+
precheckParams.include = params.include;
|
|
1938
|
+
}
|
|
1939
|
+
if (params.exclude) {
|
|
1940
|
+
precheckParams.exclude = params.exclude;
|
|
1941
|
+
}
|
|
1942
|
+
const precheck = runRoutePrechecks(precheckParams);
|
|
1943
|
+
if (!precheck) {
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
const { decisionChain, effectiveConfigValue } = precheck;
|
|
1947
|
+
const derived = deriveRouteFromFile(fileInfo.file, fileInfo.rootDir, params.logger);
|
|
1948
|
+
if (!derived) {
|
|
1949
|
+
if (shouldCollectIgnore) {
|
|
1950
|
+
decisionChain.push({
|
|
1951
|
+
step: "route.derived",
|
|
1952
|
+
result: "fail",
|
|
1953
|
+
source: fileInfo.file,
|
|
1954
|
+
detail: "invalid route name"
|
|
1955
|
+
});
|
|
1956
|
+
const ignoreInfo = {
|
|
1957
|
+
file: fileInfo.file,
|
|
1958
|
+
reason: "invalid-route",
|
|
1959
|
+
configChain,
|
|
1960
|
+
decisionChain
|
|
1961
|
+
};
|
|
1962
|
+
if (effectiveConfigValue) {
|
|
1963
|
+
ignoreInfo.effectiveConfig = effectiveConfigValue;
|
|
1964
|
+
}
|
|
1965
|
+
params.onIgnore?.(ignoreInfo);
|
|
1966
|
+
}
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
decisionChain.push({
|
|
1970
|
+
step: "route.derived",
|
|
1971
|
+
result: "pass",
|
|
1972
|
+
source: fileInfo.file
|
|
1973
|
+
});
|
|
1974
|
+
const rules = await loadRules(fileInfo.file, params.server, params.logger);
|
|
1975
|
+
for (const [index, rule] of rules.entries()) {
|
|
1976
|
+
if (!rule || typeof rule !== "object") {
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (rule.enabled === false) {
|
|
1980
|
+
if (shouldCollectSkip) {
|
|
1981
|
+
const resolved2 = resolveSkipRoute({
|
|
1982
|
+
file: fileInfo.file,
|
|
1983
|
+
rootDir: fileInfo.rootDir,
|
|
1984
|
+
prefix: params.prefix,
|
|
1985
|
+
derived
|
|
1986
|
+
});
|
|
1987
|
+
const ruleDecisionStep = {
|
|
1988
|
+
step: "rule.enabled",
|
|
1989
|
+
result: "fail",
|
|
1990
|
+
source: fileInfo.file,
|
|
1991
|
+
detail: "enabled=false"
|
|
1992
|
+
};
|
|
1993
|
+
const ruleDecisionChain = [...decisionChain, ruleDecisionStep];
|
|
1994
|
+
params.onSkip?.(buildSkipInfo(
|
|
1995
|
+
fileInfo.file,
|
|
1996
|
+
"disabled",
|
|
1997
|
+
resolved2,
|
|
1998
|
+
configChain,
|
|
1999
|
+
ruleDecisionChain,
|
|
2000
|
+
effectiveConfigValue
|
|
2001
|
+
));
|
|
2002
|
+
}
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
const ruleValue = rule;
|
|
2006
|
+
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
2007
|
+
(key2) => key2 in ruleValue
|
|
2008
|
+
);
|
|
2009
|
+
if (unsupportedKeys.length > 0) {
|
|
2010
|
+
params.logger.warn(
|
|
2011
|
+
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
2012
|
+
);
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
if (typeof rule.handler === "undefined") {
|
|
2016
|
+
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
const resolved = resolveRule({
|
|
2020
|
+
rule,
|
|
2021
|
+
derivedTemplate: derived.template,
|
|
2022
|
+
derivedMethod: derived.method,
|
|
2023
|
+
prefix: params.prefix,
|
|
2024
|
+
file: fileInfo.file,
|
|
2025
|
+
logger: params.logger
|
|
2026
|
+
});
|
|
2027
|
+
if (!resolved) {
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
resolved.ruleIndex = index;
|
|
2031
|
+
if (configChain.length > 0) {
|
|
2032
|
+
resolved.configChain = configChain;
|
|
2033
|
+
}
|
|
2034
|
+
if (config.headers) {
|
|
2035
|
+
resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
|
|
2036
|
+
}
|
|
2037
|
+
if (typeof resolved.status === "undefined" && typeof config.status === "number") {
|
|
2038
|
+
resolved.status = config.status;
|
|
2039
|
+
}
|
|
2040
|
+
if (typeof resolved.delay === "undefined" && typeof config.delay === "number") {
|
|
2041
|
+
resolved.delay = config.delay;
|
|
2042
|
+
}
|
|
2043
|
+
if (config.middlewares.length > 0) {
|
|
2044
|
+
resolved.middlewares = config.middlewares;
|
|
2045
|
+
}
|
|
2046
|
+
const key = `${resolved.method} ${resolved.template}`;
|
|
2047
|
+
if (seen.has(key)) {
|
|
2048
|
+
params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
|
|
2049
|
+
}
|
|
2050
|
+
seen.add(key);
|
|
2051
|
+
routes.push(resolved);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return sortRoutes(routes);
|
|
1644
2055
|
}
|
|
1645
2056
|
|
|
1646
|
-
export {
|
|
2057
|
+
export { sortRoutes as a, buildSwScript as b, createHonoApp as c, createDebouncer as d, resolvePlaygroundOptions as e, resolveSwConfig as f, resolveSwUnregisterConfig as g, createPlaygroundMiddleware as h, isInDirs as i, createMiddleware as j, buildManifestData as k, resolveDirs as r, scanRoutes as s, toPosix as t };
|