mockaton 8.2.21 → 8.2.23

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  _Mockaton_ is a mock server for improving the frontend development and testing experience.
8
8
 
9
- With Mockaton, you don’t need to write code for wiring your mocks. Instead, it
9
+ With Mockaton you don’t need to write code for wiring your mocks. Instead, it
10
10
  scans a given directory for filenames following a convention similar to the
11
11
  URL paths. For example, the following file will be served on `/api/user/1234`
12
12
  ```
@@ -17,8 +17,8 @@ By the way, [this browser
17
17
  extension](https://github.com/ericfortis/devtools-ext-tar-http-requests)
18
18
  can create a TAR of your requests following that convention.
19
19
 
20
- Nonetheless, you don’t need to mock all your APIs. If you don’t add
21
- a mock for some route, Mockaton can request it from your backend.
20
+ Nonetheless, you don’t need to mock all your APIs. Mockaton
21
+ can request from your backend the routes you don’t have mocks for.
22
22
  That’s done with `config.proxyFallback = 'http://mybackend'`
23
23
 
24
24
  ## Multiple Mock Variants
@@ -37,7 +37,7 @@ which is handy for setting up tests (see **Commander API** below).
37
37
  <picture>
38
38
  <source media="(prefers-color-scheme: light)" srcset="./README-dashboard-light.png">
39
39
  <source media="(prefers-color-scheme: dark)" srcset="./README-dashboard-dark.png">
40
- <img alt="Mockaton Dashboard Demo" src="./README-dashboard-light.png" style="max-width: 860px">
40
+ <img alt="Mockaton Dashboard Demo" src="./README-dashboard-light.png">
41
41
  </picture>
42
42
 
43
43
 
@@ -93,7 +93,7 @@ The _Reset_ button is for registering newly added, removed, or renamed mocks.
93
93
  - Polled resources (for triggering their different states)
94
94
  - alerts
95
95
  - notifications
96
- - slow to build assets
96
+ - slow to build resources
97
97
 
98
98
  ### Time Travel
99
99
  If you commit the mocks to your repo, it’s straightforward to bisect bugs and
@@ -103,8 +103,8 @@ backends to old API contracts or databases.
103
103
  ### Deterministic Standalone Demo Server
104
104
  Perhaps you need to demo your app, but the ideal flow is too complex to
105
105
  simulate from the actual backend. In this case, compile your frontend app and
106
- put its built assets in `config.staticDir`. Then, from the Mockaton dashboard
107
- you can "Bulk Select" mocks to simulate the complete states you want to demo.
106
+ put its built assets in `config.staticDir`. Then, on the dashboard
107
+ "Bulk Select" mocks to simulate the complete states you want to demo.
108
108
  For bulk-selecting, you just need to add a comment to the mock
109
109
  filename, such as `(demo-part1)`, `(demo-part2)`.
110
110
 
@@ -113,7 +113,7 @@ filename, such as `(demo-part1)`, `(demo-part2)`.
113
113
  - Avoids spinning up and maintaining hefty backends when developing UIs.
114
114
  - For a deterministic, comprehensive, and consistent backend state. For example, having
115
115
  a collection with all the possible state variants helps for spotting inadvertent bugs.
116
- - Sometimes, frontend progress is blocked waiting for some backend API. Similarly,
116
+ - Sometimes frontend progress is blocked waiting for some backend API. Similarly,
117
117
  it’s often delayed due to missing data or inconvenient contracts. Therefore,
118
118
  many meetings can be saved by prototyping frontend features with mocks, and
119
119
  then showing those contracts to the backend team.
@@ -123,15 +123,6 @@ filename, such as `(demo-part1)`, `(demo-part2)`.
123
123
  - Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses.
124
124
  - [Mock Server Worker](https://mswjs.io)
125
125
 
126
- ---
127
- ## Default Mock for a Route
128
- You can add the comment: `(default)` to a filename.
129
- Otherwise, the first file in **alphabetical order** wins.
130
-
131
- ```
132
- api/user(default).GET.200.json
133
- ```
134
-
135
126
  ---
136
127
 
137
128
  ## You can write JSON mocks in JavaScript or TypeScript
@@ -227,6 +218,15 @@ api/foo<b>(my comment)</b>.GET.200.json
227
218
  api/foo.GET.200.json
228
219
  </pre>
229
220
 
221
+ ### Default Mock for a Route
222
+ You can add the comment: `(default)`.
223
+ Otherwise, the first file in **alphabetical order** wins.
224
+
225
+ <pre>
226
+ api/user<b>(default)</b>.GET.200.json
227
+ </pre>
228
+
229
+
230
230
  ### Query String Params
231
231
  The query string is ignored when routing to it. In other words, it’s only used for
232
232
  documenting the URL contract.
@@ -238,9 +238,8 @@ Speaking of which, on Windows filenames containing "?" are [not
238
238
  permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query string, it’s ignored anyway.
239
239
 
240
240
 
241
- ### Index-like route
242
- For instance, if you have `api/foo/bar` and
243
- `api/foo`, you have two options:
241
+ ### Index-like routes
242
+ If you have `api/foo` and `api/foo/bar`, you have two options:
244
243
 
245
244
  **Option A:**
246
245
  ```
@@ -272,8 +271,9 @@ Defaults to `0`, which means auto-assigned
272
271
  Defaults to `/(\.DS_Store|~)$/`
273
272
 
274
273
 
275
- ### `delay?: number` 🕓
276
- The delay is globally configurable, it defaults to `1200` (milliseconds).
274
+ ### `delay?: number`
275
+ Routes can individually be delayed with the 🕓 checkbox. On the other hand,
276
+ the amount is globally configurable. It defaults to `config.delay=1200` milliseconds.
277
277
 
278
278
 
279
279
  ### `proxyFallback?: string`
@@ -309,15 +309,13 @@ config.cookies = {
309
309
  })
310
310
  }
311
311
  ```
312
- The selected cookie is sent in every response in a `Set-Cookie` header.
312
+ The selected cookie, which is the first one by default, is sent in every
313
+ response in a `Set-Cookie` header. If you need to send more
314
+ cookies, inject them globally in `config.extraHeaders`.
313
315
 
314
316
  By the way, the `jwtCookie` helper has a hardcoded header and signature.
315
317
  In other words, it’s useful only if you care about the payload.
316
318
 
317
- If you need to send more than one cookie,
318
- inject them globally in `config.extraHeaders`.
319
-
320
-
321
319
 
322
320
  ### `extraHeaders?: string[]`
323
321
  Note it’s a unidimensional array. The header name goes at even indices.
@@ -337,6 +335,8 @@ config.extraMimes = {
337
335
  jpe: 'application/jpeg'
338
336
  }
339
337
  ```
338
+ These media types take precedence over the built-in
339
+ [utils/mime.js](src/utils/mime.js), so you can override them.
340
340
 
341
341
 
342
342
  ### `plugins?: [filenameTester: RegExp, plugin: Plugin][]`
@@ -350,9 +350,10 @@ type Plugin = (
350
350
  body: string | Uint8Array
351
351
  }>
352
352
  ```
353
- Plugins are for processing mocks before sending them.
353
+ Plugins are for processing mocks before sending them. If no regex matches the filename,
354
+ it fallbacks to reading the file from disk and computing the MIME from the extension.
354
355
 
355
- Note: don’t call `response.end()`
356
+ Note: don’t call `response.end()` on any plugin.
356
357
 
357
358
  <details>
358
359
  <summary><b> See Plugin Examples </b></summary>
@@ -366,7 +367,11 @@ import { readFileSync } from 'node:js'
366
367
  import { jsToJsonPlugin } from 'mockaton'
367
368
 
368
369
  config.plugins = [
369
- [/\.(js|ts)$/, jsToJsonPlugin], // Default
370
+
371
+ // Although `jsToJsonPlugin` is set by default, you need to add it to your list if you need it.
372
+ // In other words, your plugins array overwrites the default list. This way you can remove it.
373
+ [/\.(js|ts)$/, jsToJsonPlugin],
374
+
370
375
  [/\.yml$/, yamlToJsonPlugin],
371
376
  [/foo\.GET\.200\.txt$/, capitalizePlugin], // e.g. GET /api/foo would be capitalized
372
377
  ]
@@ -401,7 +406,7 @@ config.corsExposedHeaders = [] // headers you need to access in client-side JS
401
406
 
402
407
 
403
408
  ### `onReady?: (dashboardUrl: string) => void`
404
- This defaults to trying to open the dashboard in your default browser in macOS and
409
+ This defaults to trying to open the dashboard in your default browser on macOS and
405
410
  Windows. For a more cross-platform utility, you could `npm install open` and pass it.
406
411
  ```js
407
412
  import open from 'open'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "8.2.21",
5
+ "version": "8.2.23",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -39,6 +39,8 @@ export const apiPatchRequests = new Map([
39
39
  [API.cors, setCorsAllowed]
40
40
  ])
41
41
 
42
+ /* GET */
43
+
42
44
  function serveDashboard(_, response) { sendFile(response, join(import.meta.dirname, 'Dashboard.html')) }
43
45
  function serveDashboardAsset(req, response) { sendFile(response, join(import.meta.dirname, req.url)) }
44
46
 
@@ -48,13 +50,26 @@ function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection
48
50
  function getProxyFallback(_, response) { sendJSON(response, Config.proxyFallback) }
49
51
  function getIsCorsAllowed(_, response) { sendJSON(response, Config.corsAllowed) }
50
52
 
53
+ async function listStaticFiles(req, response) { // TESTME
54
+ try {
55
+ const files = Config.staticDir
56
+ ? listFilesRecursively(Config.staticDir).filter(f => !Config.ignore.test(f))
57
+ : []
58
+ sendJSON(response, files)
59
+ }
60
+ catch (error) {
61
+ sendBadRequest(response, error)
62
+ }
63
+ }
64
+
65
+
66
+ /* PATCH */
51
67
 
52
68
  function reinitialize(_, response) {
53
69
  mockBrokersCollection.init()
54
70
  sendOK(response)
55
71
  }
56
72
 
57
-
58
73
  async function selectCookie(req, response) {
59
74
  try {
60
75
  cookie.setCurrent(await parseJSON(req))
@@ -65,14 +80,12 @@ async function selectCookie(req, response) {
65
80
  }
66
81
  }
67
82
 
68
-
69
83
  async function selectMock(req, response) {
70
84
  try {
71
85
  const file = await parseJSON(req)
72
86
  const broker = mockBrokersCollection.getBrokerByFilename(file)
73
87
  if (!broker || !broker.mockExists(file))
74
88
  throw `Missing Mock: ${file}`
75
-
76
89
  broker.updateFile(file)
77
90
  sendOK(response)
78
91
  }
@@ -81,17 +94,14 @@ async function selectMock(req, response) {
81
94
  }
82
95
  }
83
96
 
84
-
85
97
  async function setRouteIsDelayed(req, response) {
86
98
  try {
87
99
  const body = await parseJSON(req)
88
100
  const broker = mockBrokersCollection.getBrokerForUrl(
89
101
  body[DF.routeMethod],
90
102
  body[DF.routeUrlMask])
91
-
92
103
  if (!broker)
93
104
  throw `Route does not exist: ${body[DF.routeUrlMask]} ${body[DF.routeUrlMask]}`
94
-
95
105
  broker.updateDelay(body[DF.delayed])
96
106
  sendOK(response)
97
107
  }
@@ -100,7 +110,6 @@ async function setRouteIsDelayed(req, response) {
100
110
  }
101
111
  }
102
112
 
103
-
104
113
  async function updateProxyFallback(req, response) {
105
114
  try {
106
115
  const fallback = await parseJSON(req)
@@ -116,7 +125,6 @@ async function updateProxyFallback(req, response) {
116
125
  }
117
126
  }
118
127
 
119
-
120
128
  async function bulkUpdateBrokersByCommentTag(req, response) {
121
129
  try {
122
130
  mockBrokersCollection.setMocksMatchingComment(await parseJSON(req))
@@ -127,7 +135,6 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
127
135
  }
128
136
  }
129
137
 
130
-
131
138
  async function setCorsAllowed(req, response) {
132
139
  try {
133
140
  Config.corsAllowed = await parseJSON(req)
@@ -138,16 +145,3 @@ async function setCorsAllowed(req, response) {
138
145
  }
139
146
  }
140
147
 
141
-
142
- // TESTME
143
- async function listStaticFiles(req, response) {
144
- try {
145
- const files = Config.staticDir
146
- ? listFilesRecursively(Config.staticDir).filter(f => !Config.ignore.test(f))
147
- : []
148
- sendJSON(response, files)
149
- }
150
- catch (error) {
151
- sendBadRequest(response, error)
152
- }
153
- }
package/src/Commander.js CHANGED
@@ -7,6 +7,16 @@ export class Commander {
7
7
  this.#addr = addr
8
8
  }
9
9
 
10
+ #get(api) {
11
+ return fetch(this.#addr + api)
12
+ }
13
+ #patch(api, body) {
14
+ return fetch(this.#addr + api, {
15
+ method: 'PATCH',
16
+ body: JSON.stringify(body)
17
+ })
18
+ }
19
+
10
20
  listMocks() {
11
21
  return this.#get(API.mocks)
12
22
  }
@@ -58,15 +68,4 @@ export class Commander {
58
68
  listStaticFiles() {
59
69
  return this.#get(API.static)
60
70
  }
61
-
62
-
63
- #get(api) {
64
- return fetch(this.#addr + api)
65
- }
66
- #patch(api, body) {
67
- return fetch(this.#addr + api, {
68
- method: 'PATCH',
69
- body: JSON.stringify(body)
70
- })
71
- }
72
71
  }
package/src/Dashboard.js CHANGED
@@ -423,7 +423,7 @@ function createElement(elem, props = null, ...children) {
423
423
  node[key] = value
424
424
  else
425
425
  node.setAttribute(key, value)
426
- node.append(...children.flat())
426
+ node.append(...children.flat().filter(a => a))
427
427
  return node
428
428
  }
429
429
 
@@ -431,7 +431,7 @@ function createSvgElement(tagName, props, ...children) {
431
431
  const elem = document.createElementNS('http://www.w3.org/2000/svg', tagName)
432
432
  for (const [key, value] of Object.entries(props))
433
433
  elem.setAttribute(key, value)
434
- elem.append(...children.flat())
434
+ elem.append(...children.flat().filter(a => a))
435
435
  return elem
436
436
  }
437
437
 
@@ -7,10 +7,6 @@ export async function applyPlugins(filePath, req, response) {
7
7
  for (const [regex, plugin] of Config.plugins) // TESTME capitalizePlugin
8
8
  if (regex.test(filePath))
9
9
  return await plugin(filePath, req, response)
10
- return defaultPlugin(filePath)
11
- }
12
-
13
- export function defaultPlugin(filePath) {
14
10
  return {
15
11
  mime: mimeFor(filePath),
16
12
  body: read(filePath)
package/src/utils/fs.js CHANGED
@@ -8,6 +8,9 @@ export const isDirectory = path => lstatSync(path, { throwIfNoEntry: false })?.i
8
8
  export const read = path => readFileSync(path)
9
9
 
10
10
  /** @returns {Array<string>} paths relative to `dir` */
11
- export const listFilesRecursively = dir => readdirSync(dir, { recursive: true })
12
- .map(f => f.replaceAll(path.sep, path.posix.sep)) // TESTME
13
- .filter(f => isFile(join(dir, f)))
11
+ export const listFilesRecursively = dir => {
12
+ const files = readdirSync(dir, { recursive: true }).filter(f => isFile(join(dir, f)))
13
+ return process.platform === 'win32'
14
+ ? files.map(f => f.replaceAll(path.sep, path.posix.sep)) // TESTME
15
+ : files
16
+ }