emulate 0.4.1 → 0.6.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/README.md +198 -27
- package/dist/api.d.ts +2 -1
- package/dist/api.js +675 -103
- package/dist/api.js.map +1 -1
- package/dist/chunk-WVQMFHQM.js +83 -0
- package/dist/chunk-WVQMFHQM.js.map +1 -0
- package/dist/{dist-B674PYKV.js → dist-2ZZGNPJI.js} +22 -43
- package/dist/dist-2ZZGNPJI.js.map +1 -0
- package/dist/{dist-RDFBZ5O6.js → dist-CXRPM6BK.js} +211 -48
- package/dist/dist-CXRPM6BK.js.map +1 -0
- package/dist/{dist-VVXVP5EZ.js → dist-DSJSF3GY.js} +551 -91
- package/dist/dist-DSJSF3GY.js.map +1 -0
- package/dist/{dist-RMK3BS5M.js → dist-IFULY5LE.js} +196 -33
- package/dist/dist-IFULY5LE.js.map +1 -0
- package/dist/dist-IRUBHCZU.js +1898 -0
- package/dist/dist-IRUBHCZU.js.map +1 -0
- package/dist/{dist-YOVM5HEY.js → dist-NJJLJT2N.js} +520 -61
- package/dist/dist-NJJLJT2N.js.map +1 -0
- package/dist/dist-OGSAVJ25.js +4874 -0
- package/dist/dist-OGSAVJ25.js.map +1 -0
- package/dist/{dist-H6JYGQM4.js → dist-PO4CL5SJ.js} +271 -158
- package/dist/dist-PO4CL5SJ.js.map +1 -0
- package/dist/{dist-QMOJM6DV.js → dist-R3TNKUIE.js} +238 -55
- package/dist/dist-R3TNKUIE.js.map +1 -0
- package/dist/{dist-6JFNJPUU.js → dist-WACHAAVU.js} +171 -22
- package/dist/dist-WACHAAVU.js.map +1 -0
- package/dist/{dist-OTJZRQ3Q.js → dist-XWWZVLQQ.js} +216 -75
- package/dist/dist-XWWZVLQQ.js.map +1 -0
- package/dist/{dist-6EW7SSOZ.js → dist-ZY5SZSJ2.js} +397 -223
- package/dist/dist-ZY5SZSJ2.js.map +1 -0
- package/dist/fonts/favicon.ico +0 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
- package/dist/index.js +812 -117
- package/dist/index.js.map +1 -1
- package/package.json +17 -15
- package/dist/chunk-TEPNEZ63.js +0 -2143
- package/dist/chunk-TEPNEZ63.js.map +0 -1
- package/dist/dist-6EW7SSOZ.js.map +0 -1
- package/dist/dist-6JFNJPUU.js.map +0 -1
- package/dist/dist-B674PYKV.js.map +0 -1
- package/dist/dist-G7WQPZ3Y.js +0 -1287
- package/dist/dist-G7WQPZ3Y.js.map +0 -1
- package/dist/dist-H6JYGQM4.js.map +0 -1
- package/dist/dist-OTJZRQ3Q.js.map +0 -1
- package/dist/dist-QMOJM6DV.js.map +0 -1
- package/dist/dist-RDFBZ5O6.js.map +0 -1
- package/dist/dist-RMK3BS5M.js.map +0 -1
- package/dist/dist-VVXVP5EZ.js.map +0 -1
- package/dist/dist-YOVM5HEY.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -3,15 +3,12 @@ import {
|
|
|
3
3
|
importPKCS8,
|
|
4
4
|
jwtVerify
|
|
5
5
|
} from "./chunk-D6EKRYGP.js";
|
|
6
|
-
import {
|
|
7
|
-
Hono,
|
|
8
|
-
cors
|
|
9
|
-
} from "./chunk-TEPNEZ63.js";
|
|
10
6
|
|
|
11
7
|
// src/index.ts
|
|
12
8
|
import { Command } from "commander";
|
|
13
9
|
|
|
14
10
|
// ../@emulators/core/dist/index.js
|
|
11
|
+
import { createServer as createNodeServer } from "http";
|
|
15
12
|
import { createHmac } from "crypto";
|
|
16
13
|
import { readFileSync } from "fs";
|
|
17
14
|
import { fileURLToPath } from "url";
|
|
@@ -235,6 +232,397 @@ var Store = class {
|
|
|
235
232
|
}
|
|
236
233
|
}
|
|
237
234
|
};
|
|
235
|
+
var HonoRequest = class {
|
|
236
|
+
constructor(request, params) {
|
|
237
|
+
this.params = params;
|
|
238
|
+
this.raw = request;
|
|
239
|
+
this.url = request.url;
|
|
240
|
+
this.method = request.method;
|
|
241
|
+
this.path = new URL(request.url).pathname;
|
|
242
|
+
}
|
|
243
|
+
raw;
|
|
244
|
+
url;
|
|
245
|
+
method;
|
|
246
|
+
path;
|
|
247
|
+
header(name) {
|
|
248
|
+
if (name) return this.raw.headers.get(name) ?? void 0;
|
|
249
|
+
const headers = {};
|
|
250
|
+
this.raw.headers.forEach((value, key) => {
|
|
251
|
+
headers[key] = value;
|
|
252
|
+
});
|
|
253
|
+
return headers;
|
|
254
|
+
}
|
|
255
|
+
query(name) {
|
|
256
|
+
return new URL(this.url).searchParams.get(name) ?? void 0;
|
|
257
|
+
}
|
|
258
|
+
queries(name) {
|
|
259
|
+
const values = new URL(this.url).searchParams.getAll(name);
|
|
260
|
+
return values.length > 0 ? values : void 0;
|
|
261
|
+
}
|
|
262
|
+
param(name) {
|
|
263
|
+
if (!name) return { ...this.params };
|
|
264
|
+
return this.params[name] ?? "";
|
|
265
|
+
}
|
|
266
|
+
json() {
|
|
267
|
+
return this.raw.json();
|
|
268
|
+
}
|
|
269
|
+
text() {
|
|
270
|
+
return this.raw.text();
|
|
271
|
+
}
|
|
272
|
+
arrayBuffer() {
|
|
273
|
+
return this.raw.arrayBuffer();
|
|
274
|
+
}
|
|
275
|
+
async parseBody() {
|
|
276
|
+
const contentType = this.header("Content-Type") ?? "";
|
|
277
|
+
if (contentType.includes("multipart/form-data")) {
|
|
278
|
+
return formDataToObject(await this.raw.formData());
|
|
279
|
+
}
|
|
280
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
281
|
+
const params = new URLSearchParams(await this.raw.text());
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const [key, value] of params) {
|
|
284
|
+
appendBodyValue(out, key, value);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
if (contentType.includes("application/json")) {
|
|
289
|
+
const body = await this.raw.json().catch(() => ({}));
|
|
290
|
+
return body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
291
|
+
}
|
|
292
|
+
return {};
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
var Context = class {
|
|
296
|
+
constructor(request, params, notFoundHandler) {
|
|
297
|
+
this.notFoundHandler = notFoundHandler;
|
|
298
|
+
this.req = new HonoRequest(request, params);
|
|
299
|
+
}
|
|
300
|
+
req;
|
|
301
|
+
vars = /* @__PURE__ */ new Map();
|
|
302
|
+
responseHeaders = new Headers();
|
|
303
|
+
responseStatus = 200;
|
|
304
|
+
get(key) {
|
|
305
|
+
return this.vars.get(key);
|
|
306
|
+
}
|
|
307
|
+
set(key, value) {
|
|
308
|
+
this.vars.set(key, value);
|
|
309
|
+
}
|
|
310
|
+
header(name, value) {
|
|
311
|
+
this.responseHeaders.set(name, value);
|
|
312
|
+
}
|
|
313
|
+
status(status) {
|
|
314
|
+
this.responseStatus = status;
|
|
315
|
+
}
|
|
316
|
+
json(data, status, headers) {
|
|
317
|
+
return this.response(JSON.stringify(data), status, defaultContentType(headers, "application/json; charset=UTF-8"));
|
|
318
|
+
}
|
|
319
|
+
text(text, status, headers) {
|
|
320
|
+
return this.response(text, status, defaultContentType(headers, "text/plain; charset=UTF-8"));
|
|
321
|
+
}
|
|
322
|
+
html(html, status, headers) {
|
|
323
|
+
return this.response(html, status, defaultContentType(headers, "text/html; charset=UTF-8"));
|
|
324
|
+
}
|
|
325
|
+
body(body, status, headers) {
|
|
326
|
+
return this.response(body, status, headers);
|
|
327
|
+
}
|
|
328
|
+
redirect(location, status = 302) {
|
|
329
|
+
return this.response(null, status, { Location: location });
|
|
330
|
+
}
|
|
331
|
+
notFound() {
|
|
332
|
+
return this.notFoundHandler(this);
|
|
333
|
+
}
|
|
334
|
+
finalize(response) {
|
|
335
|
+
if (!hasHeaders(this.responseHeaders)) return response;
|
|
336
|
+
const headers = new Headers(response.headers);
|
|
337
|
+
this.responseHeaders.forEach((value, key) => {
|
|
338
|
+
headers.set(key, value);
|
|
339
|
+
});
|
|
340
|
+
return new Response(response.body, {
|
|
341
|
+
status: response.status,
|
|
342
|
+
statusText: response.statusText,
|
|
343
|
+
headers
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
response(body, status, headers) {
|
|
347
|
+
const merged = new Headers(headers);
|
|
348
|
+
this.responseHeaders.forEach((value, key) => {
|
|
349
|
+
merged.set(key, value);
|
|
350
|
+
});
|
|
351
|
+
return new Response(body, {
|
|
352
|
+
status: status ?? this.responseStatus,
|
|
353
|
+
headers: merged
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
var Hono = class {
|
|
358
|
+
middleware = [];
|
|
359
|
+
routes = [];
|
|
360
|
+
errorHandler = (err) => {
|
|
361
|
+
const message = err instanceof Error ? err.message : "Internal Server Error";
|
|
362
|
+
return new Response(message, { status: 500 });
|
|
363
|
+
};
|
|
364
|
+
notFoundHandler = () => new Response("404 Not Found", { status: 404 });
|
|
365
|
+
use(pathOrHandler, ...handlers) {
|
|
366
|
+
if (typeof pathOrHandler === "string") {
|
|
367
|
+
this.middleware.push({ method: "ALL", compiled: compilePath(pathOrHandler), handlers });
|
|
368
|
+
} else {
|
|
369
|
+
this.middleware.push({ method: "ALL", compiled: compilePath("*"), handlers: [pathOrHandler, ...handlers] });
|
|
370
|
+
}
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
on(method, path, ...handlers) {
|
|
374
|
+
this.routes.push({ method: method.toUpperCase(), compiled: compilePath(path), handlers });
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
get(path, ...handlers) {
|
|
378
|
+
return this.on("GET", path, ...handlers);
|
|
379
|
+
}
|
|
380
|
+
post(path, ...handlers) {
|
|
381
|
+
return this.on("POST", path, ...handlers);
|
|
382
|
+
}
|
|
383
|
+
put(path, ...handlers) {
|
|
384
|
+
return this.on("PUT", path, ...handlers);
|
|
385
|
+
}
|
|
386
|
+
patch(path, ...handlers) {
|
|
387
|
+
return this.on("PATCH", path, ...handlers);
|
|
388
|
+
}
|
|
389
|
+
delete(path, ...handlers) {
|
|
390
|
+
return this.on("DELETE", path, ...handlers);
|
|
391
|
+
}
|
|
392
|
+
onError(handler) {
|
|
393
|
+
this.errorHandler = handler;
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
notFound(handler) {
|
|
397
|
+
this.notFoundHandler = handler;
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
async request(input, init) {
|
|
401
|
+
if (input instanceof Request) return this.fetch(input);
|
|
402
|
+
const url = input.startsWith("/") ? `http://localhost${input}` : input;
|
|
403
|
+
return this.fetch(new Request(url, init));
|
|
404
|
+
}
|
|
405
|
+
fetch = async (request) => {
|
|
406
|
+
const url = new URL(request.url);
|
|
407
|
+
const path = url.pathname;
|
|
408
|
+
const method = request.method.toUpperCase();
|
|
409
|
+
const matched = this.match(method, path);
|
|
410
|
+
const context = new Context(request, matched.params, this.notFoundHandler);
|
|
411
|
+
try {
|
|
412
|
+
const response = await this.dispatch(context, matched.handlers);
|
|
413
|
+
return context.finalize(response ?? await this.notFoundHandler(context));
|
|
414
|
+
} catch (err) {
|
|
415
|
+
return context.finalize(await this.errorHandler(err, context));
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
match(method, path) {
|
|
419
|
+
const handlers = [];
|
|
420
|
+
const params = {};
|
|
421
|
+
for (const route2 of this.middleware) {
|
|
422
|
+
const match = matchPath(route2.compiled, path);
|
|
423
|
+
if (!match) continue;
|
|
424
|
+
Object.assign(params, match);
|
|
425
|
+
for (const handler of route2.handlers) {
|
|
426
|
+
handlers.push({ handler, params: match });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const route = this.routes.find((candidate) => candidate.method === method && matchPath(candidate.compiled, path) != null) ?? (method === "HEAD" ? this.routes.find((candidate) => candidate.method === "GET" && matchPath(candidate.compiled, path) != null) : void 0);
|
|
430
|
+
if (route) {
|
|
431
|
+
const match = matchPath(route.compiled, path) ?? {};
|
|
432
|
+
Object.assign(params, match);
|
|
433
|
+
for (const handler of route.handlers) {
|
|
434
|
+
handlers.push({ handler, params: match });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { handlers, params };
|
|
438
|
+
}
|
|
439
|
+
async dispatch(context, handlers) {
|
|
440
|
+
let index = -1;
|
|
441
|
+
const run = async (nextIndex) => {
|
|
442
|
+
if (nextIndex <= index) throw new Error("next() called multiple times");
|
|
443
|
+
index = nextIndex;
|
|
444
|
+
const matched = handlers[nextIndex];
|
|
445
|
+
if (!matched) return void 0;
|
|
446
|
+
const originalParams = context.req.param();
|
|
447
|
+
Object.assign(originalParams, matched.params);
|
|
448
|
+
let nextResponse = void 0;
|
|
449
|
+
let nextCalled = false;
|
|
450
|
+
const next = async () => {
|
|
451
|
+
nextCalled = true;
|
|
452
|
+
nextResponse = await run(nextIndex + 1);
|
|
453
|
+
};
|
|
454
|
+
const response = await matched.handler(context, next);
|
|
455
|
+
if (response instanceof Response) return response;
|
|
456
|
+
if (nextCalled) return nextResponse;
|
|
457
|
+
return response;
|
|
458
|
+
};
|
|
459
|
+
return run(0);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
function cors(options = {}) {
|
|
463
|
+
const origin = options.origin ?? "*";
|
|
464
|
+
const allowMethods = options.allowMethods ?? ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH", "OPTIONS"];
|
|
465
|
+
return async (c, next) => {
|
|
466
|
+
c.header("Access-Control-Allow-Origin", origin);
|
|
467
|
+
if (options.credentials) c.header("Access-Control-Allow-Credentials", "true");
|
|
468
|
+
if (c.req.method.toUpperCase() === "OPTIONS") {
|
|
469
|
+
c.header("Access-Control-Allow-Methods", allowMethods.join(","));
|
|
470
|
+
const allowHeaders = options.allowHeaders?.join(",") ?? c.req.header("Access-Control-Request-Headers");
|
|
471
|
+
if (allowHeaders) c.header("Access-Control-Allow-Headers", allowHeaders);
|
|
472
|
+
if (options.maxAge != null) c.header("Access-Control-Max-Age", String(options.maxAge));
|
|
473
|
+
return c.body(null, 204);
|
|
474
|
+
}
|
|
475
|
+
await next();
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function serve(options) {
|
|
479
|
+
const port = options.port ?? 3e3;
|
|
480
|
+
const server = createNodeServer(async (req, res) => {
|
|
481
|
+
try {
|
|
482
|
+
const request = nodeRequestToFetchRequest(req);
|
|
483
|
+
const response = await options.fetch(request);
|
|
484
|
+
await writeFetchResponse(res, response, req.method?.toUpperCase() === "HEAD");
|
|
485
|
+
} catch (err) {
|
|
486
|
+
const message = err instanceof Error ? err.message : "Internal Server Error";
|
|
487
|
+
res.statusCode = 500;
|
|
488
|
+
res.setHeader("Content-Type", "text/plain; charset=UTF-8");
|
|
489
|
+
res.end(message);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
server.listen(port, options.hostname);
|
|
493
|
+
return server;
|
|
494
|
+
}
|
|
495
|
+
function compilePath(pattern) {
|
|
496
|
+
if (pattern === "*" || pattern === "/*") {
|
|
497
|
+
return { pattern, regex: /^.*$/, paramNames: [] };
|
|
498
|
+
}
|
|
499
|
+
const paramNames = [];
|
|
500
|
+
let source = "^";
|
|
501
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
502
|
+
const char = pattern[i];
|
|
503
|
+
if (char !== ":") {
|
|
504
|
+
source += escapeRegex(char);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
let name = "";
|
|
508
|
+
i++;
|
|
509
|
+
while (i < pattern.length && /[A-Za-z0-9_]/.test(pattern[i])) {
|
|
510
|
+
name += pattern[i];
|
|
511
|
+
i++;
|
|
512
|
+
}
|
|
513
|
+
i--;
|
|
514
|
+
paramNames.push(name);
|
|
515
|
+
if (pattern[i + 1] === "{") {
|
|
516
|
+
const close = pattern.indexOf("}", i + 2);
|
|
517
|
+
if (close < 0) throw new Error(`Invalid route pattern: ${pattern}`);
|
|
518
|
+
const expr = pattern.slice(i + 2, close);
|
|
519
|
+
source += `(${expr})`;
|
|
520
|
+
i = close;
|
|
521
|
+
} else {
|
|
522
|
+
source += "([^/]+)";
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
source += "$";
|
|
526
|
+
return { pattern, regex: new RegExp(source), paramNames };
|
|
527
|
+
}
|
|
528
|
+
function matchPath(compiled, path) {
|
|
529
|
+
const match = compiled.regex.exec(path);
|
|
530
|
+
if (!match) return null;
|
|
531
|
+
const params = {};
|
|
532
|
+
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
533
|
+
params[compiled.paramNames[i]] = decodePathParam(match[i + 1] ?? "");
|
|
534
|
+
}
|
|
535
|
+
return params;
|
|
536
|
+
}
|
|
537
|
+
function decodePathParam(value) {
|
|
538
|
+
try {
|
|
539
|
+
return decodeURIComponent(value);
|
|
540
|
+
} catch {
|
|
541
|
+
return value;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function escapeRegex(value) {
|
|
545
|
+
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
546
|
+
}
|
|
547
|
+
function hasHeaders(headers) {
|
|
548
|
+
for (const _ of headers) return true;
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
function defaultContentType(headers, contentType) {
|
|
552
|
+
const out = new Headers(headers);
|
|
553
|
+
if (!out.has("Content-Type")) {
|
|
554
|
+
out.set("Content-Type", contentType);
|
|
555
|
+
}
|
|
556
|
+
return out;
|
|
557
|
+
}
|
|
558
|
+
function formDataToObject(formData) {
|
|
559
|
+
const out = {};
|
|
560
|
+
for (const [key, value] of formData) {
|
|
561
|
+
appendBodyValue(out, key, value);
|
|
562
|
+
}
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
function appendBodyValue(target, key, value) {
|
|
566
|
+
const existing = target[key];
|
|
567
|
+
if (existing === void 0) {
|
|
568
|
+
target[key] = value;
|
|
569
|
+
} else if (Array.isArray(existing)) {
|
|
570
|
+
existing.push(value);
|
|
571
|
+
} else {
|
|
572
|
+
target[key] = [existing, value];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function nodeRequestToFetchRequest(req) {
|
|
576
|
+
const host = req.headers.host ?? "localhost";
|
|
577
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
578
|
+
const headers = new Headers();
|
|
579
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
580
|
+
if (value == null) continue;
|
|
581
|
+
if (Array.isArray(value)) {
|
|
582
|
+
for (const item of value) headers.append(key, item);
|
|
583
|
+
} else {
|
|
584
|
+
headers.set(key, value);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const method = req.method ?? "GET";
|
|
588
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
589
|
+
return new Request(url.toString(), {
|
|
590
|
+
method,
|
|
591
|
+
headers,
|
|
592
|
+
body: hasBody ? req : void 0,
|
|
593
|
+
duplex: "half"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
async function writeFetchResponse(res, response, headOnly) {
|
|
597
|
+
res.statusCode = response.status;
|
|
598
|
+
res.statusMessage = response.statusText;
|
|
599
|
+
const headersWithCookies = response.headers;
|
|
600
|
+
const cookies = headersWithCookies.getSetCookie?.();
|
|
601
|
+
response.headers.forEach((value, key) => {
|
|
602
|
+
if (key.toLowerCase() === "set-cookie" && cookies && cookies.length > 0) return;
|
|
603
|
+
res.setHeader(key, value);
|
|
604
|
+
});
|
|
605
|
+
if (cookies && cookies.length > 0) {
|
|
606
|
+
res.setHeader("Set-Cookie", cookies);
|
|
607
|
+
}
|
|
608
|
+
if (headOnly || !response.body) {
|
|
609
|
+
res.end();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const reader = response.body.getReader();
|
|
613
|
+
try {
|
|
614
|
+
while (true) {
|
|
615
|
+
const { done, value } = await reader.read();
|
|
616
|
+
if (done) break;
|
|
617
|
+
if (!res.write(value)) {
|
|
618
|
+
await new Promise((resolve3) => res.once("drain", resolve3));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
res.end();
|
|
622
|
+
} catch (err) {
|
|
623
|
+
res.destroy(err instanceof Error ? err : void 0);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
238
626
|
var MAX_DELIVERIES = 1e3;
|
|
239
627
|
var WebhookDispatcher = class {
|
|
240
628
|
subscriptions = [];
|
|
@@ -391,9 +779,7 @@ function authMiddleware(tokens, appKeyResolver, fallbackUser) {
|
|
|
391
779
|
if (token.startsWith("eyJ") && appKeyResolver) {
|
|
392
780
|
try {
|
|
393
781
|
const [, payloadB64] = token.split(".");
|
|
394
|
-
const payload = JSON.parse(
|
|
395
|
-
Buffer.from(payloadB64, "base64url").toString()
|
|
396
|
-
);
|
|
782
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
397
783
|
const appId = typeof payload.iss === "string" ? parseInt(payload.iss, 10) : payload.iss;
|
|
398
784
|
if (typeof appId === "number" && !isNaN(appId)) {
|
|
399
785
|
const appInfo = appKeyResolver(appId);
|
|
@@ -430,6 +816,7 @@ var FONTS = {
|
|
|
430
816
|
"geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
|
|
431
817
|
"GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
|
|
432
818
|
};
|
|
819
|
+
var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
|
|
433
820
|
function registerFontRoutes(app) {
|
|
434
821
|
app.get("/_emulate/fonts/:name", (c) => {
|
|
435
822
|
const name = c.req.param("name");
|
|
@@ -443,6 +830,14 @@ function registerFontRoutes(app) {
|
|
|
443
830
|
}
|
|
444
831
|
});
|
|
445
832
|
});
|
|
833
|
+
app.get("/_emulate/favicon.ico", (c) => {
|
|
834
|
+
return new Response(FAVICON, {
|
|
835
|
+
headers: {
|
|
836
|
+
"Content-Type": "image/x-icon",
|
|
837
|
+
"Cache-Control": "public, max-age=31536000, immutable"
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
});
|
|
446
841
|
}
|
|
447
842
|
function createServer(plugin, options = {}) {
|
|
448
843
|
const port = options.port ?? 4e3;
|
|
@@ -512,14 +907,27 @@ function createServer(plugin, options = {}) {
|
|
|
512
907
|
}
|
|
513
908
|
|
|
514
909
|
// src/registry.ts
|
|
515
|
-
var SERVICE_NAME_LIST = [
|
|
910
|
+
var SERVICE_NAME_LIST = [
|
|
911
|
+
"vercel",
|
|
912
|
+
"github",
|
|
913
|
+
"google",
|
|
914
|
+
"slack",
|
|
915
|
+
"apple",
|
|
916
|
+
"microsoft",
|
|
917
|
+
"okta",
|
|
918
|
+
"aws",
|
|
919
|
+
"resend",
|
|
920
|
+
"stripe",
|
|
921
|
+
"mongoatlas",
|
|
922
|
+
"clerk"
|
|
923
|
+
];
|
|
516
924
|
var SERVICE_NAMES = SERVICE_NAME_LIST;
|
|
517
925
|
var SERVICE_REGISTRY = {
|
|
518
926
|
vercel: {
|
|
519
927
|
label: "Vercel REST API emulator",
|
|
520
928
|
endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass",
|
|
521
929
|
async load() {
|
|
522
|
-
const mod = await import("./dist-
|
|
930
|
+
const mod = await import("./dist-CXRPM6BK.js");
|
|
523
931
|
return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
|
|
524
932
|
},
|
|
525
933
|
defaultFallback(cfg) {
|
|
@@ -531,12 +939,14 @@ var SERVICE_REGISTRY = {
|
|
|
531
939
|
users: [{ username: "developer", name: "Developer", email: "dev@example.com" }],
|
|
532
940
|
teams: [{ slug: "my-team", name: "My Team" }],
|
|
533
941
|
projects: [{ name: "my-app", team: "my-team", framework: "nextjs" }],
|
|
534
|
-
integrations: [
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
942
|
+
integrations: [
|
|
943
|
+
{
|
|
944
|
+
client_id: "oac_example_client_id",
|
|
945
|
+
client_secret: "example_client_secret",
|
|
946
|
+
name: "My Vercel App",
|
|
947
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
|
|
948
|
+
}
|
|
949
|
+
]
|
|
540
950
|
}
|
|
541
951
|
}
|
|
542
952
|
},
|
|
@@ -544,7 +954,7 @@ var SERVICE_REGISTRY = {
|
|
|
544
954
|
label: "GitHub REST API emulator",
|
|
545
955
|
endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
|
|
546
956
|
async load() {
|
|
547
|
-
const mod = await import("./dist-
|
|
957
|
+
const mod = await import("./dist-PO4CL5SJ.js");
|
|
548
958
|
return {
|
|
549
959
|
plugin: mod.githubPlugin,
|
|
550
960
|
seedFromConfig: mod.seedFromConfig,
|
|
@@ -568,25 +978,42 @@ var SERVICE_REGISTRY = {
|
|
|
568
978
|
},
|
|
569
979
|
initConfig: {
|
|
570
980
|
github: {
|
|
571
|
-
users: [
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
981
|
+
users: [
|
|
982
|
+
{
|
|
983
|
+
login: "octocat",
|
|
984
|
+
name: "The Octocat",
|
|
985
|
+
email: "octocat@github.com",
|
|
986
|
+
bio: "I am the Octocat",
|
|
987
|
+
company: "GitHub",
|
|
988
|
+
location: "San Francisco"
|
|
989
|
+
}
|
|
990
|
+
],
|
|
579
991
|
orgs: [{ login: "my-org", name: "My Organization", description: "A test organization" }],
|
|
580
992
|
repos: [
|
|
581
|
-
{
|
|
582
|
-
|
|
993
|
+
{
|
|
994
|
+
owner: "octocat",
|
|
995
|
+
name: "hello-world",
|
|
996
|
+
description: "My first repository",
|
|
997
|
+
language: "JavaScript",
|
|
998
|
+
topics: ["hello", "world"],
|
|
999
|
+
auto_init: true
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
owner: "my-org",
|
|
1003
|
+
name: "org-repo",
|
|
1004
|
+
description: "An organization repository",
|
|
1005
|
+
language: "TypeScript",
|
|
1006
|
+
auto_init: true
|
|
1007
|
+
}
|
|
583
1008
|
],
|
|
584
|
-
oauth_apps: [
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1009
|
+
oauth_apps: [
|
|
1010
|
+
{
|
|
1011
|
+
client_id: "Iv1.example_client_id",
|
|
1012
|
+
client_secret: "example_client_secret",
|
|
1013
|
+
name: "My App",
|
|
1014
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
|
|
1015
|
+
}
|
|
1016
|
+
]
|
|
590
1017
|
}
|
|
591
1018
|
}
|
|
592
1019
|
},
|
|
@@ -594,7 +1021,7 @@ var SERVICE_REGISTRY = {
|
|
|
594
1021
|
label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
|
|
595
1022
|
endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar lists/events/freebusy, Drive files/uploads",
|
|
596
1023
|
async load() {
|
|
597
|
-
const mod = await import("./dist-
|
|
1024
|
+
const mod = await import("./dist-ZY5SZSJ2.js");
|
|
598
1025
|
return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
|
|
599
1026
|
},
|
|
600
1027
|
defaultFallback(cfg) {
|
|
@@ -603,59 +1030,153 @@ var SERVICE_REGISTRY = {
|
|
|
603
1030
|
},
|
|
604
1031
|
initConfig: {
|
|
605
1032
|
google: {
|
|
606
|
-
users: [
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1033
|
+
users: [
|
|
1034
|
+
{
|
|
1035
|
+
email: "testuser@example.com",
|
|
1036
|
+
name: "Test User",
|
|
1037
|
+
picture: "https://lh3.googleusercontent.com/a/default-user",
|
|
1038
|
+
email_verified: true
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
1041
|
+
oauth_clients: [
|
|
1042
|
+
{
|
|
1043
|
+
client_id: "example-client-id.apps.googleusercontent.com",
|
|
1044
|
+
client_secret: "GOCSPX-example_secret",
|
|
1045
|
+
name: "Code App (Google)",
|
|
1046
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
|
|
1047
|
+
}
|
|
1048
|
+
],
|
|
1049
|
+
labels: [
|
|
1050
|
+
{
|
|
1051
|
+
id: "Label_ops",
|
|
1052
|
+
user_email: "testuser@example.com",
|
|
1053
|
+
name: "Ops/Review",
|
|
1054
|
+
color_background: "#DDEEFF",
|
|
1055
|
+
color_text: "#111111"
|
|
1056
|
+
}
|
|
1057
|
+
],
|
|
1058
|
+
messages: [
|
|
1059
|
+
{
|
|
1060
|
+
id: "msg_welcome",
|
|
1061
|
+
user_email: "testuser@example.com",
|
|
1062
|
+
from: "welcome@example.com",
|
|
1063
|
+
to: "testuser@example.com",
|
|
1064
|
+
subject: "Welcome to the Gmail emulator",
|
|
1065
|
+
body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
|
|
1066
|
+
label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
|
|
1067
|
+
date: "2025-01-04T10:00:00.000Z"
|
|
1068
|
+
}
|
|
1069
|
+
],
|
|
1070
|
+
calendars: [
|
|
1071
|
+
{
|
|
1072
|
+
id: "primary",
|
|
1073
|
+
user_email: "testuser@example.com",
|
|
1074
|
+
summary: "testuser@example.com",
|
|
1075
|
+
primary: true,
|
|
1076
|
+
selected: true,
|
|
1077
|
+
time_zone: "UTC"
|
|
1078
|
+
}
|
|
1079
|
+
],
|
|
1080
|
+
calendar_events: [
|
|
1081
|
+
{
|
|
1082
|
+
id: "evt_kickoff",
|
|
1083
|
+
user_email: "testuser@example.com",
|
|
1084
|
+
calendar_id: "primary",
|
|
1085
|
+
summary: "Project Kickoff",
|
|
1086
|
+
start_date_time: "2025-01-10T09:00:00.000Z",
|
|
1087
|
+
end_date_time: "2025-01-10T09:30:00.000Z"
|
|
1088
|
+
}
|
|
1089
|
+
],
|
|
1090
|
+
drive_items: [
|
|
1091
|
+
{
|
|
1092
|
+
id: "drv_docs",
|
|
1093
|
+
user_email: "testuser@example.com",
|
|
1094
|
+
name: "Docs",
|
|
1095
|
+
mime_type: "application/vnd.google-apps.folder",
|
|
1096
|
+
parent_ids: ["root"]
|
|
1097
|
+
}
|
|
1098
|
+
]
|
|
634
1099
|
}
|
|
635
1100
|
}
|
|
636
1101
|
},
|
|
637
1102
|
slack: {
|
|
638
1103
|
label: "Slack API emulator",
|
|
639
|
-
endpoints: "auth, chat, conversations, users, reactions, team, OAuth, incoming webhooks",
|
|
1104
|
+
endpoints: "auth, chat, conversations, users, profiles, presence, files, pins, bookmarks, views, reactions, team, OAuth, incoming webhooks, inspector",
|
|
640
1105
|
async load() {
|
|
641
|
-
const mod = await import("./dist-
|
|
1106
|
+
const mod = await import("./dist-OGSAVJ25.js");
|
|
642
1107
|
return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
|
|
643
1108
|
},
|
|
644
1109
|
defaultFallback() {
|
|
645
|
-
return {
|
|
1110
|
+
return {
|
|
1111
|
+
login: "U000000001",
|
|
1112
|
+
id: 1,
|
|
1113
|
+
scopes: []
|
|
1114
|
+
};
|
|
646
1115
|
},
|
|
647
1116
|
initConfig: {
|
|
648
1117
|
slack: {
|
|
649
1118
|
team: { name: "My Workspace", domain: "my-workspace" },
|
|
650
|
-
users: [
|
|
651
|
-
|
|
1119
|
+
users: [
|
|
1120
|
+
{
|
|
1121
|
+
name: "developer",
|
|
1122
|
+
real_name: "Developer",
|
|
1123
|
+
email: "dev@example.com",
|
|
1124
|
+
profile: {
|
|
1125
|
+
title: "Local Developer",
|
|
1126
|
+
status_text: "Testing locally",
|
|
1127
|
+
status_emoji: ":computer:"
|
|
1128
|
+
},
|
|
1129
|
+
presence: "active"
|
|
1130
|
+
}
|
|
1131
|
+
],
|
|
1132
|
+
channels: [
|
|
1133
|
+
{ name: "general", topic: "General discussion" },
|
|
1134
|
+
{ name: "random", topic: "Random stuff" }
|
|
1135
|
+
],
|
|
652
1136
|
bots: [{ name: "my-bot" }],
|
|
653
|
-
oauth_apps: [
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1137
|
+
oauth_apps: [
|
|
1138
|
+
{
|
|
1139
|
+
client_id: "12345.67890",
|
|
1140
|
+
client_secret: "example_client_secret",
|
|
1141
|
+
app_id: "A000000001",
|
|
1142
|
+
name: "My Slack App",
|
|
1143
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/slack"],
|
|
1144
|
+
scopes: [
|
|
1145
|
+
"chat:write",
|
|
1146
|
+
"channels:read",
|
|
1147
|
+
"channels:history",
|
|
1148
|
+
"channels:join",
|
|
1149
|
+
"channels:manage",
|
|
1150
|
+
"channels:write",
|
|
1151
|
+
"groups:read",
|
|
1152
|
+
"groups:history",
|
|
1153
|
+
"groups:write",
|
|
1154
|
+
"im:read",
|
|
1155
|
+
"im:history",
|
|
1156
|
+
"im:write",
|
|
1157
|
+
"mpim:read",
|
|
1158
|
+
"mpim:history",
|
|
1159
|
+
"mpim:write",
|
|
1160
|
+
"users:read",
|
|
1161
|
+
"users:read.email",
|
|
1162
|
+
"users.profile:read",
|
|
1163
|
+
"users.profile:write",
|
|
1164
|
+
"users:write",
|
|
1165
|
+
"files:read",
|
|
1166
|
+
"files:write",
|
|
1167
|
+
"pins:read",
|
|
1168
|
+
"pins:write",
|
|
1169
|
+
"bookmarks:read",
|
|
1170
|
+
"bookmarks:write",
|
|
1171
|
+
"reactions:read",
|
|
1172
|
+
"reactions:write",
|
|
1173
|
+
"team:read"
|
|
1174
|
+
],
|
|
1175
|
+
user_scopes: ["users:read", "users.profile:read"],
|
|
1176
|
+
bot_name: "my-bot"
|
|
1177
|
+
}
|
|
1178
|
+
],
|
|
1179
|
+
strict_scopes: false
|
|
659
1180
|
}
|
|
660
1181
|
}
|
|
661
1182
|
},
|
|
@@ -663,7 +1184,7 @@ var SERVICE_REGISTRY = {
|
|
|
663
1184
|
label: "Apple Sign In / OAuth emulator",
|
|
664
1185
|
endpoints: "OAuth authorize, token exchange, JWKS",
|
|
665
1186
|
async load() {
|
|
666
|
-
const mod = await import("./dist-
|
|
1187
|
+
const mod = await import("./dist-WACHAAVU.js");
|
|
667
1188
|
return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
|
|
668
1189
|
},
|
|
669
1190
|
defaultFallback(cfg) {
|
|
@@ -673,12 +1194,14 @@ var SERVICE_REGISTRY = {
|
|
|
673
1194
|
initConfig: {
|
|
674
1195
|
apple: {
|
|
675
1196
|
users: [{ email: "testuser@icloud.com", name: "Test User" }],
|
|
676
|
-
oauth_clients: [
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1197
|
+
oauth_clients: [
|
|
1198
|
+
{
|
|
1199
|
+
client_id: "com.example.app",
|
|
1200
|
+
team_id: "TEAM001",
|
|
1201
|
+
name: "My Apple App",
|
|
1202
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
|
|
1203
|
+
}
|
|
1204
|
+
]
|
|
682
1205
|
}
|
|
683
1206
|
}
|
|
684
1207
|
},
|
|
@@ -686,7 +1209,7 @@ var SERVICE_REGISTRY = {
|
|
|
686
1209
|
label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
|
|
687
1210
|
endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
|
|
688
1211
|
async load() {
|
|
689
|
-
const mod = await import("./dist-
|
|
1212
|
+
const mod = await import("./dist-IFULY5LE.js");
|
|
690
1213
|
return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
|
|
691
1214
|
},
|
|
692
1215
|
defaultFallback(cfg) {
|
|
@@ -696,12 +1219,14 @@ var SERVICE_REGISTRY = {
|
|
|
696
1219
|
initConfig: {
|
|
697
1220
|
microsoft: {
|
|
698
1221
|
users: [{ email: "testuser@outlook.com", name: "Test User" }],
|
|
699
|
-
oauth_clients: [
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1222
|
+
oauth_clients: [
|
|
1223
|
+
{
|
|
1224
|
+
client_id: "example-client-id",
|
|
1225
|
+
client_secret: "example-client-secret",
|
|
1226
|
+
name: "My Microsoft App",
|
|
1227
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
|
|
1228
|
+
}
|
|
1229
|
+
]
|
|
705
1230
|
}
|
|
706
1231
|
}
|
|
707
1232
|
},
|
|
@@ -709,7 +1234,7 @@ var SERVICE_REGISTRY = {
|
|
|
709
1234
|
label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
|
|
710
1235
|
endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
|
|
711
1236
|
async load() {
|
|
712
|
-
const mod = await import("./dist-
|
|
1237
|
+
const mod = await import("./dist-XWWZVLQQ.js");
|
|
713
1238
|
return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
|
|
714
1239
|
},
|
|
715
1240
|
defaultFallback(cfg) {
|
|
@@ -721,13 +1246,15 @@ var SERVICE_REGISTRY = {
|
|
|
721
1246
|
users: [{ login: "testuser@okta.local", email: "testuser@okta.local", first_name: "Test", last_name: "User" }],
|
|
722
1247
|
groups: [{ name: "Everyone", description: "All users", type: "BUILT_IN", okta_id: "00g_everyone" }],
|
|
723
1248
|
authorization_servers: [{ id: "default", name: "default", audiences: ["api://default"] }],
|
|
724
|
-
oauth_clients: [
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1249
|
+
oauth_clients: [
|
|
1250
|
+
{
|
|
1251
|
+
client_id: "okta-test-client",
|
|
1252
|
+
client_secret: "okta-test-secret",
|
|
1253
|
+
name: "Sample OIDC Client",
|
|
1254
|
+
redirect_uris: ["http://localhost:3000/callback"],
|
|
1255
|
+
auth_server_id: "default"
|
|
1256
|
+
}
|
|
1257
|
+
]
|
|
731
1258
|
}
|
|
732
1259
|
}
|
|
733
1260
|
},
|
|
@@ -735,7 +1262,7 @@ var SERVICE_REGISTRY = {
|
|
|
735
1262
|
label: "AWS cloud service emulator",
|
|
736
1263
|
endpoints: "S3 (buckets, objects), SQS (queues, messages), IAM (users, roles, access keys), STS (assume role, caller identity)",
|
|
737
1264
|
async load() {
|
|
738
|
-
const mod = await import("./dist-
|
|
1265
|
+
const mod = await import("./dist-DSJSF3GY.js");
|
|
739
1266
|
return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
|
|
740
1267
|
},
|
|
741
1268
|
defaultFallback() {
|
|
@@ -757,7 +1284,7 @@ var SERVICE_REGISTRY = {
|
|
|
757
1284
|
label: "Resend email API emulator",
|
|
758
1285
|
endpoints: "emails, domains, contacts, API keys, inbox UI",
|
|
759
1286
|
async load() {
|
|
760
|
-
const mod = await import("./dist-
|
|
1287
|
+
const mod = await import("./dist-R3TNKUIE.js");
|
|
761
1288
|
return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
|
|
762
1289
|
},
|
|
763
1290
|
defaultFallback() {
|
|
@@ -772,9 +1299,9 @@ var SERVICE_REGISTRY = {
|
|
|
772
1299
|
},
|
|
773
1300
|
stripe: {
|
|
774
1301
|
label: "Stripe payments emulator",
|
|
775
|
-
endpoints: "customers, payment intents, charges, products, prices, checkout sessions, webhooks",
|
|
1302
|
+
endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
|
|
776
1303
|
async load() {
|
|
777
|
-
const mod = await import("./dist-
|
|
1304
|
+
const mod = await import("./dist-NJJLJT2N.js");
|
|
778
1305
|
return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
|
|
779
1306
|
},
|
|
780
1307
|
defaultFallback() {
|
|
@@ -792,7 +1319,7 @@ var SERVICE_REGISTRY = {
|
|
|
792
1319
|
label: "MongoDB Atlas service emulator",
|
|
793
1320
|
endpoints: "Atlas Admin API v2 (projects, clusters, database users, databases, collections), Atlas Data API v1 (findOne, find, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate)",
|
|
794
1321
|
async load() {
|
|
795
|
-
const mod = await import("./dist-
|
|
1322
|
+
const mod = await import("./dist-2ZZGNPJI.js");
|
|
796
1323
|
return { plugin: mod.mongoatlasPlugin, seedFromConfig: mod.seedFromConfig };
|
|
797
1324
|
},
|
|
798
1325
|
defaultFallback() {
|
|
@@ -806,15 +1333,54 @@ var SERVICE_REGISTRY = {
|
|
|
806
1333
|
databases: [{ cluster: "Cluster0", name: "test", collections: ["items"] }]
|
|
807
1334
|
}
|
|
808
1335
|
}
|
|
1336
|
+
},
|
|
1337
|
+
clerk: {
|
|
1338
|
+
label: "Clerk authentication and user management emulator",
|
|
1339
|
+
endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
|
|
1340
|
+
async load() {
|
|
1341
|
+
const mod = await import("./dist-IRUBHCZU.js");
|
|
1342
|
+
return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
|
|
1343
|
+
},
|
|
1344
|
+
defaultFallback(cfg) {
|
|
1345
|
+
const firstEmail = cfg?.users?.[0]?.email_addresses?.[0] ?? "test@example.com";
|
|
1346
|
+
return { login: firstEmail, id: 1, scopes: [] };
|
|
1347
|
+
},
|
|
1348
|
+
initConfig: {
|
|
1349
|
+
clerk: {
|
|
1350
|
+
users: [
|
|
1351
|
+
{
|
|
1352
|
+
first_name: "Test",
|
|
1353
|
+
last_name: "User",
|
|
1354
|
+
email_addresses: ["test@example.com"],
|
|
1355
|
+
password: "clerk_test_password"
|
|
1356
|
+
}
|
|
1357
|
+
],
|
|
1358
|
+
organizations: [
|
|
1359
|
+
{
|
|
1360
|
+
name: "My Company",
|
|
1361
|
+
slug: "my-company",
|
|
1362
|
+
members: [{ email: "test@example.com", role: "admin" }]
|
|
1363
|
+
}
|
|
1364
|
+
],
|
|
1365
|
+
oauth_applications: [
|
|
1366
|
+
{
|
|
1367
|
+
client_id: "clerk_emulate_client",
|
|
1368
|
+
client_secret: "clerk_emulate_secret",
|
|
1369
|
+
name: "Emulate App",
|
|
1370
|
+
redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
|
|
1371
|
+
}
|
|
1372
|
+
]
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
809
1375
|
}
|
|
810
1376
|
};
|
|
811
1377
|
var DEFAULT_TOKENS = {
|
|
812
1378
|
tokens: {
|
|
813
|
-
|
|
1379
|
+
test_token_admin: {
|
|
814
1380
|
login: "admin",
|
|
815
1381
|
scopes: ["repo", "user", "admin:org", "admin:repo_hook"]
|
|
816
1382
|
},
|
|
817
|
-
|
|
1383
|
+
test_token_user1: {
|
|
818
1384
|
login: "octocat",
|
|
819
1385
|
scopes: ["repo", "user"]
|
|
820
1386
|
}
|
|
@@ -822,12 +1388,110 @@ var DEFAULT_TOKENS = {
|
|
|
822
1388
|
};
|
|
823
1389
|
|
|
824
1390
|
// src/commands/start.ts
|
|
825
|
-
import { serve } from "@hono/node-server";
|
|
826
1391
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
827
1392
|
import { resolve } from "path";
|
|
828
1393
|
import { parse as parseYaml } from "yaml";
|
|
829
1394
|
import pc from "picocolors";
|
|
830
|
-
|
|
1395
|
+
|
|
1396
|
+
// src/portless.ts
|
|
1397
|
+
import { execSync, spawnSync } from "child_process";
|
|
1398
|
+
import { createInterface } from "readline";
|
|
1399
|
+
function isInteractive() {
|
|
1400
|
+
return Boolean(process.stdin.isTTY) && !process.env.CI;
|
|
1401
|
+
}
|
|
1402
|
+
function hasPortless() {
|
|
1403
|
+
const result = spawnSync("portless", ["--version"], { stdio: "ignore" });
|
|
1404
|
+
return result.status === 0;
|
|
1405
|
+
}
|
|
1406
|
+
function promptYesNo(question) {
|
|
1407
|
+
return new Promise((resolve3) => {
|
|
1408
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1409
|
+
rl.question(question, (answer) => {
|
|
1410
|
+
rl.close();
|
|
1411
|
+
const normalized = answer.trim().toLowerCase();
|
|
1412
|
+
resolve3(normalized === "" || normalized === "y" || normalized === "yes");
|
|
1413
|
+
});
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
function isProxyRunning() {
|
|
1417
|
+
const result = spawnSync("portless", ["list"], { stdio: "ignore" });
|
|
1418
|
+
return result.status === 0;
|
|
1419
|
+
}
|
|
1420
|
+
async function ensurePortless() {
|
|
1421
|
+
if (!hasPortless()) {
|
|
1422
|
+
if (!isInteractive()) {
|
|
1423
|
+
console.error("portless is required but not installed. Run: npm i -g portless");
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
const yes = await promptYesNo("portless is not installed. Install it now? (npm i -g portless) [Y/n] ");
|
|
1427
|
+
if (!yes) {
|
|
1428
|
+
console.error("Cannot continue without portless.");
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
try {
|
|
1432
|
+
execSync("npm i -g portless", { stdio: "inherit" });
|
|
1433
|
+
} catch {
|
|
1434
|
+
console.error("Failed to install portless.");
|
|
1435
|
+
process.exit(1);
|
|
1436
|
+
}
|
|
1437
|
+
if (!hasPortless()) {
|
|
1438
|
+
console.error("portless was installed but could not be found on PATH.");
|
|
1439
|
+
process.exit(1);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (!isProxyRunning()) {
|
|
1443
|
+
console.error("portless proxy is not running. Start it with: portless proxy start");
|
|
1444
|
+
process.exit(1);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function registerAliases(aliases) {
|
|
1448
|
+
const registered = [];
|
|
1449
|
+
for (const { name, port } of aliases) {
|
|
1450
|
+
const result = spawnSync("portless", ["alias", name, String(port), "--force"], {
|
|
1451
|
+
stdio: "inherit"
|
|
1452
|
+
});
|
|
1453
|
+
if (result.status !== 0) {
|
|
1454
|
+
if (registered.length > 0) {
|
|
1455
|
+
removeAliases(registered);
|
|
1456
|
+
}
|
|
1457
|
+
throw new Error(`Failed to register portless alias: ${name} -> ${port}`);
|
|
1458
|
+
}
|
|
1459
|
+
registered.push({ name, port });
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
function removeAliases(aliases) {
|
|
1463
|
+
for (const { name } of aliases) {
|
|
1464
|
+
const result = spawnSync("portless", ["alias", "--remove", name], { stdio: "ignore" });
|
|
1465
|
+
if (result.status !== 0) {
|
|
1466
|
+
console.error(`Warning: failed to remove portless alias: ${name}`);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function portlessBaseUrl(serviceName) {
|
|
1471
|
+
return `https://${serviceName}.emulate.localhost`;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/base-url.ts
|
|
1475
|
+
function resolveBaseUrl(opts) {
|
|
1476
|
+
if (opts.seedBaseUrl) {
|
|
1477
|
+
return opts.seedBaseUrl.replace(/\{service\}/g, opts.service);
|
|
1478
|
+
}
|
|
1479
|
+
if (opts.baseUrl) {
|
|
1480
|
+
return opts.baseUrl.replace(/\{service\}/g, opts.service);
|
|
1481
|
+
}
|
|
1482
|
+
const envBaseUrl = process.env.EMULATE_BASE_URL;
|
|
1483
|
+
if (envBaseUrl) {
|
|
1484
|
+
return envBaseUrl.replace(/\{service\}/g, opts.service);
|
|
1485
|
+
}
|
|
1486
|
+
const portlessUrl = process.env.PORTLESS_URL;
|
|
1487
|
+
if (portlessUrl) {
|
|
1488
|
+
return portlessUrl.replace(/\{service\}/g, opts.service);
|
|
1489
|
+
}
|
|
1490
|
+
return `http://localhost:${opts.port}`;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/commands/start.ts
|
|
1494
|
+
var pkg = { version: "0.6.0" };
|
|
831
1495
|
function loadSeedConfig(seedPath) {
|
|
832
1496
|
if (seedPath) {
|
|
833
1497
|
const fullPath = resolve(seedPath);
|
|
@@ -869,10 +1533,14 @@ function loadSeedConfig(seedPath) {
|
|
|
869
1533
|
}
|
|
870
1534
|
function inferServicesFromConfig(config) {
|
|
871
1535
|
const found = SERVICE_NAMES.filter((k) => k in config);
|
|
872
|
-
return found.length > 0 ? found : null;
|
|
1536
|
+
return found.length > 0 ? [...found] : null;
|
|
873
1537
|
}
|
|
874
1538
|
async function startCommand(options) {
|
|
875
1539
|
const { port: basePort } = options;
|
|
1540
|
+
if (options.portless && options.baseUrl) {
|
|
1541
|
+
console.error("--portless and --base-url are mutually exclusive.");
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
876
1544
|
const loaded = loadSeedConfig(options.seed);
|
|
877
1545
|
const seedConfig = loaded?.config ?? null;
|
|
878
1546
|
const configSource = loaded?.source ?? null;
|
|
@@ -880,9 +1548,9 @@ async function startCommand(options) {
|
|
|
880
1548
|
if (options.service) {
|
|
881
1549
|
services = options.service.split(",").map((s) => s.trim());
|
|
882
1550
|
} else if (seedConfig) {
|
|
883
|
-
services = inferServicesFromConfig(seedConfig) ?? SERVICE_NAMES;
|
|
1551
|
+
services = inferServicesFromConfig(seedConfig) ?? [...SERVICE_NAMES];
|
|
884
1552
|
} else {
|
|
885
|
-
services = SERVICE_NAMES;
|
|
1553
|
+
services = [...SERVICE_NAMES];
|
|
886
1554
|
}
|
|
887
1555
|
for (const svc of services) {
|
|
888
1556
|
if (!SERVICE_REGISTRY[svc]) {
|
|
@@ -899,26 +1567,48 @@ async function startCommand(options) {
|
|
|
899
1567
|
} else {
|
|
900
1568
|
tokens["test_token_admin"] = { login: "admin", id: 2, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
|
|
901
1569
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1570
|
+
if (options.portless) {
|
|
1571
|
+
await ensurePortless();
|
|
1572
|
+
}
|
|
1573
|
+
const portlessAliases = [];
|
|
1574
|
+
const prepared = [];
|
|
905
1575
|
for (let i = 0; i < services.length; i++) {
|
|
906
1576
|
const svc = services[i];
|
|
907
1577
|
const entry = SERVICE_REGISTRY[svc];
|
|
908
1578
|
const loadedSvc = await entry.load();
|
|
909
1579
|
const svcSeedConfig = seedConfig?.[svc];
|
|
910
1580
|
const port = svcSeedConfig?.port ?? basePort + i;
|
|
911
|
-
|
|
1581
|
+
if (options.portless) {
|
|
1582
|
+
portlessAliases.push({ name: `${svc}.emulate`, port });
|
|
1583
|
+
}
|
|
1584
|
+
const seedBaseUrl = typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : void 0;
|
|
1585
|
+
const effectiveBaseUrl = options.portless ? portlessBaseUrl(svc) : options.baseUrl;
|
|
1586
|
+
const baseUrl = resolveBaseUrl({ service: svc, port, baseUrl: effectiveBaseUrl, seedBaseUrl });
|
|
1587
|
+
prepared.push({ svc, entry, loadedSvc, svcSeedConfig, port, baseUrl });
|
|
1588
|
+
}
|
|
1589
|
+
if (portlessAliases.length > 0) {
|
|
1590
|
+
registerAliases(portlessAliases);
|
|
1591
|
+
}
|
|
1592
|
+
const serviceUrls = [];
|
|
1593
|
+
const stores = [];
|
|
1594
|
+
const httpServers = [];
|
|
1595
|
+
for (const { svc, entry, loadedSvc, svcSeedConfig, port, baseUrl } of prepared) {
|
|
912
1596
|
serviceUrls.push({ name: svc, url: baseUrl });
|
|
913
1597
|
let cachedResolver;
|
|
914
1598
|
const appKeyResolver = loadedSvc.createAppKeyResolver ? (appId) => cachedResolver(appId) : void 0;
|
|
915
1599
|
const fallbackUser = entry.defaultFallback(svcSeedConfig);
|
|
916
|
-
const { app, store } = createServer(loadedSvc.plugin, {
|
|
1600
|
+
const { app, store, webhooks } = createServer(loadedSvc.plugin, {
|
|
1601
|
+
port,
|
|
1602
|
+
baseUrl,
|
|
1603
|
+
tokens,
|
|
1604
|
+
appKeyResolver,
|
|
1605
|
+
fallbackUser
|
|
1606
|
+
});
|
|
917
1607
|
cachedResolver = loadedSvc.createAppKeyResolver?.(store);
|
|
918
1608
|
stores.push(store);
|
|
919
1609
|
loadedSvc.plugin.seed?.(store, baseUrl);
|
|
920
1610
|
if (svcSeedConfig && loadedSvc.seedFromConfig) {
|
|
921
|
-
loadedSvc.seedFromConfig(store, baseUrl, svcSeedConfig);
|
|
1611
|
+
loadedSvc.seedFromConfig(store, baseUrl, svcSeedConfig, webhooks);
|
|
922
1612
|
}
|
|
923
1613
|
const httpServer = serve({ fetch: app.fetch, port });
|
|
924
1614
|
httpServers.push(httpServer);
|
|
@@ -927,6 +1617,9 @@ async function startCommand(options) {
|
|
|
927
1617
|
const shutdown = () => {
|
|
928
1618
|
console.log(`
|
|
929
1619
|
${pc.dim("Shutting down...")}`);
|
|
1620
|
+
if (portlessAliases.length > 0) {
|
|
1621
|
+
removeAliases(portlessAliases);
|
|
1622
|
+
}
|
|
930
1623
|
for (const store of stores) {
|
|
931
1624
|
store.reset();
|
|
932
1625
|
}
|
|
@@ -959,7 +1652,7 @@ function printBanner(services, tokens, configSource) {
|
|
|
959
1652
|
if (configSource) {
|
|
960
1653
|
lines.push(` ${pc.dim("Config:")} ${configSource}`);
|
|
961
1654
|
} else {
|
|
962
|
-
lines.push(` ${pc.dim("Config:")} defaults ${pc.dim("(run")} emulate init ${pc.dim("to customize)")}`);
|
|
1655
|
+
lines.push(` ${pc.dim("Config:")} defaults ${pc.dim("(run")} npx emulate init ${pc.dim("to customize)")}`);
|
|
963
1656
|
}
|
|
964
1657
|
lines.push("");
|
|
965
1658
|
console.log(lines.join("\n"));
|
|
@@ -994,7 +1687,7 @@ function initCommand(options) {
|
|
|
994
1687
|
writeFileSync(fullPath, content, "utf-8");
|
|
995
1688
|
console.log(`Created ${filename}`);
|
|
996
1689
|
console.log(`
|
|
997
|
-
Run 'emulate' to start the emulator.`);
|
|
1690
|
+
Run 'npx emulate' to start the emulator.`);
|
|
998
1691
|
}
|
|
999
1692
|
|
|
1000
1693
|
// src/commands/list.ts
|
|
@@ -1008,11 +1701,11 @@ function listCommand() {
|
|
|
1008
1701
|
}
|
|
1009
1702
|
|
|
1010
1703
|
// src/index.ts
|
|
1011
|
-
var pkg2 = { version: "0.
|
|
1704
|
+
var pkg2 = { version: "0.6.0" };
|
|
1012
1705
|
var defaultPort = process.env.EMULATE_PORT ?? process.env.PORT ?? "4000";
|
|
1013
1706
|
var program = new Command();
|
|
1014
1707
|
program.name("emulate").description("Local drop-in replacement services for CI and no-network sandboxes").version(pkg2.version);
|
|
1015
|
-
program.command("start", { isDefault: true }).description("Start the emulator server").option("-p, --port <port>", "Base port", defaultPort).option("-s, --service <services>", "Comma-separated services to enable").option("--seed <file>", "Path to seed config file").action(async (opts) => {
|
|
1708
|
+
program.command("start", { isDefault: true }).description("Start the emulator server").option("-p, --port <port>", "Base port", defaultPort).option("-s, --service <services>", "Comma-separated services to enable").option("--seed <file>", "Path to seed config file").option("--base-url <url>", "Override advertised base URL (supports {service} template)").option("--portless", "Serve over HTTPS via portless (auto-registers aliases)").action(async (opts) => {
|
|
1016
1709
|
const port = parseInt(opts.port, 10);
|
|
1017
1710
|
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
1018
1711
|
console.error(`Invalid port: ${opts.port}`);
|
|
@@ -1021,7 +1714,9 @@ program.command("start", { isDefault: true }).description("Start the emulator se
|
|
|
1021
1714
|
await startCommand({
|
|
1022
1715
|
port,
|
|
1023
1716
|
service: opts.service,
|
|
1024
|
-
seed: opts.seed
|
|
1717
|
+
seed: opts.seed,
|
|
1718
|
+
baseUrl: opts.baseUrl,
|
|
1719
|
+
portless: opts.portless
|
|
1025
1720
|
});
|
|
1026
1721
|
});
|
|
1027
1722
|
program.command("init").description("Generate a starter config file").option("-s, --service <service>", "Service to generate config for", "all").action((opts) => {
|