mokup 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.cjs ADDED
@@ -0,0 +1,783 @@
1
+ 'use strict';
2
+
3
+ const node_process = require('node:process');
4
+ const chokidar = require('chokidar');
5
+ const node_buffer = require('node:buffer');
6
+ const runtime = require('@mokup/runtime');
7
+ const pathe = require('pathe');
8
+ const node_fs = require('node:fs');
9
+ const node_module = require('node:module');
10
+ const node_url = require('node:url');
11
+ const esbuild = require('esbuild');
12
+ const jsoncParser = require('jsonc-parser');
13
+
14
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
16
+
17
+ const chokidar__default = /*#__PURE__*/_interopDefaultCompat(chokidar);
18
+
19
+ function createLogger(enabled) {
20
+ return {
21
+ info: (...args) => {
22
+ if (enabled) {
23
+ console.info("[mokup]", ...args);
24
+ }
25
+ },
26
+ warn: (...args) => {
27
+ if (enabled) {
28
+ console.warn("[mokup]", ...args);
29
+ }
30
+ },
31
+ error: (...args) => {
32
+ if (enabled) {
33
+ console.error("[mokup]", ...args);
34
+ }
35
+ }
36
+ };
37
+ }
38
+
39
+ const methodSet = /* @__PURE__ */ new Set([
40
+ "GET",
41
+ "POST",
42
+ "PUT",
43
+ "PATCH",
44
+ "DELETE",
45
+ "OPTIONS",
46
+ "HEAD"
47
+ ]);
48
+ const methodSuffixSet = new Set(
49
+ Array.from(methodSet, (method) => method.toLowerCase())
50
+ );
51
+ const supportedExtensions = /* @__PURE__ */ new Set([
52
+ ".json",
53
+ ".jsonc",
54
+ ".ts",
55
+ ".js",
56
+ ".mjs",
57
+ ".cjs"
58
+ ]);
59
+
60
+ function normalizeMethod(method) {
61
+ if (!method) {
62
+ return void 0;
63
+ }
64
+ const normalized = method.toUpperCase();
65
+ if (methodSet.has(normalized)) {
66
+ return normalized;
67
+ }
68
+ return void 0;
69
+ }
70
+ function normalizePrefix(prefix) {
71
+ if (!prefix) {
72
+ return "";
73
+ }
74
+ const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
75
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
76
+ }
77
+ function resolveDirs(dir, root) {
78
+ const raw = typeof dir === "function" ? dir(root) : dir;
79
+ const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
80
+ const normalized = resolved.map(
81
+ (entry) => pathe.isAbsolute(entry) ? entry : pathe.resolve(root, entry)
82
+ );
83
+ return Array.from(new Set(normalized));
84
+ }
85
+ function createDebouncer(delayMs, fn) {
86
+ let timer = null;
87
+ return () => {
88
+ if (timer) {
89
+ clearTimeout(timer);
90
+ }
91
+ timer = setTimeout(() => {
92
+ timer = null;
93
+ fn();
94
+ }, delayMs);
95
+ };
96
+ }
97
+ function toPosix(value) {
98
+ return value.replace(/\\/g, "/");
99
+ }
100
+ function isInDirs(file, dirs) {
101
+ const normalized = toPosix(file);
102
+ return dirs.some((dir) => {
103
+ const normalizedDir = toPosix(dir).replace(/\/$/, "");
104
+ return normalized === normalizedDir || normalized.startsWith(`${normalizedDir}/`);
105
+ });
106
+ }
107
+ function testPatterns(patterns, value) {
108
+ const list = Array.isArray(patterns) ? patterns : [patterns];
109
+ return list.some((pattern) => pattern.test(value));
110
+ }
111
+ function matchesFilter(file, include, exclude) {
112
+ const normalized = toPosix(file);
113
+ if (exclude && testPatterns(exclude, normalized)) {
114
+ return false;
115
+ }
116
+ if (include) {
117
+ return testPatterns(include, normalized);
118
+ }
119
+ return true;
120
+ }
121
+ function delay(ms) {
122
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
123
+ }
124
+
125
+ function extractQuery(parsedUrl) {
126
+ const query = {};
127
+ for (const [key, value] of parsedUrl.searchParams.entries()) {
128
+ const current = query[key];
129
+ if (typeof current === "undefined") {
130
+ query[key] = value;
131
+ } else if (Array.isArray(current)) {
132
+ current.push(value);
133
+ } else {
134
+ query[key] = [current, value];
135
+ }
136
+ }
137
+ return query;
138
+ }
139
+ async function readRawBody(req) {
140
+ return new Promise((resolve, reject) => {
141
+ const chunks = [];
142
+ req.on("data", (chunk) => {
143
+ chunks.push(node_buffer.Buffer.isBuffer(chunk) ? chunk : node_buffer.Buffer.from(chunk));
144
+ });
145
+ req.on("end", () => {
146
+ if (chunks.length === 0) {
147
+ resolve(null);
148
+ return;
149
+ }
150
+ resolve(node_buffer.Buffer.concat(chunks));
151
+ });
152
+ req.on("error", reject);
153
+ });
154
+ }
155
+ async function readRequestBody(req, parsedUrl) {
156
+ const query = extractQuery(parsedUrl);
157
+ const rawBody = await readRawBody(req);
158
+ if (!rawBody) {
159
+ return { query, body: void 0, rawBody: void 0 };
160
+ }
161
+ const rawText = rawBody.toString("utf8");
162
+ const contentTypeHeader = req.headers["content-type"];
163
+ const contentTypeValue = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] ?? "" : contentTypeHeader ?? "";
164
+ const contentType = contentTypeValue.split(";")[0]?.trim() ?? "";
165
+ if (contentType === "application/json" || contentType.endsWith("+json")) {
166
+ try {
167
+ return { query, body: JSON.parse(rawText), rawBody: rawText };
168
+ } catch {
169
+ return { query, body: rawText, rawBody: rawText };
170
+ }
171
+ }
172
+ if (contentType === "application/x-www-form-urlencoded") {
173
+ const params = new URLSearchParams(rawText);
174
+ const body = Object.fromEntries(params.entries());
175
+ return { query, body, rawBody: rawText };
176
+ }
177
+ return { query, body: rawText, rawBody: rawText };
178
+ }
179
+ function applyHeaders(res, headers) {
180
+ if (!headers) {
181
+ return;
182
+ }
183
+ for (const [key, value] of Object.entries(headers)) {
184
+ res.setHeader(key, value);
185
+ }
186
+ }
187
+ function writeResponse(res, body) {
188
+ if (typeof body === "undefined") {
189
+ if (!res.headersSent && res.statusCode === 200) {
190
+ res.statusCode = 204;
191
+ }
192
+ res.end();
193
+ return;
194
+ }
195
+ if (node_buffer.Buffer.isBuffer(body)) {
196
+ if (!res.getHeader("Content-Type")) {
197
+ res.setHeader("Content-Type", "application/octet-stream");
198
+ }
199
+ res.end(body);
200
+ return;
201
+ }
202
+ if (typeof body === "string") {
203
+ if (!res.getHeader("Content-Type")) {
204
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
205
+ }
206
+ res.end(body);
207
+ return;
208
+ }
209
+ if (!res.getHeader("Content-Type")) {
210
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
211
+ }
212
+ res.end(JSON.stringify(body));
213
+ }
214
+ function findMatchingRoute(routes, method, pathname) {
215
+ for (const route of routes) {
216
+ if (route.method !== method) {
217
+ continue;
218
+ }
219
+ const matched = runtime.matchRouteTokens(route.tokens, pathname);
220
+ if (matched) {
221
+ return { route, params: matched.params };
222
+ }
223
+ }
224
+ return null;
225
+ }
226
+ function createMiddleware(getRoutes, logger) {
227
+ return async (req, res, next) => {
228
+ const url = req.url ?? "/";
229
+ const parsedUrl = new URL(url, "http://mokup.local");
230
+ const pathname = parsedUrl.pathname;
231
+ const method = normalizeMethod(req.method) ?? "GET";
232
+ const matched = findMatchingRoute(getRoutes(), method, pathname);
233
+ if (!matched) {
234
+ return next();
235
+ }
236
+ try {
237
+ const { query, body, rawBody } = await readRequestBody(req, parsedUrl);
238
+ const mockReq = {
239
+ url: pathname,
240
+ method,
241
+ headers: req.headers,
242
+ query,
243
+ body,
244
+ params: matched.params
245
+ };
246
+ if (rawBody) {
247
+ mockReq.rawBody = rawBody;
248
+ }
249
+ const ctx = {
250
+ delay: (ms) => delay(ms),
251
+ json: (data) => data
252
+ };
253
+ const startedAt = Date.now();
254
+ const responseValue = typeof matched.route.response === "function" ? await matched.route.response(mockReq, res, ctx) : matched.route.response;
255
+ if (res.writableEnded) {
256
+ return;
257
+ }
258
+ if (matched.route.delay && matched.route.delay > 0) {
259
+ await delay(matched.route.delay);
260
+ }
261
+ applyHeaders(res, matched.route.headers);
262
+ if (matched.route.status) {
263
+ res.statusCode = matched.route.status;
264
+ }
265
+ writeResponse(res, responseValue);
266
+ logger.info(`${method} ${pathname} ${Date.now() - startedAt}ms`);
267
+ } catch (error) {
268
+ res.statusCode = 500;
269
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
270
+ res.end("Mock handler error");
271
+ logger.error("Mock handler failed:", error);
272
+ }
273
+ };
274
+ }
275
+
276
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite.cjs', document.baseURI).href)));
277
+ const mimeTypes = {
278
+ ".html": "text/html; charset=utf-8",
279
+ ".css": "text/css; charset=utf-8",
280
+ ".js": "text/javascript; charset=utf-8",
281
+ ".map": "application/json; charset=utf-8",
282
+ ".json": "application/json; charset=utf-8",
283
+ ".svg": "image/svg+xml",
284
+ ".png": "image/png",
285
+ ".jpg": "image/jpeg",
286
+ ".jpeg": "image/jpeg",
287
+ ".ico": "image/x-icon"
288
+ };
289
+ function normalizePlaygroundPath(value) {
290
+ if (!value) {
291
+ return "/_mokup";
292
+ }
293
+ const normalized = value.startsWith("/") ? value : `/${value}`;
294
+ return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
295
+ }
296
+ function resolvePlaygroundOptions(playground) {
297
+ if (playground === false) {
298
+ return { enabled: false, path: "/_mokup" };
299
+ }
300
+ if (playground && typeof playground === "object") {
301
+ return {
302
+ enabled: playground.enabled !== false,
303
+ path: normalizePlaygroundPath(playground.path)
304
+ };
305
+ }
306
+ return { enabled: true, path: "/_mokup" };
307
+ }
308
+ function resolvePlaygroundDist() {
309
+ const pkgPath = require$1.resolve("@mokup/playground/package.json");
310
+ return pathe.join(pkgPath, "..", "dist");
311
+ }
312
+ function sendJson(res, data, statusCode = 200) {
313
+ res.statusCode = statusCode;
314
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
315
+ res.end(JSON.stringify(data, null, 2));
316
+ }
317
+ function sendFile(res, content, contentType) {
318
+ res.statusCode = 200;
319
+ res.setHeader("Content-Type", contentType);
320
+ res.end(content);
321
+ }
322
+ function toPlaygroundRoute(route) {
323
+ return {
324
+ method: route.method,
325
+ url: route.template,
326
+ file: route.file,
327
+ type: typeof route.response === "function" ? "handler" : "static",
328
+ status: route.status,
329
+ delay: route.delay
330
+ };
331
+ }
332
+ function createPlaygroundMiddleware(params) {
333
+ const distDir = resolvePlaygroundDist();
334
+ const playgroundPath = params.config.path;
335
+ const indexPath = pathe.join(distDir, "index.html");
336
+ return async (req, res, next) => {
337
+ if (!params.config.enabled) {
338
+ return next();
339
+ }
340
+ const requestUrl = req.url ?? "/";
341
+ const url = new URL(requestUrl, "http://mokup.local");
342
+ const pathname = url.pathname;
343
+ if (!pathname.startsWith(playgroundPath)) {
344
+ return next();
345
+ }
346
+ const subPath = pathname.slice(playgroundPath.length);
347
+ if (subPath === "" || subPath === "/" || subPath === "/index.html") {
348
+ try {
349
+ const html = await node_fs.promises.readFile(indexPath, "utf8");
350
+ const contentType = mimeTypes[".html"] ?? "text/html; charset=utf-8";
351
+ sendFile(res, html, contentType);
352
+ } catch (error) {
353
+ params.logger.error("Failed to load playground index:", error);
354
+ res.statusCode = 500;
355
+ res.end("Playground is not available.");
356
+ }
357
+ return;
358
+ }
359
+ if (subPath === "/routes") {
360
+ const routes = params.getRoutes();
361
+ sendJson(res, {
362
+ basePath: playgroundPath,
363
+ count: routes.length,
364
+ routes: routes.map(toPlaygroundRoute)
365
+ });
366
+ return;
367
+ }
368
+ const relPath = subPath.replace(/^\/+/, "");
369
+ if (relPath.includes("..")) {
370
+ res.statusCode = 400;
371
+ res.end("Invalid path.");
372
+ return;
373
+ }
374
+ const normalizedPath = pathe.normalize(relPath);
375
+ const filePath = pathe.join(distDir, normalizedPath);
376
+ try {
377
+ const content = await node_fs.promises.readFile(filePath);
378
+ const ext = pathe.extname(filePath);
379
+ const contentType = mimeTypes[ext] ?? "application/octet-stream";
380
+ sendFile(res, content, contentType);
381
+ } catch {
382
+ return next();
383
+ }
384
+ };
385
+ }
386
+
387
+ async function walkDir(dir, rootDir, files) {
388
+ const entries = await node_fs.promises.readdir(dir, { withFileTypes: true });
389
+ for (const entry of entries) {
390
+ if (entry.name === "node_modules" || entry.name === ".git") {
391
+ continue;
392
+ }
393
+ const fullPath = pathe.join(dir, entry.name);
394
+ if (entry.isDirectory()) {
395
+ await walkDir(fullPath, rootDir, files);
396
+ continue;
397
+ }
398
+ if (entry.isFile()) {
399
+ files.push({ file: fullPath, rootDir });
400
+ }
401
+ }
402
+ }
403
+ async function exists(path) {
404
+ try {
405
+ await node_fs.promises.stat(path);
406
+ return true;
407
+ } catch {
408
+ return false;
409
+ }
410
+ }
411
+ async function collectFiles(dirs) {
412
+ const files = [];
413
+ for (const dir of dirs) {
414
+ if (!await exists(dir)) {
415
+ continue;
416
+ }
417
+ await walkDir(dir, dir, files);
418
+ }
419
+ return files;
420
+ }
421
+ function isSupportedFile(file) {
422
+ if (file.endsWith(".d.ts")) {
423
+ return false;
424
+ }
425
+ const ext = pathe.extname(file).toLowerCase();
426
+ return supportedExtensions.has(ext);
427
+ }
428
+
429
+ async function loadModule(file) {
430
+ const ext = pathe.extname(file).toLowerCase();
431
+ if (ext === ".cjs") {
432
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite.cjs', document.baseURI).href)));
433
+ delete require$1.cache[file];
434
+ return require$1(file);
435
+ }
436
+ if (ext === ".js" || ext === ".mjs") {
437
+ return import(`${node_url.pathToFileURL(file).href}?t=${Date.now()}`);
438
+ }
439
+ if (ext === ".ts") {
440
+ const result = await esbuild.build({
441
+ entryPoints: [file],
442
+ bundle: true,
443
+ format: "esm",
444
+ platform: "node",
445
+ sourcemap: "inline",
446
+ target: "es2020",
447
+ write: false
448
+ });
449
+ const output = result.outputFiles[0];
450
+ const code = output?.text ?? "";
451
+ const dataUrl = `data:text/javascript;base64,${node_buffer.Buffer.from(code).toString(
452
+ "base64"
453
+ )}`;
454
+ return import(`${dataUrl}#${Date.now()}`);
455
+ }
456
+ return null;
457
+ }
458
+ async function loadModuleWithVite(server, file) {
459
+ const asDevServer = server;
460
+ if ("ssrLoadModule" in asDevServer) {
461
+ const moduleNode = asDevServer.moduleGraph.getModuleById(file);
462
+ if (moduleNode) {
463
+ asDevServer.moduleGraph.invalidateModule(moduleNode);
464
+ }
465
+ return asDevServer.ssrLoadModule(file);
466
+ }
467
+ return loadModule(file);
468
+ }
469
+ async function readJsonFile(file, logger) {
470
+ try {
471
+ const content = await node_fs.promises.readFile(file, "utf8");
472
+ const errors = [];
473
+ const data = jsoncParser.parse(content, errors, {
474
+ allowTrailingComma: true,
475
+ disallowComments: false
476
+ });
477
+ if (errors.length > 0) {
478
+ logger.warn(`Invalid JSONC in ${file}`);
479
+ return void 0;
480
+ }
481
+ return data;
482
+ } catch (error) {
483
+ logger.warn(`Failed to read ${file}: ${String(error)}`);
484
+ return void 0;
485
+ }
486
+ }
487
+ async function loadRules(file, server, logger) {
488
+ const ext = pathe.extname(file).toLowerCase();
489
+ if (ext === ".json" || ext === ".jsonc") {
490
+ const json = await readJsonFile(file, logger);
491
+ if (typeof json === "undefined") {
492
+ return [];
493
+ }
494
+ return [
495
+ {
496
+ response: json
497
+ }
498
+ ];
499
+ }
500
+ const mod = server ? await loadModuleWithVite(server, file) : await loadModule(file);
501
+ const value = mod?.default ?? mod;
502
+ if (!value) {
503
+ return [];
504
+ }
505
+ if (Array.isArray(value)) {
506
+ return value;
507
+ }
508
+ if (typeof value === "function") {
509
+ return [
510
+ {
511
+ response: value
512
+ }
513
+ ];
514
+ }
515
+ return [value];
516
+ }
517
+
518
+ function resolveMethod(fileMethod, ruleMethod, logger) {
519
+ if (ruleMethod) {
520
+ const normalized = normalizeMethod(ruleMethod);
521
+ if (normalized) {
522
+ return normalized;
523
+ }
524
+ logger.warn(`Unknown method "${ruleMethod}", falling back to file method`);
525
+ }
526
+ if (fileMethod) {
527
+ return fileMethod;
528
+ }
529
+ return "GET";
530
+ }
531
+ function resolveTemplate(template, prefix) {
532
+ const normalized = template.startsWith("/") ? template : `/${template}`;
533
+ if (!prefix) {
534
+ return normalized;
535
+ }
536
+ const normalizedPrefix = normalizePrefix(prefix);
537
+ if (!normalizedPrefix) {
538
+ return normalized;
539
+ }
540
+ if (normalized === normalizedPrefix || normalized.startsWith(`${normalizedPrefix}/`)) {
541
+ return normalized;
542
+ }
543
+ if (normalized === "/") {
544
+ return `${normalizedPrefix}/`;
545
+ }
546
+ return `${normalizedPrefix}${normalized}`;
547
+ }
548
+ function stripMethodSuffix(base) {
549
+ const segments = base.split(".");
550
+ const last = segments.at(-1);
551
+ if (last && methodSuffixSet.has(last.toLowerCase())) {
552
+ segments.pop();
553
+ return {
554
+ name: segments.join("."),
555
+ method: last.toUpperCase()
556
+ };
557
+ }
558
+ return {
559
+ name: base,
560
+ method: void 0
561
+ };
562
+ }
563
+ function deriveRouteFromFile(file, rootDir, logger) {
564
+ const rel = toPosix(pathe.relative(rootDir, file));
565
+ const ext = pathe.extname(rel);
566
+ const withoutExt = rel.slice(0, rel.length - ext.length);
567
+ const dir = pathe.dirname(withoutExt);
568
+ const base = pathe.basename(withoutExt);
569
+ const { name, method } = stripMethodSuffix(base);
570
+ if (!method) {
571
+ logger.warn(`Skip mock without method suffix: ${file}`);
572
+ return null;
573
+ }
574
+ if (!name) {
575
+ logger.warn(`Skip mock with empty route name: ${file}`);
576
+ return null;
577
+ }
578
+ const joined = dir === "." ? name : pathe.join(dir, name);
579
+ const segments = toPosix(joined).split("/");
580
+ if (segments.at(-1) === "index") {
581
+ segments.pop();
582
+ }
583
+ const template = segments.length === 0 ? "/" : `/${segments.join("/")}`;
584
+ const parsed = runtime.parseRouteTemplate(template);
585
+ if (parsed.errors.length > 0) {
586
+ for (const error of parsed.errors) {
587
+ logger.warn(`${error} in ${file}`);
588
+ }
589
+ return null;
590
+ }
591
+ for (const warning of parsed.warnings) {
592
+ logger.warn(`${warning} in ${file}`);
593
+ }
594
+ return {
595
+ template: parsed.template,
596
+ method,
597
+ tokens: parsed.tokens,
598
+ score: parsed.score
599
+ };
600
+ }
601
+ function resolveRule(params) {
602
+ const method = resolveMethod(params.derivedMethod, params.rule.method, params.logger);
603
+ if (!method) {
604
+ params.logger.warn(`Invalid mock method in ${params.file}`);
605
+ return null;
606
+ }
607
+ const template = resolveTemplate(params.rule.url ?? params.derivedTemplate, params.prefix);
608
+ const parsed = runtime.parseRouteTemplate(template);
609
+ if (parsed.errors.length > 0) {
610
+ for (const error of parsed.errors) {
611
+ params.logger.warn(`${error} in ${params.file}`);
612
+ }
613
+ return null;
614
+ }
615
+ for (const warning of parsed.warnings) {
616
+ params.logger.warn(`${warning} in ${params.file}`);
617
+ }
618
+ const route = {
619
+ file: params.file,
620
+ template: parsed.template,
621
+ method,
622
+ tokens: parsed.tokens,
623
+ score: parsed.score,
624
+ response: params.rule.response
625
+ };
626
+ if (typeof params.rule.status === "number") {
627
+ route.status = params.rule.status;
628
+ }
629
+ if (params.rule.headers) {
630
+ route.headers = params.rule.headers;
631
+ }
632
+ if (typeof params.rule.delay === "number") {
633
+ route.delay = params.rule.delay;
634
+ }
635
+ return route;
636
+ }
637
+ function sortRoutes(routes) {
638
+ return routes.sort((a, b) => {
639
+ if (a.method !== b.method) {
640
+ return a.method.localeCompare(b.method);
641
+ }
642
+ const scoreCompare = runtime.compareRouteScore(a.score, b.score);
643
+ if (scoreCompare !== 0) {
644
+ return scoreCompare;
645
+ }
646
+ return a.template.localeCompare(b.template);
647
+ });
648
+ }
649
+
650
+ async function scanRoutes(params) {
651
+ const routes = [];
652
+ const seen = /* @__PURE__ */ new Set();
653
+ const files = await collectFiles(params.dirs);
654
+ for (const fileInfo of files) {
655
+ if (!isSupportedFile(fileInfo.file)) {
656
+ continue;
657
+ }
658
+ if (!matchesFilter(fileInfo.file, params.include, params.exclude)) {
659
+ continue;
660
+ }
661
+ const derived = deriveRouteFromFile(fileInfo.file, fileInfo.rootDir, params.logger);
662
+ if (!derived) {
663
+ continue;
664
+ }
665
+ const rules = await loadRules(fileInfo.file, params.server, params.logger);
666
+ for (const rule of rules) {
667
+ if (!rule || typeof rule !== "object") {
668
+ continue;
669
+ }
670
+ if (typeof rule.response === "undefined") {
671
+ params.logger.warn(`Skip mock without response: ${fileInfo.file}`);
672
+ continue;
673
+ }
674
+ const resolved = resolveRule({
675
+ rule,
676
+ derivedTemplate: derived.template,
677
+ derivedMethod: derived.method,
678
+ prefix: params.prefix,
679
+ file: fileInfo.file,
680
+ logger: params.logger
681
+ });
682
+ if (!resolved) {
683
+ continue;
684
+ }
685
+ const key = `${resolved.method} ${resolved.template}`;
686
+ if (seen.has(key)) {
687
+ params.logger.warn(`Duplicate mock route ${key} from ${fileInfo.file}`);
688
+ }
689
+ seen.add(key);
690
+ routes.push(resolved);
691
+ }
692
+ }
693
+ return sortRoutes(routes);
694
+ }
695
+
696
+ function createMokupPlugin(options = {}) {
697
+ let root = node_process.cwd();
698
+ let routes = [];
699
+ let previewWatcher = null;
700
+ const logger = createLogger(options.log !== false);
701
+ const watchEnabled = options.watch !== false;
702
+ const playgroundConfig = resolvePlaygroundOptions(options.playground);
703
+ const playgroundMiddleware = createPlaygroundMiddleware({
704
+ getRoutes: () => routes,
705
+ config: playgroundConfig,
706
+ logger
707
+ });
708
+ const refreshRoutes = async (server) => {
709
+ const dirs = resolveDirs(options.dir, root);
710
+ const scanParams = {
711
+ dirs,
712
+ prefix: options.prefix ?? "",
713
+ logger
714
+ };
715
+ if (options.include) {
716
+ scanParams.include = options.include;
717
+ }
718
+ if (options.exclude) {
719
+ scanParams.exclude = options.exclude;
720
+ }
721
+ if (server) {
722
+ scanParams.server = server;
723
+ }
724
+ routes = await scanRoutes(scanParams);
725
+ };
726
+ return {
727
+ name: "mokup:vite",
728
+ enforce: "pre",
729
+ configResolved(config) {
730
+ root = config.root;
731
+ },
732
+ async configureServer(server) {
733
+ await refreshRoutes(server);
734
+ server.middlewares.use(playgroundMiddleware);
735
+ server.middlewares.use(createMiddleware(() => routes, logger));
736
+ if (!watchEnabled) {
737
+ return;
738
+ }
739
+ const dirs = resolveDirs(options.dir, root);
740
+ server.watcher.add(dirs);
741
+ const scheduleRefresh = createDebouncer(80, () => refreshRoutes(server));
742
+ server.watcher.on("add", (file) => {
743
+ if (isInDirs(file, dirs)) {
744
+ scheduleRefresh();
745
+ }
746
+ });
747
+ server.watcher.on("change", (file) => {
748
+ if (isInDirs(file, dirs)) {
749
+ scheduleRefresh();
750
+ }
751
+ });
752
+ server.watcher.on("unlink", (file) => {
753
+ if (isInDirs(file, dirs)) {
754
+ scheduleRefresh();
755
+ }
756
+ });
757
+ },
758
+ async configurePreviewServer(server) {
759
+ await refreshRoutes(server);
760
+ server.middlewares.use(playgroundMiddleware);
761
+ server.middlewares.use(createMiddleware(() => routes, logger));
762
+ if (!watchEnabled) {
763
+ return;
764
+ }
765
+ const dirs = resolveDirs(options.dir, root);
766
+ previewWatcher = chokidar__default.watch(dirs, { ignoreInitial: true });
767
+ const scheduleRefresh = createDebouncer(80, () => refreshRoutes(server));
768
+ previewWatcher.on("add", scheduleRefresh);
769
+ previewWatcher.on("change", scheduleRefresh);
770
+ previewWatcher.on("unlink", scheduleRefresh);
771
+ server.httpServer?.once("close", () => {
772
+ previewWatcher?.close();
773
+ previewWatcher = null;
774
+ });
775
+ },
776
+ closeBundle() {
777
+ previewWatcher?.close();
778
+ previewWatcher = null;
779
+ }
780
+ };
781
+ }
782
+
783
+ module.exports = createMokupPlugin;