mockaton 7.6.1 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ _Reset_ button is for registering newly added, removed, or renamed mocks.
51
51
  ## Alternatives
52
52
  - Chrome DevTools allows for [overriding responses](https://developer.chrome.com/docs/devtools/overrides)
53
53
  - Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses.
54
- - Storybook’s [MSW](https://storybook.js.org/addons/msw-storybook-addon)
54
+ - [Mock Server Worker](https://mswjs.io)
55
55
 
56
56
  ### Caveats
57
57
  - Syncing the mocks, but the browser extension mentioned above helps.
@@ -84,11 +84,11 @@ interface Config {
84
84
  mocksDir: string
85
85
  ignore?: RegExp // Defaults to /(\.DS_Store|~)$/
86
86
 
87
- staticDir?: string // These files don’t use the mock-filename convention
87
+ staticDir?: string
88
88
 
89
89
  host?: string, // Defaults to 'localhost'
90
90
  port?: number // Defaults to 0, which means auto-assigned
91
- proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
91
+ proxyFallback?: string // Target for relaying routes without mocks
92
92
 
93
93
  delay?: number // Defaults to 1200 (ms)
94
94
  cookies?: { [label: string]: string }
@@ -123,7 +123,14 @@ api/user(default).GET.200.json
123
123
 
124
124
  ---
125
125
 
126
- ## You can write JSON mocks in JavaScript
126
+ ## You can write JSON mocks in JavaScript or TypeScript
127
+ For TypeScript mocks, install [tsx](https://www.npmjs.com/package/tsx) and load it.
128
+ ```shell
129
+ npm install --save-dev tsx
130
+ node --import=tsx my-mockaton.js
131
+ ```
132
+ ---
133
+
127
134
  An Object, Array, or String is sent as JSON.
128
135
 
129
136
  `api/foo.GET.200.js`
@@ -150,14 +157,20 @@ export default function optionalName(request, response) {
150
157
  }
151
158
  ```
152
159
 
153
- If you need to serve a static `.js` file, put it in `Config.staticDir`.
160
+ If you need to serve a static `.js` file, put it in your `Config.staticDir`.
154
161
 
155
162
 
156
163
  ## File Name Convention
157
-
164
+ This convention is only for files within your `Config.mocksDir`.
158
165
 
159
166
  ### Extension
160
- `.Method.ResponseStatusCode.FileExt`
167
+
168
+ The last 3 dots are reserved for the HTTP Method,
169
+ Response Status Code, and the File Extension.
170
+
171
+ ```
172
+ api/user.GET.200.json
173
+ ```
161
174
 
162
175
 
163
176
  ### Dynamic Parameters
@@ -204,32 +217,29 @@ api/foo/.GET.200.json
204
217
  ```
205
218
 
206
219
  ---
220
+ ## Config
207
221
 
208
- ## `Config.staticDir`
209
- Files under `Config.staticDir` don’t use the filename convention.
210
- Also, they take precedence over the `GET` mocks in `Config.mockDir`.
211
-
212
- For example, if you have two files for `GET /foo/bar.jpg`
213
- ```
214
- my-static-dir/foo/bar.jpg
215
- my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreacheable
216
- ```
217
-
218
-
219
- ## `Config.proxyFallback`
222
+ ### `proxyFallback`
220
223
  Lets you specify a target server for serving routes you don’t have mocks for.
224
+ For example, `Config.proxyFallback = 'http://example.com:8080'`
221
225
 
222
226
 
223
- ## `Config.delay` 🕓
227
+ ### `delay` 🕓
224
228
  The clock icon next to the mock selector is a checkbox for delaying a
225
229
  particular response. They are handy for testing spinners.
226
230
 
227
231
  The delay is globally configurable via `Config.delay = 1200` (milliseconds).
228
232
 
229
233
 
230
- ## `Config.staticDir`
231
- These files don’t use the mock filename convention. They take precedence
232
- over mocks. Also, they get served on the same address, so no CORS issues.
234
+ ### `staticDir`
235
+ Files under `Config.staticDir` don’t use the filename convention.
236
+ Also, they take precedence over the `GET` mocks in `Config.mockDir`.
237
+
238
+ For example, if you have two files for `GET /foo/bar.jpg`
239
+ ```
240
+ my-static-dir/foo/bar.jpg
241
+ my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreacheable
242
+ ```
233
243
 
234
244
  Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
235
245
 
@@ -237,7 +247,7 @@ Use Case 2: For a standalone demo server. For example,
237
247
  build your frontend bundle, and serve it from Mockaton.
238
248
 
239
249
 
240
- ## `Config.cookies`
250
+ ### `cookies`
241
251
  The selected cookie is sent in every response in the `Set-Cookie` header.
242
252
 
243
253
  The key is just a label used for selecting a particular cookie in the
@@ -261,7 +271,7 @@ Config.cookies = {
261
271
  }
262
272
  ```
263
273
 
264
- ## `Config.extraHeaders`
274
+ ### `extraHeaders`
265
275
  They are applied last, right before ending the response.
266
276
  In other words, they can overwrite the `Content-Type`. Note
267
277
  that it's an array and the header name goes in even indices.
@@ -274,14 +284,14 @@ Config.extraHeaders = [
274
284
  ]
275
285
  ```
276
286
 
277
- ## `Config.extraMimes`
287
+ ### `extraMimes`
278
288
  ```js
279
289
  Config.extraMimes = {
280
290
  jpg: 'application/jpeg'
281
291
  }
282
292
  ```
283
293
 
284
- ## `Config.corsAllowed`
294
+ ### `corsAllowed`
285
295
  ```js
286
296
  Config.corsAllowed = true
287
297
 
@@ -294,7 +304,7 @@ Config.corsMaxAge = 0 // seconds to cache the preflight req
294
304
  Config.corsExposedHeaders = [] // headers you need to access in client-side JS
295
305
  ```
296
306
 
297
- ## `Config.onReady`
307
+ ### `onReady`
298
308
  This is a callback `(dashboardAddress: string) => void`, which defaults to
299
309
  trying to open the dashboard in your default browser in macOS and Windows.
300
310
 
@@ -368,8 +378,13 @@ but `Config.proxyFallback` and `Config.corsAllowed` are not affected.
368
378
  await mockaton.reset()
369
379
  ```
370
380
 
381
+ <div style="display: flex; align-items: center; gap: 20px">
382
+ <img src="./sample-mocks/api/user/avatar.GET.200.png" width="170"/>
383
+ <p style="font-size: 18px">“Use Mockaton” - Albert Einstein</p>
384
+ </div>
385
+
371
386
 
372
387
  ## TODO
373
388
  - Refactor Tests
374
- - Dashboard. Indicate if some static it’s overriding a mock.
375
- - jsonc, json5
389
+ - Dashboard. Indicate if some file on `staticDir` is overriding a mock.
390
+ - jsonc, json5?
package/_usage_example.js CHANGED
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { join } from 'node:path'
4
2
  import { Mockaton, jwtCookie } from './index.js' // from 'mockaton'
5
3
 
package/package.json CHANGED
@@ -2,13 +2,17 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "7.6.1",
5
+ "version": "7.7.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
9
9
  "repository": "https://github.com/ericfortis/mockaton",
10
10
  "scripts": {
11
11
  "test": "node --test",
12
- "demo": "./_usage_example.js"
12
+ "demo": "node _usage_example.js",
13
+ "demo:ts": "node --import=tsx _usage_example.js"
14
+ },
15
+ "optionalDependencies": {
16
+ "tsx": "4.19.1"
13
17
  }
14
18
  }
@@ -0,0 +1,9 @@
1
+ import scores from './scores.GET.200'
2
+
3
+ export default [
4
+ ...scores,
5
+ { id: 103 },
6
+ { id: 104 },
7
+ { id: 105 },
8
+ ]
9
+
@@ -0,0 +1,6 @@
1
+ export default [
2
+ { id: 100 },
3
+ { id: 101 },
4
+ { id: 102 },
5
+ ]
6
+
package/src/Dashboard.js CHANGED
@@ -145,7 +145,6 @@ function ResetButton() {
145
145
  }
146
146
 
147
147
  function StaticFilesList({ staticFiles }) {
148
- console.log(staticFiles)
149
148
  if (!staticFiles.length)
150
149
  return null
151
150
  return (
package/src/Filename.js CHANGED
@@ -19,13 +19,16 @@ export function filenameIsValid(file) {
19
19
  console.error(error, file)
20
20
  return !error
21
21
  }
22
+
22
23
  function validateFilename(file) {
23
24
  const tokens = file.replace(reComments, '').split('.')
24
25
  if (tokens.length < 4)
25
26
  return 'Invalid Filename Convention'
27
+
26
28
  const { status, method } = parseFilename(file)
27
29
  if (!responseStatusIsValid(status))
28
30
  return `Invalid HTTP Response Status: "${status}"`
31
+
29
32
  if (!httpMethods.includes(method))
30
33
  return `Unrecognized HTTP Method: "${method}"`
31
34
  }
package/src/MockBroker.js CHANGED
@@ -64,9 +64,9 @@ export class MockBroker {
64
64
  }
65
65
 
66
66
  extractComments() {
67
- let comments = []
67
+ const comments = []
68
68
  for (const file of this.mocks)
69
- comments = comments.concat(extractComments(file))
69
+ comments.push(...extractComments(file))
70
70
  return comments
71
71
  }
72
72
 
@@ -25,33 +25,59 @@ export async function dispatchMock(req, response) {
25
25
  console.log(decodeURIComponent(req.url), ' → ', file)
26
26
  const filePath = join(Config.mocksDir, file)
27
27
 
28
- let mockBody
29
- if (file.endsWith('.js')) {
30
- response.setHeader('Content-Type', mimeFor('.json'))
31
- const jsExport = (await import(filePath + '?' + Date.now())).default // date for cache busting
32
- mockBody = typeof jsExport === 'function'
33
- ? await jsExport(req, response)
34
- : JSON.stringify(jsExport, null, 2)
35
- }
36
- else {
37
- response.setHeader('Content-Type', mimeFor(file))
38
- mockBody = broker.isTemp500
39
- ? ''
40
- : read(filePath)
41
- }
28
+ response.statusCode = status
42
29
 
43
30
  if (cookie.getCurrent())
44
31
  response.setHeader('Set-Cookie', cookie.getCurrent())
45
32
 
46
- response.writeHead(status, Config.extraHeaders)
33
+ for (let i = 0; i < Config.extraHeaders.length; i += 2)
34
+ response.setHeader(Config.extraHeaders[i], Config.extraHeaders[i + 1])
35
+
36
+ const [mime, mockBody] = broker.isTemp500
37
+ ? temp500Plugin(filePath, req, response)
38
+ : await preprocessPlugins(filePath, req, response)
39
+
40
+ response.setHeader('Content-Type', mime)
47
41
  setTimeout(() => response.end(mockBody), delay)
48
42
  }
49
43
  catch (error) {
50
44
  if (error instanceof JsonBodyParserError)
51
45
  sendBadRequest(response, error)
52
- else if (error.code === 'ENOENT')
53
- sendNotFound(response) // file has been deleted
46
+ else if (error.code === 'ENOENT') // mock-file has been deleted
47
+ sendNotFound(response)
48
+ else if (error.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
49
+ if (error.toString().includes('Unknown file extension ".ts"'))
50
+ console.log('Looks like you need a TypeScript compiler\n',
51
+ ' npm install tsx\n',
52
+ ' node --import=tsx my-mockaton.js\n')
53
+ sendInternalServerError(response, error)
54
+ }
54
55
  else
55
56
  sendInternalServerError(response, error)
56
57
  }
57
58
  }
59
+
60
+ // TODO expose to userland for custom plugins such yaml -> json
61
+ async function preprocessPlugins(filePath, req, response) {
62
+ if (filePath.endsWith('.js') || filePath.endsWith('.ts'))
63
+ return await jsPlugin(filePath, req, response)
64
+ return readPlugin(filePath, req, response)
65
+ }
66
+
67
+ function temp500Plugin(filePath) {
68
+ return [mimeFor(filePath), '']
69
+ }
70
+
71
+ async function jsPlugin(filePath, req, response) {
72
+ const jsExport = (await import(filePath + '?' + Date.now())).default // date for cache busting
73
+ const mockBody = typeof jsExport === 'function'
74
+ ? await jsExport(req, response)
75
+ : JSON.stringify(jsExport, null, 2)
76
+ const mime = response.getHeader('Content-Type') // jsFunc are allowed to set it
77
+ || mimeFor('.json')
78
+ return [mime, mockBody]
79
+ }
80
+
81
+ function readPlugin(filePath) {
82
+ return [mimeFor(filePath), read(filePath)]
83
+ }
@@ -24,7 +24,6 @@ const fixtureCustomMime = [
24
24
  'api/custom-mime.GET.200.my_custom_extension',
25
25
  'Custom Extension and MIME'
26
26
  ]
27
-
28
27
  const fixtureNonDefaultInName = [
29
28
  '/api/the-route',
30
29
  'api/the-route(default).GET.200.json',
@@ -35,7 +34,6 @@ const fixtureDefaultInName = [
35
34
  'api/the-route(default).GET.200.json',
36
35
  'default my route body content'
37
36
  ]
38
-
39
37
  const fixtureDelayed = [
40
38
  '/api/delayed',
41
39
  'api/delayed.GET.200.json',