@jaredwray/mockhttp 1.9.0 → 2.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.
Files changed (67) hide show
  1. package/README.md +40 -8
  2. package/dist/index.d.mts +90 -11
  3. package/dist/index.mjs +203 -240
  4. package/package.json +17 -13
  5. package/site/dist/api/index.html +17983 -0
  6. package/site/dist/api/swagger.json +5487 -0
  7. package/site/dist/changelog/index.html +784 -0
  8. package/site/dist/changelog/page/2/index.html +577 -0
  9. package/site/dist/changelog/v0-2-0/index.html +437 -0
  10. package/site/dist/changelog/v0-2-1/index.html +438 -0
  11. package/site/dist/changelog/v0-2-2/index.html +436 -0
  12. package/site/dist/changelog/v0-2-3/index.html +440 -0
  13. package/site/dist/changelog/v0-3-0/index.html +443 -0
  14. package/site/dist/changelog/v0-4-0/index.html +437 -0
  15. package/site/dist/changelog/v0-4-1/index.html +441 -0
  16. package/site/dist/changelog/v0-4-2/index.html +446 -0
  17. package/site/dist/changelog/v0-4-3/index.html +437 -0
  18. package/site/dist/changelog/v0-5-0/index.html +443 -0
  19. package/site/dist/changelog/v0-6-0/index.html +444 -0
  20. package/site/dist/changelog/v0-6-1/index.html +441 -0
  21. package/site/dist/changelog/v0-7-0/index.html +445 -0
  22. package/site/dist/changelog/v0-8-0/index.html +440 -0
  23. package/site/dist/changelog/v0-9-0/index.html +437 -0
  24. package/site/dist/changelog/v1-0-0/index.html +446 -0
  25. package/site/dist/changelog/v1-0-1/index.html +437 -0
  26. package/site/dist/changelog/v1-1-0/index.html +445 -0
  27. package/site/dist/changelog/v1-2-0/index.html +443 -0
  28. package/site/dist/changelog/v1-3-0/index.html +448 -0
  29. package/site/dist/changelog/v1-4-0/index.html +445 -0
  30. package/site/dist/changelog/v1-5-0/index.html +439 -0
  31. package/site/dist/changelog/v1-5-1/index.html +443 -0
  32. package/site/dist/changelog/v1-6-0/index.html +467 -0
  33. package/site/dist/changelog/v1-7-0/index.html +462 -0
  34. package/site/dist/changelog/v1-8-0/index.html +481 -0
  35. package/site/dist/changelog/v1-8-1/index.html +457 -0
  36. package/site/dist/changelog/v1-9-0/index.html +495 -0
  37. package/site/dist/changelog/v2-0-0/index.html +500 -0
  38. package/site/dist/changelog/v2-1-0/index.html +476 -0
  39. package/site/dist/changelog-latest.json +269 -0
  40. package/site/dist/changelog.json +399 -0
  41. package/site/dist/css/api.css +1113 -0
  42. package/site/dist/css/highlight/highlight.min.js +1433 -0
  43. package/site/dist/css/highlight/styles/base16/docula.css +123 -0
  44. package/site/dist/css/home.css +133 -0
  45. package/site/dist/css/styles.css +1227 -0
  46. package/site/dist/css/variables.css +32 -0
  47. package/site/dist/docs/bins/index.html +980 -0
  48. package/site/dist/docs/configuration/index.html +809 -0
  49. package/site/dist/docs/hosted/index.html +583 -0
  50. package/site/dist/docs/how-to-deploy/cloudflare/index.html +655 -0
  51. package/site/dist/docs/how-to-deploy/docker/index.html +664 -0
  52. package/site/dist/docs/how-to-deploy/wasmer/index.html +625 -0
  53. package/site/dist/docs/https/index.html +796 -0
  54. package/site/dist/docs/index.html +640 -0
  55. package/site/dist/docs/library/index.html +818 -0
  56. package/site/dist/docs/taps/index.html +746 -0
  57. package/site/dist/favicon.svg +14 -0
  58. package/site/dist/feed.xml +1 -0
  59. package/site/dist/index.html +640 -0
  60. package/site/dist/js/api.js +363 -0
  61. package/site/dist/js/search.js +436 -0
  62. package/site/dist/llms-full.txt +7640 -0
  63. package/site/dist/llms.txt +43 -0
  64. package/site/dist/logo.svg +21 -0
  65. package/site/dist/robots.txt +2 -0
  66. package/site/dist/search-index.json +1 -0
  67. package/site/dist/sitemap.xml +1 -0
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import process$1 from "node:process";
2
+ import { existsSync, readFileSync } from "node:fs";
2
3
  import * as fsPromises from "node:fs/promises";
3
4
  import path, { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
4
6
  import fastifyCookie from "@fastify/cookie";
5
7
  import fastifyHelmet from "@fastify/helmet";
6
8
  import fastifyRateLimit from "@fastify/rate-limit";
@@ -12,10 +14,8 @@ import { Hookified } from "hookified";
12
14
  import * as crypto$1 from "node:crypto";
13
15
  import crypto, { randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
14
16
  import { Buffer as Buffer$1 } from "node:buffer";
15
- import { readFileSync } from "node:fs";
16
17
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
17
18
  import { escape } from "html-escaper";
18
- import { fastifySwaggerUi } from "@fastify/swagger-ui";
19
19
  //#region src/bin-store.ts
20
20
  /**
21
21
  * In-memory implementation of {@link BinStore} backed by two Maps. State is
@@ -188,7 +188,7 @@ var BinManager = class {
188
188
  this._cleanupTimer = setInterval(() => {
189
189
  this._store.cleanupExpired(Date.now());
190
190
  }, this._cleanupIntervalMs);
191
- this._cleanupTimer.unref();
191
+ this._cleanupTimer.unref?.();
192
192
  }
193
193
  /** Stop the periodic cleanup. Idempotent. */
194
194
  stop() {
@@ -2043,6 +2043,18 @@ const putRoute = (fastify) => {
2043
2043
  }));
2044
2044
  };
2045
2045
  //#endregion
2046
+ //#region src/public-files.ts
2047
+ const defaultPublicPath$1 = join(process.cwd(), "public");
2048
+ const defaultReader = (file) => readFileSync(join(defaultPublicPath$1, file));
2049
+ let reader = defaultReader;
2050
+ /**
2051
+ * Read a file from the package `public/` directory, or from the active reader.
2052
+ */
2053
+ async function readPublicFile(file) {
2054
+ const bytes = await reader(file);
2055
+ return Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
2056
+ }
2057
+ //#endregion
2046
2058
  //#region src/routes/images/index.ts
2047
2059
  const imageSchema = {
2048
2060
  description: "Returns an image based on Accept header (supports jpeg, png, svg, webp)",
@@ -2096,8 +2108,19 @@ const webpSchema = {
2096
2108
  format: "binary"
2097
2109
  } }
2098
2110
  };
2111
+ async function sendImage(reply, file, contentType) {
2112
+ try {
2113
+ const imageBuffer = await readPublicFile(file);
2114
+ reply.type(contentType);
2115
+ return imageBuffer;
2116
+ } catch (error) {
2117
+ /* v8 ignore next -- @preserve */
2118
+ reply.code(500);
2119
+ /* v8 ignore next -- @preserve */
2120
+ return { error: `${error}` };
2121
+ }
2122
+ }
2099
2123
  const imageRoutes = (fastify) => {
2100
- const publicPath = join(process.cwd(), "public");
2101
2124
  fastify.get("/image", { schema: imageSchema }, async (request, reply) => {
2102
2125
  const accept = request.headers.accept || "";
2103
2126
  let file = "logo.png";
@@ -2115,152 +2138,19 @@ const imageRoutes = (fastify) => {
2115
2138
  file = "logo.png";
2116
2139
  contentType = "image/png";
2117
2140
  }
2118
- try {
2119
- const imagePath = join(publicPath, file);
2120
- const imageBuffer = readFileSync(imagePath);
2121
- reply.type(contentType);
2122
- return imageBuffer;
2123
- } catch (error) {
2124
- /* v8 ignore next -- @preserve */
2125
- reply.code(500);
2126
- /* v8 ignore next -- @preserve */
2127
- return { error: `${error}` };
2128
- }
2141
+ return sendImage(reply, file, contentType);
2129
2142
  });
2130
2143
  fastify.get("/image/jpeg", { schema: jpegSchema }, async (_request, reply) => {
2131
- try {
2132
- const imagePath = join(publicPath, "logo.jpg");
2133
- const imageBuffer = readFileSync(imagePath);
2134
- reply.type("image/jpeg");
2135
- return imageBuffer;
2136
- } catch (error) {
2137
- /* v8 ignore next -- @preserve */
2138
- reply.code(500);
2139
- /* v8 ignore next -- @preserve */
2140
- return { error: `${error}` };
2141
- }
2144
+ return sendImage(reply, "logo.jpg", "image/jpeg");
2142
2145
  });
2143
2146
  fastify.get("/image/png", { schema: pngSchema }, async (_request, reply) => {
2144
- try {
2145
- const imagePath = join(publicPath, "logo.png");
2146
- const imageBuffer = readFileSync(imagePath);
2147
- reply.type("image/png");
2148
- return imageBuffer;
2149
- } catch (error) {
2150
- /* v8 ignore next -- @preserve */
2151
- reply.code(500);
2152
- /* v8 ignore next -- @preserve */
2153
- return { error: `${error}` };
2154
- }
2147
+ return sendImage(reply, "logo.png", "image/png");
2155
2148
  });
2156
2149
  fastify.get("/image/svg", { schema: svgSchema }, async (_request, reply) => {
2157
- try {
2158
- const imagePath = join(publicPath, "logo.svg");
2159
- const imageBuffer = readFileSync(imagePath);
2160
- reply.type("image/svg+xml");
2161
- return imageBuffer;
2162
- } catch (error) {
2163
- /* v8 ignore next -- @preserve */
2164
- reply.code(500);
2165
- /* v8 ignore next -- @preserve */
2166
- return { error: `${error}` };
2167
- }
2150
+ return sendImage(reply, "logo.svg", "image/svg+xml");
2168
2151
  });
2169
2152
  fastify.get("/image/webp", { schema: webpSchema }, async (_request, reply) => {
2170
- try {
2171
- const imagePath = join(publicPath, "logo.webp");
2172
- const imageBuffer = readFileSync(imagePath);
2173
- reply.type("image/webp");
2174
- return imageBuffer;
2175
- } catch (error) {
2176
- /* v8 ignore next -- @preserve */
2177
- reply.code(500);
2178
- /* v8 ignore next -- @preserve */
2179
- return { error: `${error}` };
2180
- }
2181
- });
2182
- };
2183
- //#endregion
2184
- //#region src/routes/index.ts
2185
- const indexRoute = (fastify) => {
2186
- fastify.get("/", { schema: { hide: true } }, async (_request, reply) => {
2187
- await reply.type("text/html").send(`
2188
- <!doctype html>
2189
- <html>
2190
- <head>
2191
- <title>{ mockhttp }</title>
2192
- <meta charset="utf-8" />
2193
- <meta
2194
- name="viewport"
2195
- content="width=device-width, initial-scale=1" />
2196
- <meta name="description" content="MockHttp - A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
2197
- <meta name="keywords" content="mock, http, httpbin, testing, api, fastify, docker, typescript, rest, mock-server, api-testing, http-testing, httpbin-alternative" />
2198
- <meta property="og:title" content="MockHttp - HTTP Mock Server" />
2199
- <meta property="og:description" content="A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
2200
- <meta property="og:type" content="website" />
2201
- <meta property="og:url" content="https://mockhttp.org" />
2202
- <meta property="og:image" content="https://mockhttp.org/logo.png" />
2203
- <meta property="og:site_name" content="MockHttp" />
2204
- <meta name="twitter:card" content="summary" />
2205
- <meta name="twitter:title" content="MockHttp - HTTP Mock Server" />
2206
- <meta name="twitter:description" content="A powerful HTTP mock server and httpbin replacement for API testing." />
2207
- <meta name="twitter:image" content="https://mockhttp.org/logo.png" />
2208
- <style>
2209
- :root {
2210
- --scalar-custom-header-height: 70px;
2211
- }
2212
- .custom-header {
2213
- height: var(--scalar-custom-header-height);
2214
- background-color: var(--scalar-background-1);
2215
- box-shadow: inset 0 -1px 0 var(--scalar-border-color);
2216
- color: var(--scalar-color-1);
2217
- font-size: var(--scalar-font-size-2);
2218
- padding: 0 18px;
2219
- position: sticky;
2220
- justify-content: space-between;
2221
- top: 0;
2222
- z-index: 100;
2223
- }
2224
- .custom-header,
2225
- .custom-header nav {
2226
- display: flex;
2227
- align-items: center;
2228
- gap: 18px;
2229
- }
2230
-
2231
- .custom-header img {
2232
- height: var(--scalar-custom-header-height);
2233
- object-fit: contain;
2234
- }
2235
-
2236
- .custom-header a:hover {
2237
- color: var(--scalar-color-2);
2238
- }
2239
-
2240
- .github-corner {
2241
- fill: var(--scalar-border-color) !important;
2242
- }
2243
-
2244
- </style>
2245
- </head>
2246
- <body>
2247
- <header class="custom-header scalar-app">
2248
- <a href="/"><img src="/logo.svg" alt="{ mockhttp }" /></a>
2249
- <a href="https://github.com/jaredwray/mockhttp" class="github-corner" aria-label="View source on GitHub">
2250
- <svg width="70" height="70" viewBox="0 0 250 250" style="color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
2251
- <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
2252
- <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
2253
- <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
2254
- </header>
2255
-
2256
- <script
2257
- id="api-reference"
2258
- data-url="/docs/json"><\/script>
2259
-
2260
- <script src="/scalar/browser/standalone.js"><\/script>
2261
- </body>
2262
- </html>
2263
- `);
2153
+ return sendImage(reply, "logo.webp", "image/webp");
2264
2154
  });
2265
2155
  };
2266
2156
  //#endregion
@@ -3182,29 +3072,6 @@ const responseHeadersRoutes = (fastify) => {
3182
3072
  });
3183
3073
  };
3184
3074
  //#endregion
3185
- //#region src/routes/sitemap.ts
3186
- const sitemapRoute = (fastify) => {
3187
- fastify.get("/sitemap.xml", { schema: { hide: true } }, async (_request, reply) => {
3188
- try {
3189
- const host = _request.headers.host ?? "mockhttp.org";
3190
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
3191
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3192
- <url>
3193
- <loc>${_request.headers["x-forwarded-proto"]?.includes("https") ? "https" : "http"}://${host}/</loc>
3194
- <changefreq>monthly</changefreq>
3195
- <priority>1.0</priority>
3196
- </url>
3197
- </urlset>`;
3198
- await reply.type("text/xml").send(xml);
3199
- } catch (error) {
3200
- /* v8 ignore next -- @preserve */
3201
- fastify.log.error(error);
3202
- /* v8 ignore next -- @preserve */
3203
- await reply.status(500).send({ error: "Internal Server Error" });
3204
- }
3205
- });
3206
- };
3207
- //#endregion
3208
3075
  //#region src/routes/status-codes/index.ts
3209
3076
  const statusCodes = [
3210
3077
  {
@@ -3487,47 +3354,30 @@ const statusCodeRoute = (fastify) => {
3487
3354
  fastify.patch("/status/:code?", { schema: statusSchema }, handler);
3488
3355
  fastify.delete("/status/:code?", { schema: statusSchema }, handler);
3489
3356
  };
3490
- const fastifySwaggerConfig = { openapi: {
3491
- info: {
3492
- title: "Mock HTTP API",
3493
- description: `
3357
+ const fastifySwaggerConfig = { openapi: { info: {
3358
+ title: "Mock HTTP API",
3359
+ description: `
3494
3360
  A simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using \`nodejs\` and \`fastify\` with the idea of running it via https://mockhttp.org, via docker \`jaredwray/mockhttp\`, or nodejs \`npm install @jaredwray/mockhttp\`.
3495
3361
 
3496
3362
  * [GitHub Repository](https://github.com/jaredwray/mockhttp)
3497
3363
  * [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)
3498
3364
  * [NPM Package](https://www.npmjs.com/package/@jaredwray/mockhttp)
3499
3365
 
3500
- # About mockhttp.org
3366
+ # About mockhttp.org
3501
3367
 
3502
- [mockhttp.org](https://mockhttp.org) is a free service that runs this codebase and allows you to use it for testing purposes. It is a simple way to mock HTTP responses for testing purposes. Ran via [Cloudflare](https://cloudflare.com) and [Google Cloud Run](https://cloud.google.com/run/) across 7 regions globally and can do millions of requests per second.
3368
+ [mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a Cloudflare Worker (no containers): documentation is served from Workers Assets, and mock APIs such as \`/get\` and \`/post\` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse.
3503
3369
  `,
3504
- version: "1.9.0"
3505
- },
3506
- consumes: ["application/json"],
3507
- produces: ["application/json"]
3508
- } };
3509
- const registerSwaggerUi = async (fastify) => {
3510
- await fastify.register(fastifySwaggerUi, {
3511
- routePrefix: "/docs",
3512
- uiConfig: {
3513
- docExpansion: "none",
3514
- deepLinking: false
3515
- },
3516
- uiHooks: {
3517
- /* v8 ignore next -- @preserve */
3518
- onRequest(_request, _reply, next) {
3519
- next();
3520
- },
3521
- /* v8 ignore next -- @preserve */
3522
- preHandler(_request, _reply, next) {
3523
- next();
3524
- }
3525
- },
3526
- staticCSP: true,
3527
- /* v8 ignore next -- @preserve */
3528
- transformSpecification: (swaggerObject, _request, _reply) => swaggerObject,
3529
- transformSpecificationClone: true
3530
- });
3370
+ version: "2.1.0"
3371
+ } } };
3372
+ const withLocalServer = (spec) => {
3373
+ spec.servers = [{
3374
+ url: "",
3375
+ description: "This instance"
3376
+ }];
3377
+ return spec;
3378
+ };
3379
+ const registerOpenApiJson = async (fastify) => {
3380
+ fastify.get("/openapi.json", { schema: { hide: true } }, async () => withLocalServer(fastify.swagger()));
3531
3381
  };
3532
3382
  //#endregion
3533
3383
  //#region src/tap-manager.ts
@@ -3630,13 +3480,29 @@ var TapManager = class {
3630
3480
  };
3631
3481
  //#endregion
3632
3482
  //#region src/mock-http.ts
3483
+ function resolvePackageRoot(metaUrl) {
3484
+ if (!metaUrl) return process.cwd();
3485
+ return path.resolve(path.dirname(fileURLToPath(metaUrl)), "..");
3486
+ }
3487
+ const packageRoot = resolvePackageRoot(import.meta.url);
3488
+ const defaultSiteDistPath = path.join(packageRoot, "site", "dist");
3489
+ const defaultPublicPath = path.join(packageRoot, "public");
3490
+ const requestOrigin = (request) => {
3491
+ const host = request.headers.host || "mockhttp.org";
3492
+ const forwarded = request.headers["x-forwarded-proto"];
3493
+ return `${((Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? request.protocol).includes("https") ? "https" : "http"}://${host}`;
3494
+ };
3633
3495
  var MockHttp = class extends Hookified {
3634
3496
  _port = 3e3;
3635
3497
  _host = "0.0.0.0";
3636
3498
  _autoDetectPort = true;
3637
3499
  _helmet = true;
3500
+ _staticFiles = true;
3638
3501
  _apiDocs = true;
3502
+ _siteDistPath = defaultSiteDistPath;
3639
3503
  _logging = true;
3504
+ _pluginTimeout;
3505
+ _startBins = true;
3640
3506
  _httpBin = {
3641
3507
  httpMethods: true,
3642
3508
  redirects: true,
@@ -3660,21 +3526,26 @@ var MockHttp = class extends Hookified {
3660
3526
  _http1 = true;
3661
3527
  _https;
3662
3528
  _httpsCredentials;
3663
- _server = Fastify();
3529
+ _server;
3664
3530
  _taps = new TapManager();
3665
3531
  _bins = new BinManager();
3666
3532
  constructor(options) {
3667
3533
  super(options?.hookOptions);
3668
3534
  if (options?.port) this._port = options.port;
3669
3535
  if (options?.host) this._host = options.host;
3536
+ if (options?.autoDetectPort !== void 0) this._autoDetectPort = options.autoDetectPort;
3670
3537
  if (options?.helmet !== void 0) this._helmet = options.helmet;
3538
+ if (options?.staticFiles !== void 0) this._staticFiles = options.staticFiles;
3671
3539
  if (options?.apiDocs !== void 0) this._apiDocs = options.apiDocs;
3540
+ if (options?.siteDistPath !== void 0) this._siteDistPath = path.resolve(options.siteDistPath);
3672
3541
  if (options?.httpBin !== void 0) this._httpBin = options.httpBin;
3673
3542
  if (options?.rateLimit !== void 0) {
3674
3543
  if (options.rateLimit === false) this._rateLimit = void 0;
3675
3544
  else this._rateLimit = options.rateLimit;
3676
3545
  }
3677
3546
  if (options?.logging !== void 0) this._logging = options.logging;
3547
+ if (options?.pluginTimeout !== void 0) this._pluginTimeout = options.pluginTimeout;
3548
+ if (options?.startBins !== void 0) this._startBins = options.startBins;
3678
3549
  if (options?.http2 !== void 0) this._http2 = options.http2;
3679
3550
  if (options?.http1 !== void 0) this._http1 = options.http1;
3680
3551
  if (options?.https !== void 0) {
@@ -3740,20 +3611,48 @@ var MockHttp = class extends Hookified {
3740
3611
  this._helmet = helmet;
3741
3612
  }
3742
3613
  /**
3743
- * Whether to use Swagger for API documentation. Defaults to true.
3614
+ * Whether to serve files from the package `public/` directory. Defaults to true.
3615
+ * @default true
3616
+ */
3617
+ get staticFiles() {
3618
+ return this._staticFiles;
3619
+ }
3620
+ /**
3621
+ * Whether to serve files from the package `public/` directory. Defaults to true.
3622
+ * @default true
3623
+ */
3624
+ set staticFiles(staticFiles) {
3625
+ this._staticFiles = staticFiles;
3626
+ }
3627
+ /**
3628
+ * Whether to serve the Docula documentation site and OpenAPI spec. Defaults to true.
3744
3629
  * @default true
3745
3630
  */
3746
3631
  get apiDocs() {
3747
3632
  return this._apiDocs;
3748
3633
  }
3749
3634
  /**
3750
- * Whether to use Swagger for API documentation. Defaults to true.
3635
+ * Whether to serve the Docula documentation site and OpenAPI spec. Defaults to true.
3751
3636
  * @default true
3752
3637
  */
3753
3638
  set apiDocs(apiDocs) {
3754
3639
  this._apiDocs = apiDocs;
3755
3640
  }
3756
3641
  /**
3642
+ * Path to the built Docula site served when `apiDocs` is enabled.
3643
+ * @default the package `site/dist` directory
3644
+ */
3645
+ get siteDistPath() {
3646
+ return this._siteDistPath;
3647
+ }
3648
+ /**
3649
+ * Path to the built Docula site served when `apiDocs` is enabled.
3650
+ * @default the package `site/dist` directory
3651
+ */
3652
+ set siteDistPath(siteDistPath) {
3653
+ this._siteDistPath = path.resolve(siteDistPath);
3654
+ }
3655
+ /**
3757
3656
  * Whether to enable logging. Defaults to true.
3758
3657
  * @default true
3759
3658
  */
@@ -3768,6 +3667,32 @@ var MockHttp = class extends Hookified {
3768
3667
  this._logging = logging;
3769
3668
  }
3770
3669
  /**
3670
+ * Fastify plugin boot timeout in milliseconds. `undefined` uses Fastify's default.
3671
+ */
3672
+ get pluginTimeout() {
3673
+ return this._pluginTimeout;
3674
+ }
3675
+ /**
3676
+ * Fastify plugin boot timeout in milliseconds. `undefined` uses Fastify's default.
3677
+ */
3678
+ set pluginTimeout(pluginTimeout) {
3679
+ this._pluginTimeout = pluginTimeout;
3680
+ }
3681
+ /**
3682
+ * Whether to start the request-bin cleanup timer during `initialize()`.
3683
+ * @default true
3684
+ */
3685
+ get startBins() {
3686
+ return this._startBins;
3687
+ }
3688
+ /**
3689
+ * Whether to start the request-bin cleanup timer during `initialize()`.
3690
+ * @default true
3691
+ */
3692
+ set startBins(startBins) {
3693
+ this._startBins = startBins;
3694
+ }
3695
+ /**
3771
3696
  * HTTPS configuration. Set to true to auto-generate a self-signed certificate,
3772
3697
  * or provide an HttpsOptions object with cert/key or auto-generation options.
3773
3698
  */
@@ -3851,6 +3776,7 @@ var MockHttp = class extends Hookified {
3851
3776
  * The Fastify server instance.
3852
3777
  */
3853
3778
  get server() {
3779
+ this._server ??= Fastify();
3854
3780
  return this._server;
3855
3781
  }
3856
3782
  /**
@@ -3884,15 +3810,17 @@ var MockHttp = class extends Hookified {
3884
3810
  this._bins = bins;
3885
3811
  }
3886
3812
  /**
3887
- * Start the Fastify server. If the server is already running, it will be closed and restarted.
3813
+ * Register plugins and routes without listening. Use `inject()` against
3814
+ * `server`, or call `start()` to bind a port.
3888
3815
  */
3889
- async start() {
3816
+ async initialize() {
3890
3817
  this._httpsCredentials = void 0;
3891
3818
  if (this._https) this._httpsCredentials = await this.resolveHttpsCredentials(this._https);
3892
3819
  try {
3893
3820
  /* v8 ignore next -- @preserve */
3894
- if (this._server) await this._server.close();
3821
+ if (this._server?.server.listening) await this._server.close();
3895
3822
  const fastifyOptions = { ...getFastifyConfig(this._logging) };
3823
+ if (this._pluginTimeout !== void 0) fastifyOptions.pluginTimeout = this._pluginTimeout;
3896
3824
  if (this._http2) fastifyOptions.http2 = true;
3897
3825
  if (this._httpsCredentials) {
3898
3826
  const httpsOptions = {
@@ -3912,22 +3840,14 @@ var MockHttp = class extends Hookified {
3912
3840
  return reply.send(response);
3913
3841
  }
3914
3842
  });
3915
- await this._server.register(fastifyStatic, {
3916
- root: path.resolve("./node_modules/@scalar/api-reference/dist"),
3917
- prefix: "/scalar"
3918
- });
3919
- await this.server.register(fastifyStatic, {
3920
- root: path.resolve("./public"),
3921
- decorateReply: false
3843
+ if (this._staticFiles) await this.server.register(fastifyStatic, {
3844
+ root: defaultPublicPath,
3845
+ wildcard: false,
3846
+ index: false
3922
3847
  });
3923
- await this._server.register(sitemapRoute);
3924
3848
  if (this._helmet) await this._server.register(fastifyHelmet, { contentSecurityPolicy: { directives: {
3925
3849
  defaultSrc: ["'self'"],
3926
- scriptSrc: [
3927
- "'self'",
3928
- "'unsafe-inline'",
3929
- "'wasm-unsafe-eval'"
3930
- ],
3850
+ scriptSrc: ["'self'", "'unsafe-inline'"],
3931
3851
  styleSrc: ["'self'", "'unsafe-inline'"],
3932
3852
  imgSrc: [
3933
3853
  "'self'",
@@ -3935,13 +3855,13 @@ var MockHttp = class extends Hookified {
3935
3855
  "https:"
3936
3856
  ],
3937
3857
  fontSrc: ["'self'", "data:"],
3938
- connectSrc: ["'self'"],
3858
+ connectSrc: ["'self'", "https:"],
3939
3859
  frameSrc: ["'self'"],
3940
3860
  objectSrc: ["'none'"],
3941
3861
  upgradeInsecureRequests: []
3942
3862
  } } });
3943
3863
  if (this._rateLimit) await this._server.register(fastifyRateLimit, this._rateLimit);
3944
- if (this._apiDocs) await this.registerApiDocs();
3864
+ if (this._apiDocs) await this.registerSwagger();
3945
3865
  const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes, cookies, anything, auth, images, dynamicData, bins } = this._httpBin;
3946
3866
  if (httpMethods) await this.registerHttpMethods();
3947
3867
  if (statusCodes) await this.registerStatusCodeRoutes();
@@ -3956,20 +3876,32 @@ var MockHttp = class extends Hookified {
3956
3876
  if (dynamicData) await this.registerDynamicDataRoutes();
3957
3877
  if (bins) {
3958
3878
  await this.registerBinRoutes();
3959
- this._bins.start();
3879
+ if (this._startBins) this._bins.start();
3960
3880
  }
3881
+ if (this._apiDocs) await this.registerSite();
3882
+ } catch (error) {
3883
+ /* v8 ignore next -- @preserve */
3884
+ this.server.log.error(error);
3885
+ }
3886
+ }
3887
+ /**
3888
+ * Start the Fastify server. If the server is already running, it will be closed and restarted.
3889
+ */
3890
+ async start() {
3891
+ await this.initialize();
3892
+ try {
3961
3893
  if (this._autoDetectPort) {
3962
3894
  const originalPort = this._port;
3963
3895
  this._port = await this.detectPort();
3964
- if (originalPort !== this._port) this._server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
3896
+ if (originalPort !== this._port) this.server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
3965
3897
  }
3966
- await this._server.listen({
3898
+ await this.server.listen({
3967
3899
  port: this._port,
3968
3900
  host: this._host
3969
3901
  });
3970
3902
  } catch (error) {
3971
3903
  /* v8 ignore next -- @preserve */
3972
- this._server.log.error(error);
3904
+ this.server.log.error(error);
3973
3905
  }
3974
3906
  }
3975
3907
  /**
@@ -3977,7 +3909,7 @@ var MockHttp = class extends Hookified {
3977
3909
  */
3978
3910
  async close() {
3979
3911
  this._bins.stop();
3980
- await this._server.close();
3912
+ if (this._server) await this._server.close();
3981
3913
  }
3982
3914
  /**
3983
3915
  * Detect the next available port to run on.
@@ -3988,21 +3920,51 @@ var MockHttp = class extends Hookified {
3988
3920
  return detect(port);
3989
3921
  }
3990
3922
  /**
3991
- * This will register the API documentation routes including openapi and swagger ui.
3923
+ * Register OpenAPI generation and the Docula documentation site.
3992
3924
  * @param fastifyInstance - the server instance to register the routes on.
3993
3925
  */
3994
3926
  async registerApiDocs(fastifyInstance) {
3995
- const fastify = fastifyInstance ?? this._server;
3927
+ await this.registerSwagger(fastifyInstance);
3928
+ await this.registerSite(fastifyInstance);
3929
+ }
3930
+ /**
3931
+ * Register `@fastify/swagger` and the live `/openapi.json` spec route.
3932
+ * @param fastifyInstance - the server instance to register the routes on.
3933
+ */
3934
+ async registerSwagger(fastifyInstance) {
3935
+ const fastify = fastifyInstance ?? this.server;
3996
3936
  await fastify.register(fastifySwagger, fastifySwaggerConfig);
3997
- await registerSwaggerUi(fastify);
3998
- await fastify.register(indexRoute);
3937
+ await registerOpenApiJson(fastify);
3938
+ }
3939
+ /**
3940
+ * Serve the built Docula site from `siteDistPath` when the directory exists.
3941
+ * @param fastifyInstance - the server instance to register the routes on.
3942
+ */
3943
+ async registerSite(fastifyInstance) {
3944
+ const fastify = fastifyInstance ?? this.server;
3945
+ if (!existsSync(this._siteDistPath)) {
3946
+ fastify.log.warn(`Docula site not found at ${this._siteDistPath}; skipping static docs`);
3947
+ return;
3948
+ }
3949
+ const sitemapPath = path.join(this._siteDistPath, "sitemap.xml");
3950
+ if (existsSync(sitemapPath)) fastify.get("/sitemap.xml", { schema: { hide: true } }, async (request, reply) => {
3951
+ const xml = await fsPromises.readFile(sitemapPath, "utf8");
3952
+ return reply.type("application/xml").send(xml.replaceAll("https://mockhttp.org", requestOrigin(request)));
3953
+ });
3954
+ await fastify.register(fastifyStatic, {
3955
+ root: this._siteDistPath,
3956
+ prefix: "/",
3957
+ decorateReply: !fastify.hasReplyDecorator("sendFile"),
3958
+ index: ["index.html"],
3959
+ wildcard: true
3960
+ });
3999
3961
  }
4000
3962
  /**
4001
3963
  * Register the HTTP methods routes.
4002
3964
  * @param fastifyInstance - the server instance to register the routes on.
4003
3965
  */
4004
3966
  async registerHttpMethods(fastifyInstance) {
4005
- const fastify = fastifyInstance ?? this._server;
3967
+ const fastify = fastifyInstance ?? this.server;
4006
3968
  await fastify.register(getRoute);
4007
3969
  await fastify.register(postRoute);
4008
3970
  await fastify.register(deleteRoute);
@@ -4014,14 +3976,14 @@ var MockHttp = class extends Hookified {
4014
3976
  * @param fastifyInstance - the server instance to register the routes on.
4015
3977
  */
4016
3978
  async registerStatusCodeRoutes(fastifyInstance) {
4017
- await (fastifyInstance ?? this._server).register(statusCodeRoute);
3979
+ await (fastifyInstance ?? this.server).register(statusCodeRoute);
4018
3980
  }
4019
3981
  /**
4020
3982
  * Register the request inspection routes.
4021
3983
  * @param fastifyInstance - the server instance to register the routes on.
4022
3984
  */
4023
3985
  async registerRequestInspectionRoutes(fastifyInstance) {
4024
- const fastify = fastifyInstance ?? this._server;
3986
+ const fastify = fastifyInstance ?? this.server;
4025
3987
  await fastify.register(ipRoute);
4026
3988
  await fastify.register(headersRoute);
4027
3989
  await fastify.register(userAgentRoute);
@@ -4031,20 +3993,20 @@ var MockHttp = class extends Hookified {
4031
3993
  * @param fastifyInstance - the server instance to register the routes on.
4032
3994
  */
4033
3995
  async registerResponseInspectionRoutes(fastifyInstance) {
4034
- const fastify = fastifyInstance ?? this._server;
3996
+ const fastify = fastifyInstance ?? this.server;
4035
3997
  await fastify.register(cacheRoutes);
4036
3998
  await fastify.register(etagRoutes);
4037
3999
  await fastify.register(responseHeadersRoutes);
4038
4000
  }
4039
4001
  async registerResponseFormatRoutes(fastifyInstance) {
4040
- await (fastifyInstance ?? this._server).register(responseFormatRoutes);
4002
+ await (fastifyInstance ?? this.server).register(responseFormatRoutes);
4041
4003
  }
4042
4004
  /**
4043
4005
  * Register the redirect routes.
4044
4006
  * @param fastifyInstance - the server instance to register the routes on.
4045
4007
  */
4046
4008
  async registerRedirectRoutes(fastifyInstance) {
4047
- const fastify = fastifyInstance ?? this._server;
4009
+ const fastify = fastifyInstance ?? this.server;
4048
4010
  await fastify.register(absoluteRedirectRoute);
4049
4011
  await fastify.register(relativeRedirectRoute);
4050
4012
  await fastify.register(redirectToRoute);
@@ -4054,7 +4016,7 @@ var MockHttp = class extends Hookified {
4054
4016
  * @param fastifyInstance - the server instance to register the routes on.
4055
4017
  */
4056
4018
  async registerCookieRoutes(fastifyInstance) {
4057
- const fastify = fastifyInstance ?? this._server;
4019
+ const fastify = fastifyInstance ?? this.server;
4058
4020
  await fastify.register(fastifyCookie);
4059
4021
  await fastify.register(getCookiesRoute);
4060
4022
  await fastify.register(postCookieRoute);
@@ -4065,14 +4027,14 @@ var MockHttp = class extends Hookified {
4065
4027
  * @param fastifyInstance - the server instance to register the routes on.
4066
4028
  */
4067
4029
  async registerAnythingRoutes(fastifyInstance) {
4068
- await (fastifyInstance ?? this._server).register(anythingRoute);
4030
+ await (fastifyInstance ?? this.server).register(anythingRoute);
4069
4031
  }
4070
4032
  /**
4071
4033
  * Register the auth routes.
4072
4034
  * @param fastifyInstance - the server instance to register the routes on.
4073
4035
  */
4074
4036
  async registerAuthRoutes(fastifyInstance) {
4075
- const fastify = fastifyInstance ?? this._server;
4037
+ const fastify = fastifyInstance ?? this.server;
4076
4038
  await fastify.register(basicAuthRoute);
4077
4039
  await fastify.register(hiddenBasicAuthRoute);
4078
4040
  await fastify.register(bearerAuthRoute);
@@ -4083,14 +4045,14 @@ var MockHttp = class extends Hookified {
4083
4045
  * @param fastifyInstance - the server instance to register the routes on.
4084
4046
  */
4085
4047
  async registerImageRoutes(fastifyInstance) {
4086
- await (fastifyInstance ?? this._server).register(imageRoutes);
4048
+ await (fastifyInstance ?? this.server).register(imageRoutes);
4087
4049
  }
4088
4050
  /**
4089
4051
  * Register the dynamic data routes.
4090
4052
  * @param fastifyInstance - the server instance to register the routes on.
4091
4053
  */
4092
4054
  async registerDynamicDataRoutes(fastifyInstance) {
4093
- const fastify = fastifyInstance ?? this._server;
4055
+ const fastify = fastifyInstance ?? this.server;
4094
4056
  await fastify.register(uuidRoute);
4095
4057
  await fastify.register(bytesRoute);
4096
4058
  await fastify.register(streamBytesRoute);
@@ -4106,7 +4068,7 @@ var MockHttp = class extends Hookified {
4106
4068
  * @param fastifyInstance - the server instance to register the routes on.
4107
4069
  */
4108
4070
  async registerBinRoutes(fastifyInstance) {
4109
- const fastify = fastifyInstance ?? this._server;
4071
+ const fastify = fastifyInstance ?? this.server;
4110
4072
  await fastify.register(binsManagementRoute(this._bins));
4111
4073
  await fastify.register(binsCaptureRoute(this._bins));
4112
4074
  }
@@ -4142,6 +4104,7 @@ const start = async () => {
4142
4104
  if (process$1.env.LOGGING === "false") mockHttp.logging = false;
4143
4105
  /* v8 ignore next -- @preserve */
4144
4106
  if (process$1.env.HTTP2 === "true") mockHttp.http2 = true;
4107
+ if (process$1.env.AUTO_DETECT_PORT === "false") mockHttp.autoDetectPort = false;
4145
4108
  await mockHttp.start();
4146
4109
  return mockHttp;
4147
4110
  };