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.
Files changed (50) hide show
  1. package/README.md +198 -27
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +675 -103
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunk-WVQMFHQM.js +83 -0
  6. package/dist/chunk-WVQMFHQM.js.map +1 -0
  7. package/dist/{dist-B674PYKV.js → dist-2ZZGNPJI.js} +22 -43
  8. package/dist/dist-2ZZGNPJI.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-CXRPM6BK.js} +211 -48
  10. package/dist/dist-CXRPM6BK.js.map +1 -0
  11. package/dist/{dist-VVXVP5EZ.js → dist-DSJSF3GY.js} +551 -91
  12. package/dist/dist-DSJSF3GY.js.map +1 -0
  13. package/dist/{dist-RMK3BS5M.js → dist-IFULY5LE.js} +196 -33
  14. package/dist/dist-IFULY5LE.js.map +1 -0
  15. package/dist/dist-IRUBHCZU.js +1898 -0
  16. package/dist/dist-IRUBHCZU.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-NJJLJT2N.js} +520 -61
  18. package/dist/dist-NJJLJT2N.js.map +1 -0
  19. package/dist/dist-OGSAVJ25.js +4874 -0
  20. package/dist/dist-OGSAVJ25.js.map +1 -0
  21. package/dist/{dist-H6JYGQM4.js → dist-PO4CL5SJ.js} +271 -158
  22. package/dist/dist-PO4CL5SJ.js.map +1 -0
  23. package/dist/{dist-QMOJM6DV.js → dist-R3TNKUIE.js} +238 -55
  24. package/dist/dist-R3TNKUIE.js.map +1 -0
  25. package/dist/{dist-6JFNJPUU.js → dist-WACHAAVU.js} +171 -22
  26. package/dist/dist-WACHAAVU.js.map +1 -0
  27. package/dist/{dist-OTJZRQ3Q.js → dist-XWWZVLQQ.js} +216 -75
  28. package/dist/dist-XWWZVLQQ.js.map +1 -0
  29. package/dist/{dist-6EW7SSOZ.js → dist-ZY5SZSJ2.js} +397 -223
  30. package/dist/dist-ZY5SZSJ2.js.map +1 -0
  31. package/dist/fonts/favicon.ico +0 -0
  32. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  33. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  34. package/dist/index.js +812 -117
  35. package/dist/index.js.map +1 -1
  36. package/package.json +17 -15
  37. package/dist/chunk-TEPNEZ63.js +0 -2143
  38. package/dist/chunk-TEPNEZ63.js.map +0 -1
  39. package/dist/dist-6EW7SSOZ.js.map +0 -1
  40. package/dist/dist-6JFNJPUU.js.map +0 -1
  41. package/dist/dist-B674PYKV.js.map +0 -1
  42. package/dist/dist-G7WQPZ3Y.js +0 -1287
  43. package/dist/dist-G7WQPZ3Y.js.map +0 -1
  44. package/dist/dist-H6JYGQM4.js.map +0 -1
  45. package/dist/dist-OTJZRQ3Q.js.map +0 -1
  46. package/dist/dist-QMOJM6DV.js.map +0 -1
  47. package/dist/dist-RDFBZ5O6.js.map +0 -1
  48. package/dist/dist-RMK3BS5M.js.map +0 -1
  49. package/dist/dist-VVXVP5EZ.js.map +0 -1
  50. package/dist/dist-YOVM5HEY.js.map +0 -1
package/dist/api.js CHANGED
@@ -2,12 +2,9 @@ import {
2
2
  importPKCS8,
3
3
  jwtVerify
4
4
  } from "./chunk-D6EKRYGP.js";
5
- import {
6
- Hono,
7
- cors
8
- } from "./chunk-TEPNEZ63.js";
9
5
 
10
6
  // ../@emulators/core/dist/index.js
7
+ import { createServer as createNodeServer } from "http";
11
8
  import { createHmac } from "crypto";
12
9
  import { readFileSync } from "fs";
13
10
  import { fileURLToPath } from "url";
@@ -231,6 +228,397 @@ var Store = class {
231
228
  }
232
229
  }
233
230
  };
231
+ var HonoRequest = class {
232
+ constructor(request, params) {
233
+ this.params = params;
234
+ this.raw = request;
235
+ this.url = request.url;
236
+ this.method = request.method;
237
+ this.path = new URL(request.url).pathname;
238
+ }
239
+ raw;
240
+ url;
241
+ method;
242
+ path;
243
+ header(name) {
244
+ if (name) return this.raw.headers.get(name) ?? void 0;
245
+ const headers = {};
246
+ this.raw.headers.forEach((value, key) => {
247
+ headers[key] = value;
248
+ });
249
+ return headers;
250
+ }
251
+ query(name) {
252
+ return new URL(this.url).searchParams.get(name) ?? void 0;
253
+ }
254
+ queries(name) {
255
+ const values = new URL(this.url).searchParams.getAll(name);
256
+ return values.length > 0 ? values : void 0;
257
+ }
258
+ param(name) {
259
+ if (!name) return { ...this.params };
260
+ return this.params[name] ?? "";
261
+ }
262
+ json() {
263
+ return this.raw.json();
264
+ }
265
+ text() {
266
+ return this.raw.text();
267
+ }
268
+ arrayBuffer() {
269
+ return this.raw.arrayBuffer();
270
+ }
271
+ async parseBody() {
272
+ const contentType = this.header("Content-Type") ?? "";
273
+ if (contentType.includes("multipart/form-data")) {
274
+ return formDataToObject(await this.raw.formData());
275
+ }
276
+ if (contentType.includes("application/x-www-form-urlencoded")) {
277
+ const params = new URLSearchParams(await this.raw.text());
278
+ const out = {};
279
+ for (const [key, value] of params) {
280
+ appendBodyValue(out, key, value);
281
+ }
282
+ return out;
283
+ }
284
+ if (contentType.includes("application/json")) {
285
+ const body = await this.raw.json().catch(() => ({}));
286
+ return body && typeof body === "object" && !Array.isArray(body) ? body : {};
287
+ }
288
+ return {};
289
+ }
290
+ };
291
+ var Context = class {
292
+ constructor(request, params, notFoundHandler) {
293
+ this.notFoundHandler = notFoundHandler;
294
+ this.req = new HonoRequest(request, params);
295
+ }
296
+ req;
297
+ vars = /* @__PURE__ */ new Map();
298
+ responseHeaders = new Headers();
299
+ responseStatus = 200;
300
+ get(key) {
301
+ return this.vars.get(key);
302
+ }
303
+ set(key, value) {
304
+ this.vars.set(key, value);
305
+ }
306
+ header(name, value) {
307
+ this.responseHeaders.set(name, value);
308
+ }
309
+ status(status) {
310
+ this.responseStatus = status;
311
+ }
312
+ json(data, status, headers) {
313
+ return this.response(JSON.stringify(data), status, defaultContentType(headers, "application/json; charset=UTF-8"));
314
+ }
315
+ text(text, status, headers) {
316
+ return this.response(text, status, defaultContentType(headers, "text/plain; charset=UTF-8"));
317
+ }
318
+ html(html, status, headers) {
319
+ return this.response(html, status, defaultContentType(headers, "text/html; charset=UTF-8"));
320
+ }
321
+ body(body, status, headers) {
322
+ return this.response(body, status, headers);
323
+ }
324
+ redirect(location, status = 302) {
325
+ return this.response(null, status, { Location: location });
326
+ }
327
+ notFound() {
328
+ return this.notFoundHandler(this);
329
+ }
330
+ finalize(response) {
331
+ if (!hasHeaders(this.responseHeaders)) return response;
332
+ const headers = new Headers(response.headers);
333
+ this.responseHeaders.forEach((value, key) => {
334
+ headers.set(key, value);
335
+ });
336
+ return new Response(response.body, {
337
+ status: response.status,
338
+ statusText: response.statusText,
339
+ headers
340
+ });
341
+ }
342
+ response(body, status, headers) {
343
+ const merged = new Headers(headers);
344
+ this.responseHeaders.forEach((value, key) => {
345
+ merged.set(key, value);
346
+ });
347
+ return new Response(body, {
348
+ status: status ?? this.responseStatus,
349
+ headers: merged
350
+ });
351
+ }
352
+ };
353
+ var Hono = class {
354
+ middleware = [];
355
+ routes = [];
356
+ errorHandler = (err) => {
357
+ const message = err instanceof Error ? err.message : "Internal Server Error";
358
+ return new Response(message, { status: 500 });
359
+ };
360
+ notFoundHandler = () => new Response("404 Not Found", { status: 404 });
361
+ use(pathOrHandler, ...handlers) {
362
+ if (typeof pathOrHandler === "string") {
363
+ this.middleware.push({ method: "ALL", compiled: compilePath(pathOrHandler), handlers });
364
+ } else {
365
+ this.middleware.push({ method: "ALL", compiled: compilePath("*"), handlers: [pathOrHandler, ...handlers] });
366
+ }
367
+ return this;
368
+ }
369
+ on(method, path, ...handlers) {
370
+ this.routes.push({ method: method.toUpperCase(), compiled: compilePath(path), handlers });
371
+ return this;
372
+ }
373
+ get(path, ...handlers) {
374
+ return this.on("GET", path, ...handlers);
375
+ }
376
+ post(path, ...handlers) {
377
+ return this.on("POST", path, ...handlers);
378
+ }
379
+ put(path, ...handlers) {
380
+ return this.on("PUT", path, ...handlers);
381
+ }
382
+ patch(path, ...handlers) {
383
+ return this.on("PATCH", path, ...handlers);
384
+ }
385
+ delete(path, ...handlers) {
386
+ return this.on("DELETE", path, ...handlers);
387
+ }
388
+ onError(handler) {
389
+ this.errorHandler = handler;
390
+ return this;
391
+ }
392
+ notFound(handler) {
393
+ this.notFoundHandler = handler;
394
+ return this;
395
+ }
396
+ async request(input, init) {
397
+ if (input instanceof Request) return this.fetch(input);
398
+ const url = input.startsWith("/") ? `http://localhost${input}` : input;
399
+ return this.fetch(new Request(url, init));
400
+ }
401
+ fetch = async (request) => {
402
+ const url = new URL(request.url);
403
+ const path = url.pathname;
404
+ const method = request.method.toUpperCase();
405
+ const matched = this.match(method, path);
406
+ const context = new Context(request, matched.params, this.notFoundHandler);
407
+ try {
408
+ const response = await this.dispatch(context, matched.handlers);
409
+ return context.finalize(response ?? await this.notFoundHandler(context));
410
+ } catch (err) {
411
+ return context.finalize(await this.errorHandler(err, context));
412
+ }
413
+ };
414
+ match(method, path) {
415
+ const handlers = [];
416
+ const params = {};
417
+ for (const route2 of this.middleware) {
418
+ const match = matchPath(route2.compiled, path);
419
+ if (!match) continue;
420
+ Object.assign(params, match);
421
+ for (const handler of route2.handlers) {
422
+ handlers.push({ handler, params: match });
423
+ }
424
+ }
425
+ 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);
426
+ if (route) {
427
+ const match = matchPath(route.compiled, path) ?? {};
428
+ Object.assign(params, match);
429
+ for (const handler of route.handlers) {
430
+ handlers.push({ handler, params: match });
431
+ }
432
+ }
433
+ return { handlers, params };
434
+ }
435
+ async dispatch(context, handlers) {
436
+ let index = -1;
437
+ const run = async (nextIndex) => {
438
+ if (nextIndex <= index) throw new Error("next() called multiple times");
439
+ index = nextIndex;
440
+ const matched = handlers[nextIndex];
441
+ if (!matched) return void 0;
442
+ const originalParams = context.req.param();
443
+ Object.assign(originalParams, matched.params);
444
+ let nextResponse = void 0;
445
+ let nextCalled = false;
446
+ const next = async () => {
447
+ nextCalled = true;
448
+ nextResponse = await run(nextIndex + 1);
449
+ };
450
+ const response = await matched.handler(context, next);
451
+ if (response instanceof Response) return response;
452
+ if (nextCalled) return nextResponse;
453
+ return response;
454
+ };
455
+ return run(0);
456
+ }
457
+ };
458
+ function cors(options = {}) {
459
+ const origin = options.origin ?? "*";
460
+ const allowMethods = options.allowMethods ?? ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH", "OPTIONS"];
461
+ return async (c, next) => {
462
+ c.header("Access-Control-Allow-Origin", origin);
463
+ if (options.credentials) c.header("Access-Control-Allow-Credentials", "true");
464
+ if (c.req.method.toUpperCase() === "OPTIONS") {
465
+ c.header("Access-Control-Allow-Methods", allowMethods.join(","));
466
+ const allowHeaders = options.allowHeaders?.join(",") ?? c.req.header("Access-Control-Request-Headers");
467
+ if (allowHeaders) c.header("Access-Control-Allow-Headers", allowHeaders);
468
+ if (options.maxAge != null) c.header("Access-Control-Max-Age", String(options.maxAge));
469
+ return c.body(null, 204);
470
+ }
471
+ await next();
472
+ };
473
+ }
474
+ function serve(options) {
475
+ const port = options.port ?? 3e3;
476
+ const server = createNodeServer(async (req, res) => {
477
+ try {
478
+ const request = nodeRequestToFetchRequest(req);
479
+ const response = await options.fetch(request);
480
+ await writeFetchResponse(res, response, req.method?.toUpperCase() === "HEAD");
481
+ } catch (err) {
482
+ const message = err instanceof Error ? err.message : "Internal Server Error";
483
+ res.statusCode = 500;
484
+ res.setHeader("Content-Type", "text/plain; charset=UTF-8");
485
+ res.end(message);
486
+ }
487
+ });
488
+ server.listen(port, options.hostname);
489
+ return server;
490
+ }
491
+ function compilePath(pattern) {
492
+ if (pattern === "*" || pattern === "/*") {
493
+ return { pattern, regex: /^.*$/, paramNames: [] };
494
+ }
495
+ const paramNames = [];
496
+ let source = "^";
497
+ for (let i = 0; i < pattern.length; i++) {
498
+ const char = pattern[i];
499
+ if (char !== ":") {
500
+ source += escapeRegex(char);
501
+ continue;
502
+ }
503
+ let name = "";
504
+ i++;
505
+ while (i < pattern.length && /[A-Za-z0-9_]/.test(pattern[i])) {
506
+ name += pattern[i];
507
+ i++;
508
+ }
509
+ i--;
510
+ paramNames.push(name);
511
+ if (pattern[i + 1] === "{") {
512
+ const close = pattern.indexOf("}", i + 2);
513
+ if (close < 0) throw new Error(`Invalid route pattern: ${pattern}`);
514
+ const expr = pattern.slice(i + 2, close);
515
+ source += `(${expr})`;
516
+ i = close;
517
+ } else {
518
+ source += "([^/]+)";
519
+ }
520
+ }
521
+ source += "$";
522
+ return { pattern, regex: new RegExp(source), paramNames };
523
+ }
524
+ function matchPath(compiled, path) {
525
+ const match = compiled.regex.exec(path);
526
+ if (!match) return null;
527
+ const params = {};
528
+ for (let i = 0; i < compiled.paramNames.length; i++) {
529
+ params[compiled.paramNames[i]] = decodePathParam(match[i + 1] ?? "");
530
+ }
531
+ return params;
532
+ }
533
+ function decodePathParam(value) {
534
+ try {
535
+ return decodeURIComponent(value);
536
+ } catch {
537
+ return value;
538
+ }
539
+ }
540
+ function escapeRegex(value) {
541
+ return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
542
+ }
543
+ function hasHeaders(headers) {
544
+ for (const _ of headers) return true;
545
+ return false;
546
+ }
547
+ function defaultContentType(headers, contentType) {
548
+ const out = new Headers(headers);
549
+ if (!out.has("Content-Type")) {
550
+ out.set("Content-Type", contentType);
551
+ }
552
+ return out;
553
+ }
554
+ function formDataToObject(formData) {
555
+ const out = {};
556
+ for (const [key, value] of formData) {
557
+ appendBodyValue(out, key, value);
558
+ }
559
+ return out;
560
+ }
561
+ function appendBodyValue(target, key, value) {
562
+ const existing = target[key];
563
+ if (existing === void 0) {
564
+ target[key] = value;
565
+ } else if (Array.isArray(existing)) {
566
+ existing.push(value);
567
+ } else {
568
+ target[key] = [existing, value];
569
+ }
570
+ }
571
+ function nodeRequestToFetchRequest(req) {
572
+ const host = req.headers.host ?? "localhost";
573
+ const url = new URL(req.url ?? "/", `http://${host}`);
574
+ const headers = new Headers();
575
+ for (const [key, value] of Object.entries(req.headers)) {
576
+ if (value == null) continue;
577
+ if (Array.isArray(value)) {
578
+ for (const item of value) headers.append(key, item);
579
+ } else {
580
+ headers.set(key, value);
581
+ }
582
+ }
583
+ const method = req.method ?? "GET";
584
+ const hasBody = method !== "GET" && method !== "HEAD";
585
+ return new Request(url.toString(), {
586
+ method,
587
+ headers,
588
+ body: hasBody ? req : void 0,
589
+ duplex: "half"
590
+ });
591
+ }
592
+ async function writeFetchResponse(res, response, headOnly) {
593
+ res.statusCode = response.status;
594
+ res.statusMessage = response.statusText;
595
+ const headersWithCookies = response.headers;
596
+ const cookies = headersWithCookies.getSetCookie?.();
597
+ response.headers.forEach((value, key) => {
598
+ if (key.toLowerCase() === "set-cookie" && cookies && cookies.length > 0) return;
599
+ res.setHeader(key, value);
600
+ });
601
+ if (cookies && cookies.length > 0) {
602
+ res.setHeader("Set-Cookie", cookies);
603
+ }
604
+ if (headOnly || !response.body) {
605
+ res.end();
606
+ return;
607
+ }
608
+ const reader = response.body.getReader();
609
+ try {
610
+ while (true) {
611
+ const { done, value } = await reader.read();
612
+ if (done) break;
613
+ if (!res.write(value)) {
614
+ await new Promise((resolve) => res.once("drain", resolve));
615
+ }
616
+ }
617
+ res.end();
618
+ } catch (err) {
619
+ res.destroy(err instanceof Error ? err : void 0);
620
+ }
621
+ }
234
622
  var MAX_DELIVERIES = 1e3;
235
623
  var WebhookDispatcher = class {
236
624
  subscriptions = [];
@@ -387,9 +775,7 @@ function authMiddleware(tokens, appKeyResolver, fallbackUser) {
387
775
  if (token.startsWith("eyJ") && appKeyResolver) {
388
776
  try {
389
777
  const [, payloadB64] = token.split(".");
390
- const payload = JSON.parse(
391
- Buffer.from(payloadB64, "base64url").toString()
392
- );
778
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
393
779
  const appId = typeof payload.iss === "string" ? parseInt(payload.iss, 10) : payload.iss;
394
780
  if (typeof appId === "number" && !isNaN(appId)) {
395
781
  const appInfo = appKeyResolver(appId);
@@ -426,6 +812,7 @@ var FONTS = {
426
812
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
427
813
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
428
814
  };
815
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
429
816
  function registerFontRoutes(app) {
430
817
  app.get("/_emulate/fonts/:name", (c) => {
431
818
  const name = c.req.param("name");
@@ -439,6 +826,14 @@ function registerFontRoutes(app) {
439
826
  }
440
827
  });
441
828
  });
829
+ app.get("/_emulate/favicon.ico", (c) => {
830
+ return new Response(FAVICON, {
831
+ headers: {
832
+ "Content-Type": "image/x-icon",
833
+ "Cache-Control": "public, max-age=31536000, immutable"
834
+ }
835
+ });
836
+ });
442
837
  }
443
838
  function createServer(plugin, options = {}) {
444
839
  const port = options.port ?? 4e3;
@@ -513,7 +908,7 @@ var SERVICE_REGISTRY = {
513
908
  label: "Vercel REST API emulator",
514
909
  endpoints: "projects, deployments, domains, env vars, users, teams, file uploads, protection bypass",
515
910
  async load() {
516
- const mod = await import("./dist-RDFBZ5O6.js");
911
+ const mod = await import("./dist-CXRPM6BK.js");
517
912
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
518
913
  },
519
914
  defaultFallback(cfg) {
@@ -525,12 +920,14 @@ var SERVICE_REGISTRY = {
525
920
  users: [{ username: "developer", name: "Developer", email: "dev@example.com" }],
526
921
  teams: [{ slug: "my-team", name: "My Team" }],
527
922
  projects: [{ name: "my-app", team: "my-team", framework: "nextjs" }],
528
- integrations: [{
529
- client_id: "oac_example_client_id",
530
- client_secret: "example_client_secret",
531
- name: "My Vercel App",
532
- redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
533
- }]
923
+ integrations: [
924
+ {
925
+ client_id: "oac_example_client_id",
926
+ client_secret: "example_client_secret",
927
+ name: "My Vercel App",
928
+ redirect_uris: ["http://localhost:3000/api/auth/callback/vercel"]
929
+ }
930
+ ]
534
931
  }
535
932
  }
536
933
  },
@@ -538,7 +935,7 @@ var SERVICE_REGISTRY = {
538
935
  label: "GitHub REST API emulator",
539
936
  endpoints: "users, repos, issues, PRs, comments, reviews, labels, milestones, branches, git data, orgs, teams, releases, webhooks, search, actions, checks, rate limit",
540
937
  async load() {
541
- const mod = await import("./dist-H6JYGQM4.js");
938
+ const mod = await import("./dist-PO4CL5SJ.js");
542
939
  return {
543
940
  plugin: mod.githubPlugin,
544
941
  seedFromConfig: mod.seedFromConfig,
@@ -562,25 +959,42 @@ var SERVICE_REGISTRY = {
562
959
  },
563
960
  initConfig: {
564
961
  github: {
565
- users: [{
566
- login: "octocat",
567
- name: "The Octocat",
568
- email: "octocat@github.com",
569
- bio: "I am the Octocat",
570
- company: "GitHub",
571
- location: "San Francisco"
572
- }],
962
+ users: [
963
+ {
964
+ login: "octocat",
965
+ name: "The Octocat",
966
+ email: "octocat@github.com",
967
+ bio: "I am the Octocat",
968
+ company: "GitHub",
969
+ location: "San Francisco"
970
+ }
971
+ ],
573
972
  orgs: [{ login: "my-org", name: "My Organization", description: "A test organization" }],
574
973
  repos: [
575
- { owner: "octocat", name: "hello-world", description: "My first repository", language: "JavaScript", topics: ["hello", "world"], auto_init: true },
576
- { owner: "my-org", name: "org-repo", description: "An organization repository", language: "TypeScript", auto_init: true }
974
+ {
975
+ owner: "octocat",
976
+ name: "hello-world",
977
+ description: "My first repository",
978
+ language: "JavaScript",
979
+ topics: ["hello", "world"],
980
+ auto_init: true
981
+ },
982
+ {
983
+ owner: "my-org",
984
+ name: "org-repo",
985
+ description: "An organization repository",
986
+ language: "TypeScript",
987
+ auto_init: true
988
+ }
577
989
  ],
578
- oauth_apps: [{
579
- client_id: "Iv1.example_client_id",
580
- client_secret: "example_client_secret",
581
- name: "My App",
582
- redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
583
- }]
990
+ oauth_apps: [
991
+ {
992
+ client_id: "Iv1.example_client_id",
993
+ client_secret: "example_client_secret",
994
+ name: "My App",
995
+ redirect_uris: ["http://localhost:3000/api/auth/callback/github"]
996
+ }
997
+ ]
584
998
  }
585
999
  }
586
1000
  },
@@ -588,7 +1002,7 @@ var SERVICE_REGISTRY = {
588
1002
  label: "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator",
589
1003
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, token revocation, Gmail messages/drafts/threads/labels/history/settings, Calendar lists/events/freebusy, Drive files/uploads",
590
1004
  async load() {
591
- const mod = await import("./dist-6EW7SSOZ.js");
1005
+ const mod = await import("./dist-ZY5SZSJ2.js");
592
1006
  return { plugin: mod.googlePlugin, seedFromConfig: mod.seedFromConfig };
593
1007
  },
594
1008
  defaultFallback(cfg) {
@@ -597,59 +1011,153 @@ var SERVICE_REGISTRY = {
597
1011
  },
598
1012
  initConfig: {
599
1013
  google: {
600
- users: [{ email: "testuser@example.com", name: "Test User", picture: "https://lh3.googleusercontent.com/a/default-user", email_verified: true }],
601
- oauth_clients: [{
602
- client_id: "example-client-id.apps.googleusercontent.com",
603
- client_secret: "GOCSPX-example_secret",
604
- name: "Code App (Google)",
605
- redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
606
- }],
607
- labels: [{ id: "Label_ops", user_email: "testuser@example.com", name: "Ops/Review", color_background: "#DDEEFF", color_text: "#111111" }],
608
- messages: [{
609
- id: "msg_welcome",
610
- user_email: "testuser@example.com",
611
- from: "welcome@example.com",
612
- to: "testuser@example.com",
613
- subject: "Welcome to the Gmail emulator",
614
- body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
615
- label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
616
- date: "2025-01-04T10:00:00.000Z"
617
- }],
618
- calendars: [{ id: "primary", user_email: "testuser@example.com", summary: "testuser@example.com", primary: true, selected: true, time_zone: "UTC" }],
619
- calendar_events: [{
620
- id: "evt_kickoff",
621
- user_email: "testuser@example.com",
622
- calendar_id: "primary",
623
- summary: "Project Kickoff",
624
- start_date_time: "2025-01-10T09:00:00.000Z",
625
- end_date_time: "2025-01-10T09:30:00.000Z"
626
- }],
627
- drive_items: [{ id: "drv_docs", user_email: "testuser@example.com", name: "Docs", mime_type: "application/vnd.google-apps.folder", parent_ids: ["root"] }]
1014
+ users: [
1015
+ {
1016
+ email: "testuser@example.com",
1017
+ name: "Test User",
1018
+ picture: "https://lh3.googleusercontent.com/a/default-user",
1019
+ email_verified: true
1020
+ }
1021
+ ],
1022
+ oauth_clients: [
1023
+ {
1024
+ client_id: "example-client-id.apps.googleusercontent.com",
1025
+ client_secret: "GOCSPX-example_secret",
1026
+ name: "Code App (Google)",
1027
+ redirect_uris: ["http://localhost:3000/api/auth/callback/google"]
1028
+ }
1029
+ ],
1030
+ labels: [
1031
+ {
1032
+ id: "Label_ops",
1033
+ user_email: "testuser@example.com",
1034
+ name: "Ops/Review",
1035
+ color_background: "#DDEEFF",
1036
+ color_text: "#111111"
1037
+ }
1038
+ ],
1039
+ messages: [
1040
+ {
1041
+ id: "msg_welcome",
1042
+ user_email: "testuser@example.com",
1043
+ from: "welcome@example.com",
1044
+ to: "testuser@example.com",
1045
+ subject: "Welcome to the Gmail emulator",
1046
+ body_text: "You can now test Gmail, Calendar, and Drive flows locally.",
1047
+ label_ids: ["INBOX", "UNREAD", "CATEGORY_UPDATES"],
1048
+ date: "2025-01-04T10:00:00.000Z"
1049
+ }
1050
+ ],
1051
+ calendars: [
1052
+ {
1053
+ id: "primary",
1054
+ user_email: "testuser@example.com",
1055
+ summary: "testuser@example.com",
1056
+ primary: true,
1057
+ selected: true,
1058
+ time_zone: "UTC"
1059
+ }
1060
+ ],
1061
+ calendar_events: [
1062
+ {
1063
+ id: "evt_kickoff",
1064
+ user_email: "testuser@example.com",
1065
+ calendar_id: "primary",
1066
+ summary: "Project Kickoff",
1067
+ start_date_time: "2025-01-10T09:00:00.000Z",
1068
+ end_date_time: "2025-01-10T09:30:00.000Z"
1069
+ }
1070
+ ],
1071
+ drive_items: [
1072
+ {
1073
+ id: "drv_docs",
1074
+ user_email: "testuser@example.com",
1075
+ name: "Docs",
1076
+ mime_type: "application/vnd.google-apps.folder",
1077
+ parent_ids: ["root"]
1078
+ }
1079
+ ]
628
1080
  }
629
1081
  }
630
1082
  },
631
1083
  slack: {
632
1084
  label: "Slack API emulator",
633
- endpoints: "auth, chat, conversations, users, reactions, team, OAuth, incoming webhooks",
1085
+ endpoints: "auth, chat, conversations, users, profiles, presence, files, pins, bookmarks, views, reactions, team, OAuth, incoming webhooks, inspector",
634
1086
  async load() {
635
- const mod = await import("./dist-G7WQPZ3Y.js");
1087
+ const mod = await import("./dist-OGSAVJ25.js");
636
1088
  return { plugin: mod.slackPlugin, seedFromConfig: mod.seedFromConfig };
637
1089
  },
638
1090
  defaultFallback() {
639
- return { login: "U000000001", id: 1, scopes: ["chat:write", "channels:read", "users:read", "reactions:write"] };
1091
+ return {
1092
+ login: "U000000001",
1093
+ id: 1,
1094
+ scopes: []
1095
+ };
640
1096
  },
641
1097
  initConfig: {
642
1098
  slack: {
643
1099
  team: { name: "My Workspace", domain: "my-workspace" },
644
- users: [{ name: "developer", real_name: "Developer", email: "dev@example.com" }],
645
- channels: [{ name: "general", topic: "General discussion" }, { name: "random", topic: "Random stuff" }],
1100
+ users: [
1101
+ {
1102
+ name: "developer",
1103
+ real_name: "Developer",
1104
+ email: "dev@example.com",
1105
+ profile: {
1106
+ title: "Local Developer",
1107
+ status_text: "Testing locally",
1108
+ status_emoji: ":computer:"
1109
+ },
1110
+ presence: "active"
1111
+ }
1112
+ ],
1113
+ channels: [
1114
+ { name: "general", topic: "General discussion" },
1115
+ { name: "random", topic: "Random stuff" }
1116
+ ],
646
1117
  bots: [{ name: "my-bot" }],
647
- oauth_apps: [{
648
- client_id: "12345.67890",
649
- client_secret: "example_client_secret",
650
- name: "My Slack App",
651
- redirect_uris: ["http://localhost:3000/api/auth/callback/slack"]
652
- }]
1118
+ oauth_apps: [
1119
+ {
1120
+ client_id: "12345.67890",
1121
+ client_secret: "example_client_secret",
1122
+ app_id: "A000000001",
1123
+ name: "My Slack App",
1124
+ redirect_uris: ["http://localhost:3000/api/auth/callback/slack"],
1125
+ scopes: [
1126
+ "chat:write",
1127
+ "channels:read",
1128
+ "channels:history",
1129
+ "channels:join",
1130
+ "channels:manage",
1131
+ "channels:write",
1132
+ "groups:read",
1133
+ "groups:history",
1134
+ "groups:write",
1135
+ "im:read",
1136
+ "im:history",
1137
+ "im:write",
1138
+ "mpim:read",
1139
+ "mpim:history",
1140
+ "mpim:write",
1141
+ "users:read",
1142
+ "users:read.email",
1143
+ "users.profile:read",
1144
+ "users.profile:write",
1145
+ "users:write",
1146
+ "files:read",
1147
+ "files:write",
1148
+ "pins:read",
1149
+ "pins:write",
1150
+ "bookmarks:read",
1151
+ "bookmarks:write",
1152
+ "reactions:read",
1153
+ "reactions:write",
1154
+ "team:read"
1155
+ ],
1156
+ user_scopes: ["users:read", "users.profile:read"],
1157
+ bot_name: "my-bot"
1158
+ }
1159
+ ],
1160
+ strict_scopes: false
653
1161
  }
654
1162
  }
655
1163
  },
@@ -657,7 +1165,7 @@ var SERVICE_REGISTRY = {
657
1165
  label: "Apple Sign In / OAuth emulator",
658
1166
  endpoints: "OAuth authorize, token exchange, JWKS",
659
1167
  async load() {
660
- const mod = await import("./dist-6JFNJPUU.js");
1168
+ const mod = await import("./dist-WACHAAVU.js");
661
1169
  return { plugin: mod.applePlugin, seedFromConfig: mod.seedFromConfig };
662
1170
  },
663
1171
  defaultFallback(cfg) {
@@ -667,12 +1175,14 @@ var SERVICE_REGISTRY = {
667
1175
  initConfig: {
668
1176
  apple: {
669
1177
  users: [{ email: "testuser@icloud.com", name: "Test User" }],
670
- oauth_clients: [{
671
- client_id: "com.example.app",
672
- team_id: "TEAM001",
673
- name: "My Apple App",
674
- redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
675
- }]
1178
+ oauth_clients: [
1179
+ {
1180
+ client_id: "com.example.app",
1181
+ team_id: "TEAM001",
1182
+ name: "My Apple App",
1183
+ redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]
1184
+ }
1185
+ ]
676
1186
  }
677
1187
  }
678
1188
  },
@@ -680,7 +1190,7 @@ var SERVICE_REGISTRY = {
680
1190
  label: "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator",
681
1191
  endpoints: "OAuth authorize, token exchange, userinfo, OIDC discovery, Graph /me, logout, token revocation",
682
1192
  async load() {
683
- const mod = await import("./dist-RMK3BS5M.js");
1193
+ const mod = await import("./dist-IFULY5LE.js");
684
1194
  return { plugin: mod.microsoftPlugin, seedFromConfig: mod.seedFromConfig };
685
1195
  },
686
1196
  defaultFallback(cfg) {
@@ -690,12 +1200,14 @@ var SERVICE_REGISTRY = {
690
1200
  initConfig: {
691
1201
  microsoft: {
692
1202
  users: [{ email: "testuser@outlook.com", name: "Test User" }],
693
- oauth_clients: [{
694
- client_id: "example-client-id",
695
- client_secret: "example-client-secret",
696
- name: "My Microsoft App",
697
- redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
698
- }]
1203
+ oauth_clients: [
1204
+ {
1205
+ client_id: "example-client-id",
1206
+ client_secret: "example-client-secret",
1207
+ name: "My Microsoft App",
1208
+ redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
1209
+ }
1210
+ ]
699
1211
  }
700
1212
  }
701
1213
  },
@@ -703,7 +1215,7 @@ var SERVICE_REGISTRY = {
703
1215
  label: "Okta OAuth 2.0 / OpenID Connect + management API emulator",
704
1216
  endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo/introspect/revoke/logout, users, groups, apps, authorization servers",
705
1217
  async load() {
706
- const mod = await import("./dist-OTJZRQ3Q.js");
1218
+ const mod = await import("./dist-XWWZVLQQ.js");
707
1219
  return { plugin: mod.oktaPlugin, seedFromConfig: mod.seedFromConfig };
708
1220
  },
709
1221
  defaultFallback(cfg) {
@@ -715,13 +1227,15 @@ var SERVICE_REGISTRY = {
715
1227
  users: [{ login: "testuser@okta.local", email: "testuser@okta.local", first_name: "Test", last_name: "User" }],
716
1228
  groups: [{ name: "Everyone", description: "All users", type: "BUILT_IN", okta_id: "00g_everyone" }],
717
1229
  authorization_servers: [{ id: "default", name: "default", audiences: ["api://default"] }],
718
- oauth_clients: [{
719
- client_id: "okta-test-client",
720
- client_secret: "okta-test-secret",
721
- name: "Sample OIDC Client",
722
- redirect_uris: ["http://localhost:3000/callback"],
723
- auth_server_id: "default"
724
- }]
1230
+ oauth_clients: [
1231
+ {
1232
+ client_id: "okta-test-client",
1233
+ client_secret: "okta-test-secret",
1234
+ name: "Sample OIDC Client",
1235
+ redirect_uris: ["http://localhost:3000/callback"],
1236
+ auth_server_id: "default"
1237
+ }
1238
+ ]
725
1239
  }
726
1240
  }
727
1241
  },
@@ -729,7 +1243,7 @@ var SERVICE_REGISTRY = {
729
1243
  label: "AWS cloud service emulator",
730
1244
  endpoints: "S3 (buckets, objects), SQS (queues, messages), IAM (users, roles, access keys), STS (assume role, caller identity)",
731
1245
  async load() {
732
- const mod = await import("./dist-VVXVP5EZ.js");
1246
+ const mod = await import("./dist-DSJSF3GY.js");
733
1247
  return { plugin: mod.awsPlugin, seedFromConfig: mod.seedFromConfig };
734
1248
  },
735
1249
  defaultFallback() {
@@ -751,7 +1265,7 @@ var SERVICE_REGISTRY = {
751
1265
  label: "Resend email API emulator",
752
1266
  endpoints: "emails, domains, contacts, API keys, inbox UI",
753
1267
  async load() {
754
- const mod = await import("./dist-QMOJM6DV.js");
1268
+ const mod = await import("./dist-R3TNKUIE.js");
755
1269
  return { plugin: mod.resendPlugin, seedFromConfig: mod.seedFromConfig };
756
1270
  },
757
1271
  defaultFallback() {
@@ -766,9 +1280,9 @@ var SERVICE_REGISTRY = {
766
1280
  },
767
1281
  stripe: {
768
1282
  label: "Stripe payments emulator",
769
- endpoints: "customers, payment intents, charges, products, prices, checkout sessions, webhooks",
1283
+ endpoints: "customers, payment methods, customer sessions, payment intents, charges, products, prices, checkout sessions, webhooks",
770
1284
  async load() {
771
- const mod = await import("./dist-YOVM5HEY.js");
1285
+ const mod = await import("./dist-NJJLJT2N.js");
772
1286
  return { plugin: mod.stripePlugin, seedFromConfig: mod.seedFromConfig };
773
1287
  },
774
1288
  defaultFallback() {
@@ -786,7 +1300,7 @@ var SERVICE_REGISTRY = {
786
1300
  label: "MongoDB Atlas service emulator",
787
1301
  endpoints: "Atlas Admin API v2 (projects, clusters, database users, databases, collections), Atlas Data API v1 (findOne, find, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate)",
788
1302
  async load() {
789
- const mod = await import("./dist-B674PYKV.js");
1303
+ const mod = await import("./dist-2ZZGNPJI.js");
790
1304
  return { plugin: mod.mongoatlasPlugin, seedFromConfig: mod.seedFromConfig };
791
1305
  },
792
1306
  defaultFallback() {
@@ -800,11 +1314,68 @@ var SERVICE_REGISTRY = {
800
1314
  databases: [{ cluster: "Cluster0", name: "test", collections: ["items"] }]
801
1315
  }
802
1316
  }
1317
+ },
1318
+ clerk: {
1319
+ label: "Clerk authentication and user management emulator",
1320
+ endpoints: "OIDC discovery, JWKS, OAuth authorize/token/userinfo, users, email addresses, organizations, memberships, invitations, sessions",
1321
+ async load() {
1322
+ const mod = await import("./dist-IRUBHCZU.js");
1323
+ return { plugin: mod.clerkPlugin, seedFromConfig: mod.seedFromConfig };
1324
+ },
1325
+ defaultFallback(cfg) {
1326
+ const firstEmail = cfg?.users?.[0]?.email_addresses?.[0] ?? "test@example.com";
1327
+ return { login: firstEmail, id: 1, scopes: [] };
1328
+ },
1329
+ initConfig: {
1330
+ clerk: {
1331
+ users: [
1332
+ {
1333
+ first_name: "Test",
1334
+ last_name: "User",
1335
+ email_addresses: ["test@example.com"],
1336
+ password: "clerk_test_password"
1337
+ }
1338
+ ],
1339
+ organizations: [
1340
+ {
1341
+ name: "My Company",
1342
+ slug: "my-company",
1343
+ members: [{ email: "test@example.com", role: "admin" }]
1344
+ }
1345
+ ],
1346
+ oauth_applications: [
1347
+ {
1348
+ client_id: "clerk_emulate_client",
1349
+ client_secret: "clerk_emulate_secret",
1350
+ name: "Emulate App",
1351
+ redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
1352
+ }
1353
+ ]
1354
+ }
1355
+ }
803
1356
  }
804
1357
  };
805
1358
 
1359
+ // src/base-url.ts
1360
+ function resolveBaseUrl(opts) {
1361
+ if (opts.seedBaseUrl) {
1362
+ return opts.seedBaseUrl.replace(/\{service\}/g, opts.service);
1363
+ }
1364
+ if (opts.baseUrl) {
1365
+ return opts.baseUrl.replace(/\{service\}/g, opts.service);
1366
+ }
1367
+ const envBaseUrl = process.env.EMULATE_BASE_URL;
1368
+ if (envBaseUrl) {
1369
+ return envBaseUrl.replace(/\{service\}/g, opts.service);
1370
+ }
1371
+ const portlessUrl = process.env.PORTLESS_URL;
1372
+ if (portlessUrl) {
1373
+ return portlessUrl.replace(/\{service\}/g, opts.service);
1374
+ }
1375
+ return `http://localhost:${opts.port}`;
1376
+ }
1377
+
806
1378
  // src/api.ts
807
- import { serve } from "@hono/node-server";
808
1379
  async function createEmulator(options) {
809
1380
  const { service, port = 4e3, seed: seedConfig } = options;
810
1381
  const entry = SERVICE_REGISTRY[service];
@@ -821,17 +1392,18 @@ async function createEmulator(options) {
821
1392
  } else {
822
1393
  tokens["test_token_admin"] = { login: "admin", id: 2, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
823
1394
  }
824
- const baseUrl = `http://localhost:${port}`;
1395
+ const svcSeedConfig = seedConfig?.[service];
1396
+ const seedBaseUrl = typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : void 0;
1397
+ const baseUrl = resolveBaseUrl({ service, port, baseUrl: options.baseUrl, seedBaseUrl });
825
1398
  let cachedResolver;
826
1399
  const appKeyResolver = loaded.createAppKeyResolver ? (appId) => cachedResolver(appId) : void 0;
827
- const svcSeedConfig = seedConfig?.[service];
828
1400
  const fallbackUser = entry.defaultFallback(svcSeedConfig);
829
- const { app, store } = createServer(loaded.plugin, { port, baseUrl, tokens, appKeyResolver, fallbackUser });
1401
+ const { app, store, webhooks } = createServer(loaded.plugin, { port, baseUrl, tokens, appKeyResolver, fallbackUser });
830
1402
  cachedResolver = loaded.createAppKeyResolver?.(store);
831
1403
  const seed = () => {
832
1404
  loaded.plugin.seed?.(store, baseUrl);
833
1405
  if (svcSeedConfig && loaded.seedFromConfig) {
834
- loaded.seedFromConfig(store, baseUrl, svcSeedConfig);
1406
+ loaded.seedFromConfig(store, baseUrl, svcSeedConfig, webhooks);
835
1407
  }
836
1408
  };
837
1409
  seed();