mockaton 8.16.3 → 8.16.5

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 CHANGED
@@ -29,9 +29,9 @@ Nonetheless, there’s a programmatic API, which is handy
29
29
  for setting up tests (see **Commander API** section).
30
30
 
31
31
  <picture>
32
- <source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
33
- <source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.dark.gold.png">
34
- <img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
32
+ <source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp962x800.light.gold.png">
33
+ <source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp962x800.dark.gold.png">
34
+ <img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.vp962x800.light.gold.png">
35
35
  </picture>
36
36
 
37
37
 
@@ -62,14 +62,20 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
62
62
 
63
63
  <br/>
64
64
 
65
- ## Fallback to Your Backend
66
- No need to mock everything. You can forward requests to your backend for routes
67
- you don’t have mocks for, or routes that have the ☁️ **Cloud Checkbox** checked.
65
+ ## Scraping mocks from your Backend
68
66
 
67
+ ### Option 1: Browser Extension
68
+ This [devtools extension](https://github.com/ericfortis/devtools-ext-zip-http-requests)
69
+ lets you download a ZIP with all the responses following the filename convention.
69
70
 
70
- ### Scraping mocks from your backend
71
- By checking **Save Mocks**, you can collect the responses that hit your backend.
72
- They will be saved in your `config.mocksDir` following the filename convention.
71
+ ### Option 2: Fallback to Your Backend
72
+ This option could be a bit elaborate if your backend uses third-party auth,
73
+ because in that case you’d have to inject the cookies manually.
74
+
75
+ At any rate, you can forward requests to your backend for routes you don’t have
76
+ mocks for, or routes that have the ☁️ **Cloud Checkbox** checked. By checking
77
+ ✅ **Save Mocks**, you can collect the responses that hit your backend. They
78
+ will be saved in your `config.mocksDir` following the filename convention.
73
79
 
74
80
 
75
81
  <br/>
package/dev-config.js ADDED
@@ -0,0 +1,18 @@
1
+ import { join } from 'node:path'
2
+ import { jwtCookie } from './index.js'
3
+
4
+
5
+ export const devConfig = {
6
+ port: 2345,
7
+ mocksDir: join(import.meta.dirname, 'fixtures-mocks'),
8
+ staticDir: join(import.meta.dirname, 'fixtures-static-mocks'),
9
+ cookies: {
10
+ 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
11
+ 'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
12
+ 'My JWT': jwtCookie('my-cookie', {
13
+ email: 'john.doe@example.com',
14
+ picture: 'https://cdn.auth0.com/avatars/jd.png'
15
+ }),
16
+ 'None': ''
17
+ }
18
+ }
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Mockaton } from './src/Mockaton.js'
2
2
  export { Commander } from './src/Commander.js'
3
- export { jsToJsonPlugin } from './src/MockDispatcherPlugins.js'
4
3
 
5
4
  export { jwtCookie } from './src/utils/jwt.js'
6
5
  export { parseJSON } from './src/utils/http-request.js'
6
+ export { jsToJsonPlugin } from './src/MockDispatcher.js'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "8.16.3",
5
+ "version": "8.16.5",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -7,11 +7,11 @@ import { join } from 'node:path'
7
7
  import { cookie } from './cookie.js'
8
8
  import { parseJSON } from './utils/http-request.js'
9
9
  import { uiSyncVersion } from './Watcher.js'
10
+ import * as staticCollection from './staticCollection.js'
10
11
  import * as mockBrokersCollection from './mockBrokersCollection.js'
11
12
  import { config, ConfigValidator } from './config.js'
12
13
  import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
13
14
  import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/http-response.js'
14
- import { getStaticFilesCollection, findStaticBrokerByRoute, initStaticCollection } from './StaticDispatcher.js'
15
15
 
16
16
 
17
17
  const dashboardAssets = [
@@ -25,9 +25,7 @@ const dashboardAssets = [
25
25
 
26
26
  export const apiGetRequests = new Map([
27
27
  [API.dashboard, serveDashboard],
28
- ...dashboardAssets.map(f => [
29
- API.dashboard + f, serveDashboardAsset(f)
30
- ]),
28
+ ...dashboardAssets.map(f => [API.dashboard + f, serveDashboardAsset(f)]),
31
29
  [API.cors, getIsCorsAllowed],
32
30
  [API.static, listStaticFiles],
33
31
  [API.mocks, listMockBrokers],
@@ -68,12 +66,14 @@ function serveDashboardAsset(f) {
68
66
 
69
67
  function listCookies(_, response) { sendJSON(response, cookie.list()) }
70
68
  function listComments(_, response) { sendJSON(response, mockBrokersCollection.extractAllComments()) }
71
- function listStaticFiles(_, response) { sendJSON(response, getStaticFilesCollection()) }
69
+
70
+ function listStaticFiles(_, response) { sendJSON(response, staticCollection.all()) }
71
+ function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.all()) }
72
+
72
73
  function getGlobalDelay(_, response) { sendJSON(response, config.delay) }
73
- function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.getAll()) }
74
74
  function getProxyFallback(_, response) { sendJSON(response, config.proxyFallback) }
75
- function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
76
75
  function getCollectProxied(_, response) { sendJSON(response, config.collectProxied) }
76
+ function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
77
77
 
78
78
  function longPollClientSyncVersion(req, response) {
79
79
  if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
@@ -101,7 +101,7 @@ function longPollClientSyncVersion(req, response) {
101
101
 
102
102
  function reinitialize(_, response) {
103
103
  mockBrokersCollection.init()
104
- initStaticCollection() // TESTME
104
+ staticCollection.init() // TESTME
105
105
  sendOK(response)
106
106
  }
107
107
 
@@ -115,7 +115,7 @@ async function selectCookie(req, response) {
115
115
 
116
116
  async function selectMock(req, response) {
117
117
  const file = await parseJSON(req)
118
- const broker = mockBrokersCollection.findBrokerByFilename(file)
118
+ const broker = mockBrokersCollection.brokerByFilename(file)
119
119
  if (!broker || !broker.hasMock(file))
120
120
  sendUnprocessableContent(response, `Missing Mock: ${file}`)
121
121
  else {
@@ -127,7 +127,7 @@ async function selectMock(req, response) {
127
127
  async function setRouteIsDelayed(req, response) {
128
128
  const body = await parseJSON(req)
129
129
  const delayed = body[DF.delayed]
130
- const broker = mockBrokersCollection.findBrokerByRoute(
130
+ const broker = mockBrokersCollection.brokerByRoute(
131
131
  body[DF.routeMethod],
132
132
  body[DF.routeUrlMask])
133
133
 
@@ -144,7 +144,7 @@ async function setRouteIsDelayed(req, response) {
144
144
  async function setRouteIsProxied(req, response) { // TESTME
145
145
  const body = await parseJSON(req)
146
146
  const proxied = body[DF.proxied]
147
- const broker = mockBrokersCollection.findBrokerByRoute(
147
+ const broker = mockBrokersCollection.brokerByRoute(
148
148
  body[DF.routeMethod],
149
149
  body[DF.routeUrlMask])
150
150
 
@@ -201,7 +201,7 @@ async function setGlobalDelay(req, response) { // TESTME
201
201
  async function setStaticRouteStatusCode(req, response) {
202
202
  const body = await parseJSON(req)
203
203
  const status = Number(body[DF.statusCode])
204
- const broker = findStaticBrokerByRoute(body[DF.routeUrlMask])
204
+ const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
205
205
 
206
206
  if (!broker) // TESTME
207
207
  sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
@@ -217,7 +217,7 @@ async function setStaticRouteStatusCode(req, response) {
217
217
  async function setStaticRouteIsDelayed(req, response) {
218
218
  const body = await parseJSON(req)
219
219
  const delayed = body[DF.delayed]
220
- const broker = findStaticBrokerByRoute(body[DF.routeUrlMask])
220
+ const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
221
221
 
222
222
  if (!broker) // TESTME
223
223
  sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
@@ -31,7 +31,7 @@ export const DF = { // Dashboard Fields (XHR)
31
31
  export const DEFAULT_500_COMMENT = '(Mockaton 500)'
32
32
  export const DEFAULT_MOCK_COMMENT = '(default)'
33
33
  export const EXT_FOR_UNKNOWN_MIME = 'unknown'
34
-
35
34
  export const LONG_POLL_SERVER_TIMEOUT = 8_000
36
35
 
36
+
37
37
  export const HEADER_FOR_502 = 'mockaton502'
package/src/Dashboard.css CHANGED
@@ -1,6 +1,6 @@
1
1
  :root {
2
- --boxShadow1: 0 3px 1px -1px rgba(0, 0, 0, 0.04), 0 1px 1px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
3
2
  --radius: 4px;
3
+ --boxShadow1: 0 3px 1px -1px rgba(0, 0, 0, 0.04), 0 1px 1px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
4
4
  }
5
5
 
6
6
  @media (prefers-color-scheme: light) {
@@ -27,7 +27,7 @@
27
27
  :root {
28
28
  --color4xxBackground: #403630;
29
29
  --colorAccent: #2495ff;
30
- --colorBackground: #161616;
30
+ --colorBackground: #181818;
31
31
  --colorHeaderBackground: #111;
32
32
  --colorComboBoxBackground: #2a2a2a;
33
33
  --colorSecondaryButtonBackground: #2a2a2a;
@@ -113,10 +113,6 @@ select {
113
113
  }
114
114
  }
115
115
 
116
- .red {
117
- color: var(--colorRed);
118
- }
119
-
120
116
  header {
121
117
  display: flex;
122
118
  width: 100%;
@@ -129,8 +125,13 @@ header {
129
125
  flex-wrap: wrap;
130
126
  align-items: flex-end;
131
127
  gap: 10px;
128
+
129
+ @media (max-width: 860px) {
130
+ max-width: 500px;
131
+ }
132
132
  }
133
133
 
134
+
134
135
  img {
135
136
  width: 130px;
136
137
  align-self: center;
@@ -227,7 +228,6 @@ header {
227
228
  color: var(--colorRed);
228
229
  border-radius: 50px;
229
230
 
230
-
231
231
  &:hover {
232
232
  background: var(--colorRed);
233
233
  color: white;
@@ -239,12 +239,19 @@ header {
239
239
  main {
240
240
  display: grid;
241
241
  min-height: 0;
242
- grid-template-columns: 50% 50%;
242
+ grid-template-columns: min(820px) 1fr;
243
+
244
+ @media (max-width: 1160px) {
245
+ grid-template-columns: min(620px) 1fr;
246
+ }
243
247
 
244
248
  @media (max-width: 960px) {
245
249
  grid-template-columns: 100%;
246
- grid-template-rows: 50% 50%;
247
- gap: 32px;
250
+ grid-template-rows: 1fr 1fr;
251
+
252
+ .leftSide {
253
+ box-shadow: var(--boxShadow1);
254
+ }
248
255
  }
249
256
 
250
257
  .leftSide {
@@ -279,32 +286,11 @@ table {
279
286
  }
280
287
 
281
288
 
282
- .PayloadViewer {
283
- /*h2 {*/
284
- /* top: 0;*/
285
- /* position: sticky;*/
286
- /* background: var(--colorBackground);*/
287
- /*}*/
288
- pre {
289
- padding-top: 12px;
290
-
291
- code {
292
- white-space: pre;
293
- tab-size: 2;
294
- font-family: monospace;
295
-
296
- * {
297
- font-family: monospace;
298
- }
299
- }
300
- }
301
- }
302
-
303
289
  .PreviewLink {
304
290
  position: relative;
305
291
  left: -8px;
306
292
  display: inline-block;
307
- width: 240px;
293
+ width: 100%;
308
294
  padding: 8px;
309
295
  margin-left: 4px;
310
296
  border-radius: var(--radius);
@@ -486,6 +472,38 @@ table {
486
472
  }
487
473
  }
488
474
 
475
+
476
+ .StaticFilesList {
477
+ a {
478
+ display: inline-block;
479
+ padding: 6px 0;
480
+ border-radius: var(--radius);
481
+ color: var(--colorAccent);
482
+ text-decoration: none;
483
+
484
+ &:hover {
485
+ text-decoration: underline;
486
+ }
487
+ }
488
+ }
489
+
490
+
491
+ .PayloadViewer {
492
+ pre {
493
+ padding-top: 12px;
494
+
495
+ code {
496
+ white-space: pre;
497
+ tab-size: 2;
498
+ font-family: monospace;
499
+
500
+ * {
501
+ font-family: monospace;
502
+ }
503
+ }
504
+ }
505
+ }
506
+
489
507
  .SpinnerClock {
490
508
  display: flex;
491
509
  width: 36px;
@@ -512,20 +530,8 @@ table {
512
530
  }
513
531
  }
514
532
 
515
-
516
- .StaticFilesList {
517
-
518
- a {
519
- display: inline-block;
520
- padding: 6px 0;
521
- border-radius: var(--radius);
522
- color: var(--colorAccent);
523
- text-decoration: none;
524
-
525
- &:hover {
526
- text-decoration: underline;
527
- }
528
- }
533
+ .red {
534
+ color: var(--colorRed);
529
535
  }
530
536
 
531
537
  .dittoDir {
@@ -538,7 +544,6 @@ table {
538
544
  * Prism
539
545
  */
540
546
 
541
-
542
547
  @media (prefers-color-scheme: dark) {
543
548
  .token.cdata, .token.comment, .token.doctype, .token.prolog {
544
549
  color: #8292a2
@@ -1,8 +1,10 @@
1
1
  import { join } from 'node:path'
2
+ import { readFileSync } from 'node:fs'
3
+ import { pathToFileURL } from 'node:url'
2
4
 
3
5
  import { proxy } from './ProxyRelay.js'
4
6
  import { cookie } from './cookie.js'
5
- import { applyPlugins } from './MockDispatcherPlugins.js'
7
+ import { mimeFor } from './utils/mime.js'
6
8
  import { config, calcDelay } from './config.js'
7
9
  import { BodyReaderError } from './utils/http-request.js'
8
10
  import * as mockBrokerCollection from './mockBrokersCollection.js'
@@ -11,7 +13,7 @@ import { sendInternalServerError, sendNotFound, sendUnprocessableContent } from
11
13
 
12
14
  export async function dispatchMock(req, response) {
13
15
  try {
14
- const broker = mockBrokerCollection.findBrokerByRoute(req.method, req.url)
16
+ const broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
15
17
  if (!broker || broker.proxied) {
16
18
  if (config.proxyFallback)
17
19
  await proxy(req, response, Number(broker?.delayed && calcDelay()))
@@ -50,3 +52,26 @@ export async function dispatchMock(req, response) {
50
52
  sendInternalServerError(response, error)
51
53
  }
52
54
  }
55
+
56
+
57
+ async function applyPlugins(filePath, req, response) {
58
+ for (const [regex, plugin] of config.plugins)
59
+ if (regex.test(filePath))
60
+ return await plugin(filePath, req, response)
61
+ return {
62
+ mime: mimeFor(filePath),
63
+ body: readFileSync(filePath)
64
+ }
65
+ }
66
+
67
+
68
+ export async function jsToJsonPlugin(filePath, req, response) {
69
+ const jsExport = (await import(pathToFileURL(filePath) + '?' + Date.now())).default // date for cache busting
70
+ const body = typeof jsExport === 'function'
71
+ ? await jsExport(req, response)
72
+ : JSON.stringify(jsExport, null, 2)
73
+ return {
74
+ mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
75
+ body
76
+ }
77
+ }
package/src/Mockaton.js CHANGED
@@ -3,12 +3,13 @@ import { createServer } from 'node:http'
3
3
  import { API } from './ApiConstants.js'
4
4
  import { config, setup } from './config.js'
5
5
  import { dispatchMock } from './MockDispatcher.js'
6
+ import { dispatchStatic } from './StaticDispatcher.js'
7
+ import * as staticCollection from './staticCollection.js'
6
8
  import { BodyReaderError } from './utils/http-request.js'
7
9
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
10
  import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
11
+ import { watchMocksDir, watchStaticDir } from './Watcher.js'
9
12
  import { apiPatchRequests, apiGetRequests } from './Api.js'
10
- import { watchMocksDir, watchStaticMocksDir } from './Watcher.js'
11
- import { dispatchStatic, initStaticCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
12
13
  import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from './utils/http-response.js'
13
14
 
14
15
 
@@ -17,9 +18,9 @@ process.on('unhandledRejection', error => { throw error })
17
18
  export function Mockaton(options) {
18
19
  setup(options)
19
20
  mockBrokerCollection.init()
20
- initStaticCollection()
21
+ staticCollection.init()
21
22
  watchMocksDir()
22
- watchStaticMocksDir()
23
+ watchStaticDir()
23
24
 
24
25
  return createServer(onRequest).listen(config.port, config.host, function (error) {
25
26
  if (error) {
@@ -54,7 +55,7 @@ async function onRequest(req, response) {
54
55
  else if (method === 'GET' && apiGetRequests.has(url))
55
56
  apiGetRequests.get(url)(req, response)
56
57
 
57
- else if (method === 'GET' && findStaticBrokerByRoute(url))
58
+ else if (method === 'GET' && staticCollection.brokerByRoute(url))
58
59
  await dispatchStatic(req, response)
59
60
 
60
61
  else
@@ -1,65 +1,14 @@
1
+ import { join } from 'node:path'
1
2
  import { readFileSync } from 'node:fs'
2
- import { join, basename } from 'node:path'
3
3
 
4
4
  import { mimeFor } from './utils/mime.js'
5
- import { listFilesRecursively } from './utils/fs.js'
6
- import { config, isFileAllowed, calcDelay } from './config.js'
7
- import { sendPartialContent, sendNotFound } from './utils/http-response.js'
8
-
9
-
10
- class StaticBroker {
11
- constructor(route) {
12
- this.route = route
13
- this.delayed = false
14
- this.status = 200 // 200 or 404
15
- }
16
-
17
- setDelayed(value) { this.delayed = value }
18
- setStatus(value) { this.status = value }
19
- }
20
-
21
- /** @type {{ [route: string]: StaticBroker }} */
22
- let collection = {}
23
-
24
- export function initStaticCollection() {
25
- collection = {}
26
- listFilesRecursively(config.staticDir)
27
- .sort()
28
- .forEach(registerStaticMock)
29
- }
30
-
31
-
32
- /** @returns {boolean} registered */
33
- export function registerStaticMock(relativeFile) {
34
- if (!isFileAllowed(basename(relativeFile)))
35
- return false
36
-
37
- const route = '/' + relativeFile
38
- if (findStaticBrokerByRoute(route))
39
- return false
40
-
41
- collection[route] = new StaticBroker(route)
42
- return true
43
- }
44
-
45
-
46
- export function unregisterStaticMock(relativeFile) {
47
- delete collection['/' + relativeFile]
48
- }
49
-
50
-
51
- /** @returns {StaticBroker | undefined} */
52
- export function findStaticBrokerByRoute(route) {
53
- return collection[route] || collection[join(route, 'index.html')]
54
- }
55
-
56
- export function getStaticFilesCollection() {
57
- return collection
58
- }
5
+ import { brokerByRoute } from './staticCollection.js'
6
+ import { config, calcDelay } from './config.js'
7
+ import { sendNotFound, sendPartialContent } from './utils/http-response.js'
59
8
 
60
9
 
61
10
  export async function dispatchStatic(req, response) {
62
- const broker = findStaticBrokerByRoute(req.url)
11
+ const broker = brokerByRoute(req.url)
63
12
 
64
13
  setTimeout(async () => {
65
14
  if (!broker || broker.status === 404) { // TESTME
@@ -75,4 +24,3 @@ export async function dispatchStatic(req, response) {
75
24
  }
76
25
  }, Number(broker.delayed && calcDelay()))
77
26
  }
78
-
package/src/Watcher.js CHANGED
@@ -5,7 +5,7 @@ import { EventEmitter } from 'node:events'
5
5
  import { config } from './config.js'
6
6
  import { isFile } from './utils/fs.js'
7
7
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
- import { registerStaticMock, unregisterStaticMock } from './StaticDispatcher.js'
8
+ import { registerStaticMock, unregisterStaticMock } from './staticCollection.js'
9
9
 
10
10
 
11
11
  /** # AR = Add or Remove Mock Event */
@@ -40,7 +40,7 @@ export function watchMocksDir() {
40
40
  })
41
41
  }
42
42
 
43
- export function watchStaticMocksDir() {
43
+ export function watchStaticDir() {
44
44
  const dir = config.staticDir
45
45
  if (!dir)
46
46
  return
package/src/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { realpathSync } from 'node:fs'
2
2
  import { isDirectory } from './utils/fs.js'
3
3
  import { openInBrowser } from './utils/openInBrowser.js'
4
- import { jsToJsonPlugin } from './MockDispatcherPlugins.js'
4
+ import { jsToJsonPlugin } from './MockDispatcher.js'
5
5
  import { optional, is, validate } from './utils/validate.js'
6
6
  import { SUPPORTED_METHODS } from './utils/http-request.js'
7
7
  import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/http-cors.js'
@@ -23,6 +23,8 @@ import { parseFilename, filenameIsValid } from './Filename.js'
23
23
  */
24
24
  let collection = {}
25
25
 
26
+ export const all = () => collection
27
+
26
28
  export function init() {
27
29
  collection = {}
28
30
  cookie.init(config.cookies)
@@ -39,7 +41,7 @@ export function init() {
39
41
 
40
42
  /** @returns {boolean} registered */
41
43
  export function registerMock(file, isFromWatcher = false) {
42
- if (findBrokerByFilename(file)?.hasMock(file)
44
+ if (brokerByFilename(file)?.hasMock(file)
43
45
  || !isFileAllowed(basename(file)) // TESTME
44
46
  || !filenameIsValid(file))
45
47
  return false
@@ -62,7 +64,7 @@ export function registerMock(file, isFromWatcher = false) {
62
64
  }
63
65
 
64
66
  export function unregisterMock(file) {
65
- const broker = findBrokerByFilename(file)
67
+ const broker = brokerByFilename(file)
66
68
  if (!broker)
67
69
  return
68
70
  const isEmpty = broker.unregister(file)
@@ -74,11 +76,9 @@ export function unregisterMock(file) {
74
76
  }
75
77
  }
76
78
 
77
- export const getAll = () => collection
78
-
79
79
 
80
80
  /** @returns {MockBroker | undefined} */
81
- export function findBrokerByFilename(file) {
81
+ export function brokerByFilename(file) {
82
82
  const { method, urlMask } = parseFilename(file)
83
83
  if (collection[method])
84
84
  return collection[method][urlMask]
@@ -91,7 +91,7 @@ export function findBrokerByFilename(file) {
91
91
  * BTW, `urlMasks` always start with "/", so there’s no need to
92
92
  * worry about the primacy of array-like keys when iterating.
93
93
  @returns {MockBroker | undefined} */
94
- export function findBrokerByRoute(method, url) {
94
+ export function brokerByRoute(method, url) {
95
95
  if (!collection[method])
96
96
  return
97
97
  const brokers = Object.values(collection[method])
@@ -0,0 +1,56 @@
1
+ import { join, basename } from 'node:path'
2
+ import { listFilesRecursively } from './utils/fs.js'
3
+ import { config, isFileAllowed } from './config.js'
4
+
5
+
6
+ class StaticBroker {
7
+ constructor(route) {
8
+ this.route = route
9
+ this.delayed = false
10
+ this.status = 200 // 200 or 404
11
+ }
12
+
13
+ setDelayed(value) { this.delayed = value }
14
+ setStatus(value) { this.status = value }
15
+ }
16
+
17
+ /** @type {{ [route: string]: StaticBroker }} */
18
+ let collection = {}
19
+
20
+ export const all = () => collection
21
+
22
+ export function init() {
23
+ collection = {}
24
+ listFilesRecursively(config.staticDir)
25
+ .sort()
26
+ .forEach(registerStaticMock)
27
+ }
28
+
29
+
30
+ /** @returns {boolean} registered */
31
+ export function registerStaticMock(relativeFile) {
32
+ if (!isFileAllowed(basename(relativeFile)))
33
+ return false
34
+
35
+ const route = '/' + relativeFile
36
+ if (brokerByRoute(route))
37
+ return false
38
+
39
+ collection[route] = new StaticBroker(route)
40
+ return true
41
+ }
42
+
43
+
44
+ export function unregisterStaticMock(relativeFile) {
45
+ delete collection['/' + relativeFile]
46
+ }
47
+
48
+
49
+ /** @returns {StaticBroker | undefined} */
50
+ export function brokerByRoute(route) {
51
+ return collection[route] || collection[join(route, 'index.html')]
52
+ }
53
+
54
+
55
+
56
+
@@ -1,25 +0,0 @@
1
- import { readFileSync as read } from 'node:fs'
2
- import { mimeFor } from './utils/mime.js'
3
- import { config } from './config.js'
4
-
5
-
6
- export async function applyPlugins(filePath, req, response) {
7
- for (const [regex, plugin] of config.plugins)
8
- if (regex.test(filePath))
9
- return await plugin(filePath, req, response)
10
- return {
11
- mime: mimeFor(filePath),
12
- body: read(filePath)
13
- }
14
- }
15
-
16
- export async function jsToJsonPlugin(filePath, req, response) {
17
- const jsExport = (await import(filePath + '?' + Date.now())).default // date for cache busting
18
- const body = typeof jsExport === 'function'
19
- ? await jsExport(req, response)
20
- : JSON.stringify(jsExport, null, 2)
21
- return {
22
- mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
23
- body
24
- }
25
- }