mokup 0.0.1 → 0.1.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/index.d.cts +13 -17
- package/dist/index.d.mts +13 -17
- package/dist/index.d.ts +13 -17
- package/dist/vite.cjs +170 -142
- package/dist/vite.d.cts +2 -2
- package/dist/vite.d.mts +2 -2
- package/dist/vite.d.ts +2 -2
- package/dist/vite.mjs +170 -142
- package/package.json +3 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
2
2
|
|
|
3
3
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
headers: IncomingMessage['headers'];
|
|
8
|
-
query: Record<string, string | string[]>;
|
|
9
|
-
body: unknown;
|
|
10
|
-
rawBody?: string;
|
|
11
|
-
params?: Record<string, string | string[]>;
|
|
12
|
-
}
|
|
13
|
-
interface MockContext {
|
|
14
|
-
delay: (ms: number) => Promise<void>;
|
|
15
|
-
json: (data: unknown) => unknown;
|
|
16
|
-
}
|
|
17
|
-
type MockResponseHandler = (req: MockRequest, res: ServerResponse, ctx: MockContext) => unknown | Promise<unknown>;
|
|
4
|
+
type MockContext = Context;
|
|
5
|
+
type MockMiddleware = MiddlewareHandler;
|
|
6
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
18
7
|
type MockResponse = unknown | MockResponseHandler;
|
|
19
8
|
interface MockRule {
|
|
20
|
-
|
|
9
|
+
handler: MockResponse;
|
|
21
10
|
status?: number;
|
|
22
11
|
headers?: Record<string, string>;
|
|
23
12
|
delay?: number;
|
|
24
13
|
}
|
|
14
|
+
interface DirectoryConfig {
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
status?: number;
|
|
17
|
+
delay?: number;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
middleware?: MockMiddleware | MockMiddleware[];
|
|
20
|
+
}
|
|
25
21
|
interface MokupViteOptions {
|
|
26
22
|
dir?: string | string[] | ((root: string) => string | string[]);
|
|
27
23
|
prefix?: string;
|
|
@@ -36,4 +32,4 @@ interface MokupViteOptions {
|
|
|
36
32
|
}
|
|
37
33
|
type MokupViteOptionsInput = MokupViteOptions | MokupViteOptions[];
|
|
38
34
|
|
|
39
|
-
export type { HttpMethod, MockContext,
|
|
35
|
+
export type { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions, MokupViteOptionsInput };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
2
2
|
|
|
3
3
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
headers: IncomingMessage['headers'];
|
|
8
|
-
query: Record<string, string | string[]>;
|
|
9
|
-
body: unknown;
|
|
10
|
-
rawBody?: string;
|
|
11
|
-
params?: Record<string, string | string[]>;
|
|
12
|
-
}
|
|
13
|
-
interface MockContext {
|
|
14
|
-
delay: (ms: number) => Promise<void>;
|
|
15
|
-
json: (data: unknown) => unknown;
|
|
16
|
-
}
|
|
17
|
-
type MockResponseHandler = (req: MockRequest, res: ServerResponse, ctx: MockContext) => unknown | Promise<unknown>;
|
|
4
|
+
type MockContext = Context;
|
|
5
|
+
type MockMiddleware = MiddlewareHandler;
|
|
6
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
18
7
|
type MockResponse = unknown | MockResponseHandler;
|
|
19
8
|
interface MockRule {
|
|
20
|
-
|
|
9
|
+
handler: MockResponse;
|
|
21
10
|
status?: number;
|
|
22
11
|
headers?: Record<string, string>;
|
|
23
12
|
delay?: number;
|
|
24
13
|
}
|
|
14
|
+
interface DirectoryConfig {
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
status?: number;
|
|
17
|
+
delay?: number;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
middleware?: MockMiddleware | MockMiddleware[];
|
|
20
|
+
}
|
|
25
21
|
interface MokupViteOptions {
|
|
26
22
|
dir?: string | string[] | ((root: string) => string | string[]);
|
|
27
23
|
prefix?: string;
|
|
@@ -36,4 +32,4 @@ interface MokupViteOptions {
|
|
|
36
32
|
}
|
|
37
33
|
type MokupViteOptionsInput = MokupViteOptions | MokupViteOptions[];
|
|
38
34
|
|
|
39
|
-
export type { HttpMethod, MockContext,
|
|
35
|
+
export type { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions, MokupViteOptionsInput };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
2
2
|
|
|
3
3
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
headers: IncomingMessage['headers'];
|
|
8
|
-
query: Record<string, string | string[]>;
|
|
9
|
-
body: unknown;
|
|
10
|
-
rawBody?: string;
|
|
11
|
-
params?: Record<string, string | string[]>;
|
|
12
|
-
}
|
|
13
|
-
interface MockContext {
|
|
14
|
-
delay: (ms: number) => Promise<void>;
|
|
15
|
-
json: (data: unknown) => unknown;
|
|
16
|
-
}
|
|
17
|
-
type MockResponseHandler = (req: MockRequest, res: ServerResponse, ctx: MockContext) => unknown | Promise<unknown>;
|
|
4
|
+
type MockContext = Context;
|
|
5
|
+
type MockMiddleware = MiddlewareHandler;
|
|
6
|
+
type MockResponseHandler = (context: Context) => Response | Promise<Response> | unknown;
|
|
18
7
|
type MockResponse = unknown | MockResponseHandler;
|
|
19
8
|
interface MockRule {
|
|
20
|
-
|
|
9
|
+
handler: MockResponse;
|
|
21
10
|
status?: number;
|
|
22
11
|
headers?: Record<string, string>;
|
|
23
12
|
delay?: number;
|
|
24
13
|
}
|
|
14
|
+
interface DirectoryConfig {
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
status?: number;
|
|
17
|
+
delay?: number;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
middleware?: MockMiddleware | MockMiddleware[];
|
|
20
|
+
}
|
|
25
21
|
interface MokupViteOptions {
|
|
26
22
|
dir?: string | string[] | ((root: string) => string | string[]);
|
|
27
23
|
prefix?: string;
|
|
@@ -36,4 +32,4 @@ interface MokupViteOptions {
|
|
|
36
32
|
}
|
|
37
33
|
type MokupViteOptionsInput = MokupViteOptions | MokupViteOptions[];
|
|
38
34
|
|
|
39
|
-
export type { HttpMethod, MockContext,
|
|
35
|
+
export type { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions, MokupViteOptionsInput };
|
package/dist/vite.cjs
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const node_process = require('node:process');
|
|
4
4
|
const chokidar = require('chokidar');
|
|
5
5
|
const node_buffer = require('node:buffer');
|
|
6
|
-
const
|
|
6
|
+
const hono = require('hono');
|
|
7
|
+
const patternRouter = require('hono/router/pattern-router');
|
|
7
8
|
const pathe = require('pathe');
|
|
8
9
|
const node_fs = require('node:fs');
|
|
9
10
|
const node_module = require('node:module');
|
|
11
|
+
const runtime = require('@mokup/runtime');
|
|
10
12
|
const node_url = require('node:url');
|
|
11
13
|
const esbuild = require('esbuild');
|
|
12
14
|
const jsoncParser = require('jsonc-parser');
|
|
@@ -122,25 +124,113 @@ function delay(ms) {
|
|
|
122
124
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
function toHonoPath(route) {
|
|
128
|
+
if (!route.tokens || route.tokens.length === 0) {
|
|
129
|
+
return "/";
|
|
130
|
+
}
|
|
131
|
+
const segments = route.tokens.map((token) => {
|
|
132
|
+
if (token.type === "static") {
|
|
133
|
+
return token.value;
|
|
134
|
+
}
|
|
135
|
+
if (token.type === "param") {
|
|
136
|
+
return `:${token.name}`;
|
|
137
|
+
}
|
|
138
|
+
if (token.type === "catchall") {
|
|
139
|
+
return `:${token.name}{.+}`;
|
|
140
|
+
}
|
|
141
|
+
return `:${token.name}{.+}?`;
|
|
142
|
+
});
|
|
143
|
+
return `/${segments.join("/")}`;
|
|
144
|
+
}
|
|
145
|
+
function applyRouteOverrides(response, route) {
|
|
146
|
+
const headers = new Headers(response.headers);
|
|
147
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
148
|
+
if (route.headers) {
|
|
149
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
150
|
+
headers.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const status = route.status ?? response.status;
|
|
154
|
+
if (status === response.status && !hasHeaders) {
|
|
155
|
+
return response;
|
|
156
|
+
}
|
|
157
|
+
return new Response(response.body, { status, headers });
|
|
158
|
+
}
|
|
159
|
+
function normalizeHandlerValue(c, value) {
|
|
160
|
+
if (value instanceof Response) {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === "undefined") {
|
|
164
|
+
const response = c.body(null);
|
|
165
|
+
if (response.status === 200) {
|
|
166
|
+
return new Response(response.body, {
|
|
167
|
+
status: 204,
|
|
168
|
+
headers: response.headers
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
return c.text(value);
|
|
175
|
+
}
|
|
176
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
177
|
+
if (!c.res.headers.get("content-type")) {
|
|
178
|
+
c.header("content-type", "application/octet-stream");
|
|
135
179
|
}
|
|
180
|
+
const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
|
|
181
|
+
return c.body(data);
|
|
136
182
|
}
|
|
137
|
-
return
|
|
183
|
+
return c.json(value);
|
|
184
|
+
}
|
|
185
|
+
function createRouteHandler(route) {
|
|
186
|
+
return async (c) => {
|
|
187
|
+
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
188
|
+
return normalizeHandlerValue(c, value);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function createFinalizeMiddleware(route) {
|
|
192
|
+
return async (c, next) => {
|
|
193
|
+
const response = await next();
|
|
194
|
+
const resolved = response ?? c.res;
|
|
195
|
+
if (route.delay && route.delay > 0) {
|
|
196
|
+
await delay(route.delay);
|
|
197
|
+
}
|
|
198
|
+
return applyRouteOverrides(resolved, route);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function wrapMiddleware(handler) {
|
|
202
|
+
return async (c, next) => {
|
|
203
|
+
const response = await handler(c, next);
|
|
204
|
+
return response ?? c.res;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function createHonoApp(routes) {
|
|
208
|
+
const app = new hono.Hono({ router: new patternRouter.PatternRouter(), strict: false });
|
|
209
|
+
for (const route of routes) {
|
|
210
|
+
const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
|
|
211
|
+
app.on(
|
|
212
|
+
route.method,
|
|
213
|
+
toHonoPath(route),
|
|
214
|
+
createFinalizeMiddleware(route),
|
|
215
|
+
...middlewares,
|
|
216
|
+
createRouteHandler(route)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return app;
|
|
138
220
|
}
|
|
139
221
|
async function readRawBody(req) {
|
|
140
|
-
return new Promise((resolve, reject) => {
|
|
222
|
+
return await new Promise((resolve, reject) => {
|
|
141
223
|
const chunks = [];
|
|
142
224
|
req.on("data", (chunk) => {
|
|
143
|
-
|
|
225
|
+
if (typeof chunk === "string") {
|
|
226
|
+
chunks.push(node_buffer.Buffer.from(chunk));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (chunk instanceof Uint8Array) {
|
|
230
|
+
chunks.push(chunk);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
chunks.push(node_buffer.Buffer.from(String(chunk)));
|
|
144
234
|
});
|
|
145
235
|
req.on("end", () => {
|
|
146
236
|
if (chunks.length === 0) {
|
|
@@ -152,148 +242,74 @@ async function readRawBody(req) {
|
|
|
152
242
|
req.on("error", reject);
|
|
153
243
|
});
|
|
154
244
|
}
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 };
|
|
245
|
+
function buildHeaders(headers) {
|
|
246
|
+
const result = new Headers();
|
|
247
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
248
|
+
if (typeof value === "undefined") {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
result.set(key, value.join(","));
|
|
253
|
+
} else {
|
|
254
|
+
result.set(key, value);
|
|
170
255
|
}
|
|
171
256
|
}
|
|
172
|
-
|
|
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 };
|
|
257
|
+
return result;
|
|
178
258
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
259
|
+
async function toRequest(req) {
|
|
260
|
+
const url = new URL(req.url ?? "/", "http://mokup.local");
|
|
261
|
+
const method = req.method ?? "GET";
|
|
262
|
+
const headers = buildHeaders(req.headers);
|
|
263
|
+
const init = { method, headers };
|
|
264
|
+
const rawBody = await readRawBody(req);
|
|
265
|
+
if (rawBody && method !== "GET" && method !== "HEAD") {
|
|
266
|
+
init.body = rawBody;
|
|
185
267
|
}
|
|
268
|
+
return new Request(url.toString(), init);
|
|
186
269
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
270
|
+
async function sendResponse(res, response) {
|
|
271
|
+
res.statusCode = response.status;
|
|
272
|
+
response.headers.forEach((value, key) => {
|
|
273
|
+
res.setHeader(key, value);
|
|
274
|
+
});
|
|
275
|
+
if (!response.body) {
|
|
192
276
|
res.end();
|
|
193
277
|
return;
|
|
194
278
|
}
|
|
195
|
-
|
|
196
|
-
|
|
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));
|
|
279
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
280
|
+
res.end(buffer);
|
|
213
281
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
const matched = runtime.matchRouteTokens(route.tokens, pathname);
|
|
220
|
-
if (matched) {
|
|
221
|
-
return { route, params: matched.params };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return null;
|
|
282
|
+
function hasMatch(app, method, pathname) {
|
|
283
|
+
const matchMethod = method === "HEAD" ? "GET" : method;
|
|
284
|
+
const match = app.router.match(matchMethod, pathname);
|
|
285
|
+
return !!match && match[0].length > 0;
|
|
225
286
|
}
|
|
226
|
-
function createMiddleware(
|
|
287
|
+
function createMiddleware(getApp, logger) {
|
|
227
288
|
return async (req, res, next) => {
|
|
289
|
+
const app = getApp();
|
|
290
|
+
if (!app) {
|
|
291
|
+
return next();
|
|
292
|
+
}
|
|
228
293
|
const url = req.url ?? "/";
|
|
229
294
|
const parsedUrl = new URL(url, "http://mokup.local");
|
|
230
295
|
const pathname = parsedUrl.pathname;
|
|
231
296
|
const method = normalizeMethod(req.method) ?? "GET";
|
|
232
|
-
|
|
233
|
-
if (!matched) {
|
|
297
|
+
if (!hasMatch(app, method, pathname)) {
|
|
234
298
|
return next();
|
|
235
299
|
}
|
|
300
|
+
const startedAt = Date.now();
|
|
236
301
|
try {
|
|
237
|
-
const
|
|
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 executeHandler = async () => {
|
|
255
|
-
return typeof matched.route.response === "function" ? await matched.route.response(mockReq, res, ctx) : matched.route.response;
|
|
256
|
-
};
|
|
257
|
-
const runMiddlewares = async (middlewares) => {
|
|
258
|
-
let lastIndex = -1;
|
|
259
|
-
const dispatch = async (index) => {
|
|
260
|
-
if (index <= lastIndex) {
|
|
261
|
-
throw new Error("Middleware next() called multiple times.");
|
|
262
|
-
}
|
|
263
|
-
lastIndex = index;
|
|
264
|
-
const entry = middlewares[index];
|
|
265
|
-
if (!entry) {
|
|
266
|
-
return executeHandler();
|
|
267
|
-
}
|
|
268
|
-
let nextResult;
|
|
269
|
-
const next2 = async () => {
|
|
270
|
-
nextResult = await dispatch(index + 1);
|
|
271
|
-
return nextResult;
|
|
272
|
-
};
|
|
273
|
-
const value = await entry.handle(mockReq, res, ctx, next2);
|
|
274
|
-
if (typeof value !== "undefined") {
|
|
275
|
-
return value;
|
|
276
|
-
}
|
|
277
|
-
return nextResult;
|
|
278
|
-
};
|
|
279
|
-
return dispatch(0);
|
|
280
|
-
};
|
|
281
|
-
const responseValue = matched.route.middlewares && matched.route.middlewares.length > 0 ? await runMiddlewares(matched.route.middlewares) : await executeHandler();
|
|
302
|
+
const response = await app.fetch(await toRequest(req));
|
|
282
303
|
if (res.writableEnded) {
|
|
283
304
|
return;
|
|
284
305
|
}
|
|
285
|
-
|
|
286
|
-
await delay(matched.route.delay);
|
|
287
|
-
}
|
|
288
|
-
applyHeaders(res, matched.route.headers);
|
|
289
|
-
if (matched.route.status) {
|
|
290
|
-
res.statusCode = matched.route.status;
|
|
291
|
-
}
|
|
292
|
-
writeResponse(res, responseValue);
|
|
306
|
+
await sendResponse(res, response);
|
|
293
307
|
logger.info(`${method} ${pathname} ${Date.now() - startedAt}ms`);
|
|
294
308
|
} catch (error) {
|
|
295
|
-
res.
|
|
296
|
-
|
|
309
|
+
if (!res.headersSent) {
|
|
310
|
+
res.statusCode = 500;
|
|
311
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
312
|
+
}
|
|
297
313
|
res.end("Mock handler error");
|
|
298
314
|
logger.error("Mock handler failed:", error);
|
|
299
315
|
}
|
|
@@ -475,7 +491,7 @@ function toPlaygroundRoute(route, root, groups) {
|
|
|
475
491
|
method: route.method,
|
|
476
492
|
url: route.template,
|
|
477
493
|
file: formatRouteFile(route.file, root),
|
|
478
|
-
type: typeof route.
|
|
494
|
+
type: typeof route.handler === "function" ? "handler" : "static",
|
|
479
495
|
status: route.status,
|
|
480
496
|
delay: route.delay,
|
|
481
497
|
middlewareCount: middlewareSources?.length ?? 0,
|
|
@@ -648,7 +664,7 @@ function resolveRule(params) {
|
|
|
648
664
|
method,
|
|
649
665
|
tokens: parsed.tokens,
|
|
650
666
|
score: parsed.score,
|
|
651
|
-
|
|
667
|
+
handler: params.rule.handler
|
|
652
668
|
};
|
|
653
669
|
if (typeof params.rule.status === "number") {
|
|
654
670
|
route.status = params.rule.status;
|
|
@@ -926,7 +942,7 @@ async function loadRules(file, server, logger) {
|
|
|
926
942
|
}
|
|
927
943
|
return [
|
|
928
944
|
{
|
|
929
|
-
|
|
945
|
+
handler: json
|
|
930
946
|
}
|
|
931
947
|
];
|
|
932
948
|
}
|
|
@@ -941,7 +957,7 @@ async function loadRules(file, server, logger) {
|
|
|
941
957
|
if (typeof value === "function") {
|
|
942
958
|
return [
|
|
943
959
|
{
|
|
944
|
-
|
|
960
|
+
handler: value
|
|
945
961
|
}
|
|
946
962
|
];
|
|
947
963
|
}
|
|
@@ -984,8 +1000,18 @@ async function scanRoutes(params) {
|
|
|
984
1000
|
if (!rule || typeof rule !== "object") {
|
|
985
1001
|
continue;
|
|
986
1002
|
}
|
|
987
|
-
|
|
988
|
-
|
|
1003
|
+
const ruleValue = rule;
|
|
1004
|
+
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
1005
|
+
(key2) => key2 in ruleValue
|
|
1006
|
+
);
|
|
1007
|
+
if (unsupportedKeys.length > 0) {
|
|
1008
|
+
params.logger.warn(
|
|
1009
|
+
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
1010
|
+
);
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof rule.handler === "undefined") {
|
|
1014
|
+
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
989
1015
|
continue;
|
|
990
1016
|
}
|
|
991
1017
|
const resolved = resolveRule({
|
|
@@ -1028,7 +1054,7 @@ function buildRouteSignature(routes) {
|
|
|
1028
1054
|
route.method,
|
|
1029
1055
|
route.template,
|
|
1030
1056
|
route.file,
|
|
1031
|
-
typeof route.
|
|
1057
|
+
typeof route.handler === "function" ? "handler" : "static",
|
|
1032
1058
|
route.status ?? "",
|
|
1033
1059
|
route.delay ?? ""
|
|
1034
1060
|
].join("|")
|
|
@@ -1052,6 +1078,7 @@ function resolvePlaygroundInput(list) {
|
|
|
1052
1078
|
function createMokupPlugin(options = {}) {
|
|
1053
1079
|
let root = node_process.cwd();
|
|
1054
1080
|
let routes = [];
|
|
1081
|
+
let app = null;
|
|
1055
1082
|
let previewWatcher = null;
|
|
1056
1083
|
let currentServer = null;
|
|
1057
1084
|
let lastSignature = null;
|
|
@@ -1103,6 +1130,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1103
1130
|
collected.push(...scanned);
|
|
1104
1131
|
}
|
|
1105
1132
|
routes = sortRoutes(collected);
|
|
1133
|
+
app = createHonoApp(routes);
|
|
1106
1134
|
const signature = buildRouteSignature(routes);
|
|
1107
1135
|
if (isViteDevServer(server) && server.ws) {
|
|
1108
1136
|
if (lastSignature && signature !== lastSignature) {
|
|
@@ -1125,7 +1153,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1125
1153
|
currentServer = server;
|
|
1126
1154
|
await refreshRoutes(server);
|
|
1127
1155
|
server.middlewares.use(playgroundMiddleware);
|
|
1128
|
-
server.middlewares.use(createMiddleware(() =>
|
|
1156
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1129
1157
|
if (!watchEnabled) {
|
|
1130
1158
|
return;
|
|
1131
1159
|
}
|
|
@@ -1152,7 +1180,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1152
1180
|
currentServer = server;
|
|
1153
1181
|
await refreshRoutes(server);
|
|
1154
1182
|
server.middlewares.use(playgroundMiddleware);
|
|
1155
|
-
server.middlewares.use(createMiddleware(() =>
|
|
1183
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1156
1184
|
if (!watchEnabled) {
|
|
1157
1185
|
return;
|
|
1158
1186
|
}
|
package/dist/vite.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { MokupViteOptionsInput } from './index.cjs';
|
|
3
|
-
export { HttpMethod, MockContext,
|
|
4
|
-
import '
|
|
3
|
+
export { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions } from './index.cjs';
|
|
4
|
+
import 'hono';
|
|
5
5
|
|
|
6
6
|
declare function createMokupPlugin(options?: MokupViteOptionsInput): Plugin;
|
|
7
7
|
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { MokupViteOptionsInput } from './index.mjs';
|
|
3
|
-
export { HttpMethod, MockContext,
|
|
4
|
-
import '
|
|
3
|
+
export { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions } from './index.mjs';
|
|
4
|
+
import 'hono';
|
|
5
5
|
|
|
6
6
|
declare function createMokupPlugin(options?: MokupViteOptionsInput): Plugin;
|
|
7
7
|
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { MokupViteOptionsInput } from './index.js';
|
|
3
|
-
export { HttpMethod, MockContext,
|
|
4
|
-
import '
|
|
3
|
+
export { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupViteOptions } from './index.js';
|
|
4
|
+
import 'hono';
|
|
5
5
|
|
|
6
6
|
declare function createMokupPlugin(options?: MokupViteOptionsInput): Plugin;
|
|
7
7
|
|
package/dist/vite.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { cwd } from 'node:process';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
3
|
import { Buffer } from 'node:buffer';
|
|
4
|
-
import {
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { PatternRouter } from 'hono/router/pattern-router';
|
|
5
6
|
import { resolve, isAbsolute, join, normalize, extname, dirname, relative, basename } from 'pathe';
|
|
6
7
|
import { promises } from 'node:fs';
|
|
7
8
|
import { createRequire } from 'node:module';
|
|
9
|
+
import { compareRouteScore, parseRouteTemplate } from '@mokup/runtime';
|
|
8
10
|
import { pathToFileURL } from 'node:url';
|
|
9
11
|
import { build } from 'esbuild';
|
|
10
12
|
import { parse } from 'jsonc-parser';
|
|
@@ -115,25 +117,113 @@ function delay(ms) {
|
|
|
115
117
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
function toHonoPath(route) {
|
|
121
|
+
if (!route.tokens || route.tokens.length === 0) {
|
|
122
|
+
return "/";
|
|
123
|
+
}
|
|
124
|
+
const segments = route.tokens.map((token) => {
|
|
125
|
+
if (token.type === "static") {
|
|
126
|
+
return token.value;
|
|
127
|
+
}
|
|
128
|
+
if (token.type === "param") {
|
|
129
|
+
return `:${token.name}`;
|
|
130
|
+
}
|
|
131
|
+
if (token.type === "catchall") {
|
|
132
|
+
return `:${token.name}{.+}`;
|
|
133
|
+
}
|
|
134
|
+
return `:${token.name}{.+}?`;
|
|
135
|
+
});
|
|
136
|
+
return `/${segments.join("/")}`;
|
|
137
|
+
}
|
|
138
|
+
function applyRouteOverrides(response, route) {
|
|
139
|
+
const headers = new Headers(response.headers);
|
|
140
|
+
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
141
|
+
if (route.headers) {
|
|
142
|
+
for (const [key, value] of Object.entries(route.headers)) {
|
|
143
|
+
headers.set(key, value);
|
|
128
144
|
}
|
|
129
145
|
}
|
|
130
|
-
|
|
146
|
+
const status = route.status ?? response.status;
|
|
147
|
+
if (status === response.status && !hasHeaders) {
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
return new Response(response.body, { status, headers });
|
|
151
|
+
}
|
|
152
|
+
function normalizeHandlerValue(c, value) {
|
|
153
|
+
if (value instanceof Response) {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === "undefined") {
|
|
157
|
+
const response = c.body(null);
|
|
158
|
+
if (response.status === 200) {
|
|
159
|
+
return new Response(response.body, {
|
|
160
|
+
status: 204,
|
|
161
|
+
headers: response.headers
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return response;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
return c.text(value);
|
|
168
|
+
}
|
|
169
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
170
|
+
if (!c.res.headers.get("content-type")) {
|
|
171
|
+
c.header("content-type", "application/octet-stream");
|
|
172
|
+
}
|
|
173
|
+
const data = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value);
|
|
174
|
+
return c.body(data);
|
|
175
|
+
}
|
|
176
|
+
return c.json(value);
|
|
177
|
+
}
|
|
178
|
+
function createRouteHandler(route) {
|
|
179
|
+
return async (c) => {
|
|
180
|
+
const value = typeof route.handler === "function" ? await route.handler(c) : route.handler;
|
|
181
|
+
return normalizeHandlerValue(c, value);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function createFinalizeMiddleware(route) {
|
|
185
|
+
return async (c, next) => {
|
|
186
|
+
const response = await next();
|
|
187
|
+
const resolved = response ?? c.res;
|
|
188
|
+
if (route.delay && route.delay > 0) {
|
|
189
|
+
await delay(route.delay);
|
|
190
|
+
}
|
|
191
|
+
return applyRouteOverrides(resolved, route);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function wrapMiddleware(handler) {
|
|
195
|
+
return async (c, next) => {
|
|
196
|
+
const response = await handler(c, next);
|
|
197
|
+
return response ?? c.res;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function createHonoApp(routes) {
|
|
201
|
+
const app = new Hono({ router: new PatternRouter(), strict: false });
|
|
202
|
+
for (const route of routes) {
|
|
203
|
+
const middlewares = route.middlewares?.map((entry) => wrapMiddleware(entry.handle)) ?? [];
|
|
204
|
+
app.on(
|
|
205
|
+
route.method,
|
|
206
|
+
toHonoPath(route),
|
|
207
|
+
createFinalizeMiddleware(route),
|
|
208
|
+
...middlewares,
|
|
209
|
+
createRouteHandler(route)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return app;
|
|
131
213
|
}
|
|
132
214
|
async function readRawBody(req) {
|
|
133
|
-
return new Promise((resolve, reject) => {
|
|
215
|
+
return await new Promise((resolve, reject) => {
|
|
134
216
|
const chunks = [];
|
|
135
217
|
req.on("data", (chunk) => {
|
|
136
|
-
|
|
218
|
+
if (typeof chunk === "string") {
|
|
219
|
+
chunks.push(Buffer.from(chunk));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (chunk instanceof Uint8Array) {
|
|
223
|
+
chunks.push(chunk);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
chunks.push(Buffer.from(String(chunk)));
|
|
137
227
|
});
|
|
138
228
|
req.on("end", () => {
|
|
139
229
|
if (chunks.length === 0) {
|
|
@@ -145,148 +235,74 @@ async function readRawBody(req) {
|
|
|
145
235
|
req.on("error", reject);
|
|
146
236
|
});
|
|
147
237
|
}
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (contentType === "application/json" || contentType.endsWith("+json")) {
|
|
159
|
-
try {
|
|
160
|
-
return { query, body: JSON.parse(rawText), rawBody: rawText };
|
|
161
|
-
} catch {
|
|
162
|
-
return { query, body: rawText, rawBody: rawText };
|
|
238
|
+
function buildHeaders(headers) {
|
|
239
|
+
const result = new Headers();
|
|
240
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
241
|
+
if (typeof value === "undefined") {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(value)) {
|
|
245
|
+
result.set(key, value.join(","));
|
|
246
|
+
} else {
|
|
247
|
+
result.set(key, value);
|
|
163
248
|
}
|
|
164
249
|
}
|
|
165
|
-
|
|
166
|
-
const params = new URLSearchParams(rawText);
|
|
167
|
-
const body = Object.fromEntries(params.entries());
|
|
168
|
-
return { query, body, rawBody: rawText };
|
|
169
|
-
}
|
|
170
|
-
return { query, body: rawText, rawBody: rawText };
|
|
250
|
+
return result;
|
|
171
251
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
252
|
+
async function toRequest(req) {
|
|
253
|
+
const url = new URL(req.url ?? "/", "http://mokup.local");
|
|
254
|
+
const method = req.method ?? "GET";
|
|
255
|
+
const headers = buildHeaders(req.headers);
|
|
256
|
+
const init = { method, headers };
|
|
257
|
+
const rawBody = await readRawBody(req);
|
|
258
|
+
if (rawBody && method !== "GET" && method !== "HEAD") {
|
|
259
|
+
init.body = rawBody;
|
|
178
260
|
}
|
|
261
|
+
return new Request(url.toString(), init);
|
|
179
262
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
263
|
+
async function sendResponse(res, response) {
|
|
264
|
+
res.statusCode = response.status;
|
|
265
|
+
response.headers.forEach((value, key) => {
|
|
266
|
+
res.setHeader(key, value);
|
|
267
|
+
});
|
|
268
|
+
if (!response.body) {
|
|
185
269
|
res.end();
|
|
186
270
|
return;
|
|
187
271
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
res.setHeader("Content-Type", "application/octet-stream");
|
|
191
|
-
}
|
|
192
|
-
res.end(body);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (typeof body === "string") {
|
|
196
|
-
if (!res.getHeader("Content-Type")) {
|
|
197
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
198
|
-
}
|
|
199
|
-
res.end(body);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (!res.getHeader("Content-Type")) {
|
|
203
|
-
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
204
|
-
}
|
|
205
|
-
res.end(JSON.stringify(body));
|
|
272
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
273
|
+
res.end(buffer);
|
|
206
274
|
}
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
const matched = matchRouteTokens(route.tokens, pathname);
|
|
213
|
-
if (matched) {
|
|
214
|
-
return { route, params: matched.params };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return null;
|
|
275
|
+
function hasMatch(app, method, pathname) {
|
|
276
|
+
const matchMethod = method === "HEAD" ? "GET" : method;
|
|
277
|
+
const match = app.router.match(matchMethod, pathname);
|
|
278
|
+
return !!match && match[0].length > 0;
|
|
218
279
|
}
|
|
219
|
-
function createMiddleware(
|
|
280
|
+
function createMiddleware(getApp, logger) {
|
|
220
281
|
return async (req, res, next) => {
|
|
282
|
+
const app = getApp();
|
|
283
|
+
if (!app) {
|
|
284
|
+
return next();
|
|
285
|
+
}
|
|
221
286
|
const url = req.url ?? "/";
|
|
222
287
|
const parsedUrl = new URL(url, "http://mokup.local");
|
|
223
288
|
const pathname = parsedUrl.pathname;
|
|
224
289
|
const method = normalizeMethod(req.method) ?? "GET";
|
|
225
|
-
|
|
226
|
-
if (!matched) {
|
|
290
|
+
if (!hasMatch(app, method, pathname)) {
|
|
227
291
|
return next();
|
|
228
292
|
}
|
|
293
|
+
const startedAt = Date.now();
|
|
229
294
|
try {
|
|
230
|
-
const
|
|
231
|
-
const mockReq = {
|
|
232
|
-
url: pathname,
|
|
233
|
-
method,
|
|
234
|
-
headers: req.headers,
|
|
235
|
-
query,
|
|
236
|
-
body,
|
|
237
|
-
params: matched.params
|
|
238
|
-
};
|
|
239
|
-
if (rawBody) {
|
|
240
|
-
mockReq.rawBody = rawBody;
|
|
241
|
-
}
|
|
242
|
-
const ctx = {
|
|
243
|
-
delay: (ms) => delay(ms),
|
|
244
|
-
json: (data) => data
|
|
245
|
-
};
|
|
246
|
-
const startedAt = Date.now();
|
|
247
|
-
const executeHandler = async () => {
|
|
248
|
-
return typeof matched.route.response === "function" ? await matched.route.response(mockReq, res, ctx) : matched.route.response;
|
|
249
|
-
};
|
|
250
|
-
const runMiddlewares = async (middlewares) => {
|
|
251
|
-
let lastIndex = -1;
|
|
252
|
-
const dispatch = async (index) => {
|
|
253
|
-
if (index <= lastIndex) {
|
|
254
|
-
throw new Error("Middleware next() called multiple times.");
|
|
255
|
-
}
|
|
256
|
-
lastIndex = index;
|
|
257
|
-
const entry = middlewares[index];
|
|
258
|
-
if (!entry) {
|
|
259
|
-
return executeHandler();
|
|
260
|
-
}
|
|
261
|
-
let nextResult;
|
|
262
|
-
const next2 = async () => {
|
|
263
|
-
nextResult = await dispatch(index + 1);
|
|
264
|
-
return nextResult;
|
|
265
|
-
};
|
|
266
|
-
const value = await entry.handle(mockReq, res, ctx, next2);
|
|
267
|
-
if (typeof value !== "undefined") {
|
|
268
|
-
return value;
|
|
269
|
-
}
|
|
270
|
-
return nextResult;
|
|
271
|
-
};
|
|
272
|
-
return dispatch(0);
|
|
273
|
-
};
|
|
274
|
-
const responseValue = matched.route.middlewares && matched.route.middlewares.length > 0 ? await runMiddlewares(matched.route.middlewares) : await executeHandler();
|
|
295
|
+
const response = await app.fetch(await toRequest(req));
|
|
275
296
|
if (res.writableEnded) {
|
|
276
297
|
return;
|
|
277
298
|
}
|
|
278
|
-
|
|
279
|
-
await delay(matched.route.delay);
|
|
280
|
-
}
|
|
281
|
-
applyHeaders(res, matched.route.headers);
|
|
282
|
-
if (matched.route.status) {
|
|
283
|
-
res.statusCode = matched.route.status;
|
|
284
|
-
}
|
|
285
|
-
writeResponse(res, responseValue);
|
|
299
|
+
await sendResponse(res, response);
|
|
286
300
|
logger.info(`${method} ${pathname} ${Date.now() - startedAt}ms`);
|
|
287
301
|
} catch (error) {
|
|
288
|
-
res.
|
|
289
|
-
|
|
302
|
+
if (!res.headersSent) {
|
|
303
|
+
res.statusCode = 500;
|
|
304
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
305
|
+
}
|
|
290
306
|
res.end("Mock handler error");
|
|
291
307
|
logger.error("Mock handler failed:", error);
|
|
292
308
|
}
|
|
@@ -468,7 +484,7 @@ function toPlaygroundRoute(route, root, groups) {
|
|
|
468
484
|
method: route.method,
|
|
469
485
|
url: route.template,
|
|
470
486
|
file: formatRouteFile(route.file, root),
|
|
471
|
-
type: typeof route.
|
|
487
|
+
type: typeof route.handler === "function" ? "handler" : "static",
|
|
472
488
|
status: route.status,
|
|
473
489
|
delay: route.delay,
|
|
474
490
|
middlewareCount: middlewareSources?.length ?? 0,
|
|
@@ -641,7 +657,7 @@ function resolveRule(params) {
|
|
|
641
657
|
method,
|
|
642
658
|
tokens: parsed.tokens,
|
|
643
659
|
score: parsed.score,
|
|
644
|
-
|
|
660
|
+
handler: params.rule.handler
|
|
645
661
|
};
|
|
646
662
|
if (typeof params.rule.status === "number") {
|
|
647
663
|
route.status = params.rule.status;
|
|
@@ -919,7 +935,7 @@ async function loadRules(file, server, logger) {
|
|
|
919
935
|
}
|
|
920
936
|
return [
|
|
921
937
|
{
|
|
922
|
-
|
|
938
|
+
handler: json
|
|
923
939
|
}
|
|
924
940
|
];
|
|
925
941
|
}
|
|
@@ -934,7 +950,7 @@ async function loadRules(file, server, logger) {
|
|
|
934
950
|
if (typeof value === "function") {
|
|
935
951
|
return [
|
|
936
952
|
{
|
|
937
|
-
|
|
953
|
+
handler: value
|
|
938
954
|
}
|
|
939
955
|
];
|
|
940
956
|
}
|
|
@@ -977,8 +993,18 @@ async function scanRoutes(params) {
|
|
|
977
993
|
if (!rule || typeof rule !== "object") {
|
|
978
994
|
continue;
|
|
979
995
|
}
|
|
980
|
-
|
|
981
|
-
|
|
996
|
+
const ruleValue = rule;
|
|
997
|
+
const unsupportedKeys = ["response", "url", "method"].filter(
|
|
998
|
+
(key2) => key2 in ruleValue
|
|
999
|
+
);
|
|
1000
|
+
if (unsupportedKeys.length > 0) {
|
|
1001
|
+
params.logger.warn(
|
|
1002
|
+
`Skip mock with unsupported fields (${unsupportedKeys.join(", ")}): ${fileInfo.file}`
|
|
1003
|
+
);
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (typeof rule.handler === "undefined") {
|
|
1007
|
+
params.logger.warn(`Skip mock without handler: ${fileInfo.file}`);
|
|
982
1008
|
continue;
|
|
983
1009
|
}
|
|
984
1010
|
const resolved = resolveRule({
|
|
@@ -1021,7 +1047,7 @@ function buildRouteSignature(routes) {
|
|
|
1021
1047
|
route.method,
|
|
1022
1048
|
route.template,
|
|
1023
1049
|
route.file,
|
|
1024
|
-
typeof route.
|
|
1050
|
+
typeof route.handler === "function" ? "handler" : "static",
|
|
1025
1051
|
route.status ?? "",
|
|
1026
1052
|
route.delay ?? ""
|
|
1027
1053
|
].join("|")
|
|
@@ -1045,6 +1071,7 @@ function resolvePlaygroundInput(list) {
|
|
|
1045
1071
|
function createMokupPlugin(options = {}) {
|
|
1046
1072
|
let root = cwd();
|
|
1047
1073
|
let routes = [];
|
|
1074
|
+
let app = null;
|
|
1048
1075
|
let previewWatcher = null;
|
|
1049
1076
|
let currentServer = null;
|
|
1050
1077
|
let lastSignature = null;
|
|
@@ -1096,6 +1123,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1096
1123
|
collected.push(...scanned);
|
|
1097
1124
|
}
|
|
1098
1125
|
routes = sortRoutes(collected);
|
|
1126
|
+
app = createHonoApp(routes);
|
|
1099
1127
|
const signature = buildRouteSignature(routes);
|
|
1100
1128
|
if (isViteDevServer(server) && server.ws) {
|
|
1101
1129
|
if (lastSignature && signature !== lastSignature) {
|
|
@@ -1118,7 +1146,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1118
1146
|
currentServer = server;
|
|
1119
1147
|
await refreshRoutes(server);
|
|
1120
1148
|
server.middlewares.use(playgroundMiddleware);
|
|
1121
|
-
server.middlewares.use(createMiddleware(() =>
|
|
1149
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1122
1150
|
if (!watchEnabled) {
|
|
1123
1151
|
return;
|
|
1124
1152
|
}
|
|
@@ -1145,7 +1173,7 @@ function createMokupPlugin(options = {}) {
|
|
|
1145
1173
|
currentServer = server;
|
|
1146
1174
|
await refreshRoutes(server);
|
|
1147
1175
|
server.middlewares.use(playgroundMiddleware);
|
|
1148
|
-
server.middlewares.use(createMiddleware(() =>
|
|
1176
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1149
1177
|
if (!watchEnabled) {
|
|
1150
1178
|
return;
|
|
1151
1179
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mokup",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"description": "Mock utilities and Vite plugin for mokup.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://mokup.icebreaker.top",
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"chokidar": "^5.0.0",
|
|
39
39
|
"esbuild": "^0.27.2",
|
|
40
|
+
"hono": "^4.11.4",
|
|
40
41
|
"jsonc-parser": "^3.3.1",
|
|
41
42
|
"pathe": "^2.0.3",
|
|
42
43
|
"@mokup/playground": "0.0.1",
|
|
43
|
-
"@mokup/runtime": "0.0
|
|
44
|
+
"@mokup/runtime": "0.1.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@types/node": "^25.0.9",
|