mockaton 8.22.0 → 8.23.1

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
@@ -1,4 +1,4 @@
1
- <img src="src/logo.svg" alt="Mockaton Logo" width="210" style="margin-top: 30px"/>
1
+ <img src="src/Logo.svg" alt="Mockaton Logo" width="210" style="margin-top: 30px"/>
2
2
 
3
3
  ![NPM Version](https://img.shields.io/npm/v/mockaton)
4
4
  ![NPM Version](https://img.shields.io/npm/l/mockaton)
@@ -92,8 +92,9 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
92
92
  ## Scraping Mocks from your Backend
93
93
 
94
94
  ### Option 1: Browser Extension
95
- With the companion [browser-devtools extension](https://github.com/ericfortis/download-http-requests-browser-ext)
96
- you can download all the HTTP responses at once, and they
95
+ With the companion [browser-devtools
96
+ extension](https://github.com/ericfortis/download-http-requests-browser-ext)
97
+ you can download all the HTTP responses, and they
97
98
  get saved following Mockaton’s filename convention.
98
99
 
99
100
  ### Option 2: Fallback to Your Backend
@@ -117,260 +118,50 @@ They will be saved in your `config.mocksDir` following the filename convention.
117
118
 
118
119
 
119
120
  ## Basic Usage
120
- ```sh
121
- npm install mockaton --save-dev
122
- ```
123
-
124
- Create a `my-mockaton.js` file
125
- ```js
126
- import { resolve } from 'node:path'
127
- import { Mockaton } from 'mockaton'
128
-
129
-
130
- Mockaton({
131
- mocksDir: resolve('my-mocks-dir'), // must exist
132
- port: 2345
133
- }) // The Config section below documents more options
134
- ```
121
+ Mockaton is a Node.js program with no dependencies.
135
122
 
123
+ ### Create a Sample Mock
124
+ The default `--mocks-dir` is **mockaton-mocks** in the current working directory.
136
125
  ```sh
137
- node my-mockaton.js
126
+ mkdir -p mockaton-mocks/api/
127
+ echo "[1,2,3]" > mockaton-mocks/api/foo.GET.200.json
138
128
  ```
139
129
 
140
- <details>
141
- <summary>About TypeScript in Node < 23.6</summary>
142
- If you want to write mocks in TypeScript in a version older than Node 23.6:
143
-
144
- ```shell
145
- npm install tsx
146
- node --import=tsx my-mockaton.js
147
- ```
148
- </details>
149
-
150
-
151
- <br/>
152
-
153
- ## Demo App (Vite + React)
154
-
155
- ```sh
156
- git clone https://github.com/ericfortis/mockaton.git
157
- cd mockaton/demo-app-vite
158
- npm install
159
-
160
- npm run mockaton
161
- npm run start # in another terminal
162
- ```
163
-
164
- The demo app has a list of colors containing all of their possible states. For example,
165
- permutations for out-of-stock, new-arrival, and discontinued.
166
-
167
- <img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
168
-
169
- <br/>
170
- <br/>
171
-
172
-
173
- ## Use Cases
174
- ### Testing Backend or Frontend
175
- - Empty responses
176
- - Errors such as _Bad Request_ and _Internal Server Error_
177
- - Mocking third-party APIs
178
- - Polled resources (for triggering their different states)
179
- - alerts
180
- - notifications
181
- - slow to build resources
182
-
183
- ### Testing Frontend
184
- - Spinners by delaying responses
185
- - Setting up UI tests
186
-
187
- ### Demoing complex backend states
188
- Sometimes, the ideal flow you need is too difficult to reproduce from the actual backend.
189
- For this, you can **Bulk Select** mocks by comments to simulate the complete states
190
- you want. For example, by adding `(demo-part1)`, `(demo-part2)` to the filenames.
191
-
192
- Similarly, you can deploy a **Standalone Demo Server** by compiling the frontend app and
193
- putting its built assets in `config.staticDir`. And simulate the flow by Bulk Selecting mocks.
194
- The [aot-fetch-demo repo](https://github.com/ericfortis/aot-fetch-demo) has a working example.
195
-
196
-
197
- <br/>
198
-
199
-
200
- ## You can write JSON mocks in JavaScript or TypeScript
201
- For example, `api/foo.GET.200.js`
202
-
203
- **Option A:** An Object, Array, or String is sent as JSON.
204
-
205
- ```js
206
- export default { foo: 'bar' }
207
- ```
208
-
209
- **Option B:** Function
210
-
211
- Return a `string | Buffer | Uint8Array`, but don’t call `response.end()`
212
-
213
- ```js
214
- export default (request, response) =>
215
- JSON.stringify({ foo: 'bar' })
130
+ ### Install and Run
131
+ ```sh
132
+ npm install mockaton
133
+ npx mockaton --port 2345
216
134
  ```
217
135
 
218
- Think of these functions as HTTP handlers. For example,
219
- you can intercept requests to write to a database.
220
-
221
- <details>
222
- <summary><b>See Intercepting Requests Examples</b></summary>
223
-
224
- Imagine you have an initial list of colors, and
225
- you want to concatenate newly added colors.
226
-
227
- `api/colors.POST.201.js`
228
- ```js
229
- import { parseJSON } from 'mockaton'
230
-
231
-
232
- export default async function insertColor(request, response) {
233
- const color = await parseJSON(request)
234
- globalThis.newColorsDatabase ??= []
235
- globalThis.newColorsDatabase.push(color)
236
-
237
- // These two lines are not needed but you can change their values
238
- // response.statusCode = 201 // default derived from filename
239
- // response.setHeader('Content-Type', 'application/json') // unconditional default
240
-
241
- return JSON.stringify({ msg: 'CREATED' })
242
- }
243
- ```
136
+ ## CLI Options
244
137
 
245
- `api/colors.GET.200.js`
246
- ```js
247
- import colorsFixture from './colors.json' with { type: 'json' }
138
+ ```txt
139
+ -c, --config <file> (default: ./mockaton.config.js)
248
140
 
141
+ -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
142
+ -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
249
143
 
250
- export default function listColors() {
251
- return JSON.stringify([
252
- ...colorsFixture,
253
- ...(globalThis.newColorsDatabase || [])
254
- ])
255
- }
144
+ -H, --host <host> (default: 127.0.0.1)
145
+ -p, --port <port> (default: 0) which means auto-assigned
256
146
  ```
257
- </details>
258
-
259
- <br/>
260
-
261
- **What if I need to serve a static .js or .ts?**
262
147
 
263
- **Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
264
148
 
265
- **Option B:** Read it and return it. For example:
149
+ ## mockaton.config.js
150
+ Optionally, use a `mockaton.config.js` file
266
151
  ```js
267
- export default function (_, response) {
268
- response.setHeader('Content-Type', 'application/javascript')
269
- return readFileSync('./some-dir/foo.js', 'utf8')
270
- }
271
- ```
152
+ import { defineConfig } from 'mockaton'
272
153
 
273
- <br/>
274
-
275
- ## Mock Filename Convention
276
-
277
- ### Extension
278
-
279
- The last three dots are reserved for the HTTP Method,
280
- Response Status Code, and File Extension.
281
-
282
- ```
283
- api/user.GET.200.json
154
+ export default defineConfig({
155
+ port: 2345,
156
+ mocksDir: 'my-mocks-dir',
157
+ delayJitter: 0.5,
158
+ //
159
+ })
284
160
  ```
285
161
 
286
- You can also use `.empty` or `.unknown` if you don’t
287
- want a `Content-Type` header in the response.
288
-
289
162
  <details>
290
- <summary>Supported Methods</summary>
291
- <p>From <code>require('node:http').METHODS</code></p>
292
- <p>
293
- ACL, BIND, CHECKOUT,
294
- CONNECT, COPY, DELETE,
295
- GET, HEAD, LINK,
296
- LOCK, M-SEARCH, MERGE,
297
- MKACTIVITY, MKCALENDAR, MKCOL,
298
- MOVE, NOTIFY, OPTIONS,
299
- PATCH, POST, PROPFIND,
300
- PROPPATCH, PURGE, PUT,
301
- QUERY, REBIND, REPORT,
302
- SEARCH, SOURCE, SUBSCRIBE,
303
- TRACE, UNBIND, UNLINK,
304
- UNLOCK, UNSUBSCRIBE
305
- </p>
306
- </details>
307
-
308
- <br/>
309
-
310
- ### Dynamic parameters
311
- Anything within square brackets is always matched.
312
-
313
- For example, for <a href="#">/api/company/<b>123</b>/user/<b>789</b></a>,
314
- the filename could be:
315
-
316
- <pre><code>api/company/<b>[id]</b>/user/<b>[uid]</b>.GET.200.json</code></pre>
317
-
318
- <br/>
163
+ <summary><b>See all config options</b></summary>
319
164
 
320
- ### Comments
321
- Comments are anything within parentheses, including them.
322
- They are ignored for routing purposes, so they have no effect
323
- on the URL mask. For example, these two are for `/api/foo`
324
- <pre>
325
- api/foo<b>(my comment)</b>.GET.200.json
326
- api/foo.GET.200.json
327
- </pre>
328
-
329
- A filename can have many comments.
330
-
331
- <br/>
332
-
333
- ### Default mock for a route
334
- You can add the comment: `(default)`.
335
- Otherwise, the first file in **alphabetical order** wins.
336
-
337
- <pre>
338
- api/user<b>(default)</b>.GET.200.json
339
- </pre>
340
-
341
- <br/>
342
-
343
- ### Query string params
344
- The query string is ignored for routing purposes. In other words, it’s only used for
345
- documenting the URL contract.
346
- <pre>
347
- api/video<b>?limit=[limit]</b>.GET.200.json
348
- </pre>
349
-
350
- On Windows, filenames containing "?" are [not
351
- permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query
352
- string it’s ignored anyway.
353
-
354
- <br/>
355
-
356
- ### Index-like routes
357
- If you have <a href="#">api/foo</a> and <a href="#">api/foo/bar</a>, you have two options:
358
-
359
- **Option A.** Standard naming:
360
- ```
361
- api/foo.GET.200.json
362
- api/foo/bar.GET.200.json
363
- ```
364
-
365
- **Option B.** Omit the URL on the filename:
366
- ```text
367
- api/foo/.GET.200.json
368
- api/foo/bar.GET.200.json
369
- ```
370
-
371
- <br/>
372
-
373
- ## Config
374
165
  ### `mocksDir: string`
375
166
  This is the only required field. The directory must exist.
376
167
 
@@ -598,6 +389,235 @@ config.onReady = () => {}
598
389
 
599
390
  At any rate, you can trigger any command besides opening a browser.
600
391
 
392
+
393
+
394
+
395
+ </details>
396
+
397
+
398
+
399
+ <br/>
400
+
401
+ ## Demo App (Vite + React)
402
+
403
+ ```sh
404
+ git clone https://github.com/ericfortis/mockaton.git
405
+ cd mockaton/demo-app-vite
406
+ npm install
407
+
408
+ npm run mockaton
409
+ npm run start # in another terminal
410
+ ```
411
+
412
+ The demo app has a list of colors containing all of their possible states. For example,
413
+ permutations for out-of-stock, new-arrival, and discontinued.
414
+
415
+ <img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
416
+
417
+ <br/>
418
+ <br/>
419
+
420
+
421
+ ## Use Cases
422
+ ### Testing Backend or Frontend
423
+ - Empty responses
424
+ - Errors such as _Bad Request_ and _Internal Server Error_
425
+ - Mocking third-party APIs
426
+ - Polled resources (for triggering their different states)
427
+ - alerts
428
+ - notifications
429
+ - slow to build resources
430
+
431
+ ### Testing Frontend
432
+ - Spinners by delaying responses
433
+ - Setting up UI tests
434
+
435
+ ### Demoing complex backend states
436
+ Sometimes, the ideal flow you need is too difficult to reproduce from the actual backend.
437
+ For this, you can **Bulk Select** mocks by comments to simulate the complete states
438
+ you want. For example, by adding `(demo-part1)`, `(demo-part2)` to the filenames.
439
+
440
+ Similarly, you can deploy a **Standalone Demo Server** by compiling the frontend app and
441
+ putting its built assets in `config.staticDir`. And simulate the flow by Bulk Selecting mocks.
442
+ The [aot-fetch-demo repo](https://github.com/ericfortis/aot-fetch-demo) has a working example.
443
+
444
+
445
+ <br/>
446
+
447
+
448
+ ## You can write JSON mocks in JavaScript or TypeScript
449
+ For example, `api/foo.GET.200.js`
450
+
451
+ **Option A:** An Object, Array, or String is sent as JSON.
452
+
453
+ ```js
454
+ export default { foo: 'bar' }
455
+ ```
456
+
457
+ **Option B:** Function
458
+
459
+ Return a `string | Buffer | Uint8Array`, but don’t call `response.end()`
460
+
461
+ ```js
462
+ export default (request, response) =>
463
+ JSON.stringify({ foo: 'bar' })
464
+ ```
465
+
466
+ Think of these functions as HTTP handlers. For example,
467
+ you can intercept requests to write to a database.
468
+
469
+ <details>
470
+ <summary><b>See Intercepting Requests Examples</b></summary>
471
+
472
+ Imagine you have an initial list of colors, and
473
+ you want to concatenate newly added colors.
474
+
475
+ `api/colors.POST.201.js`
476
+ ```js
477
+ import { parseJSON } from 'mockaton'
478
+
479
+
480
+ export default async function insertColor(request, response) {
481
+ const color = await parseJSON(request)
482
+ globalThis.newColorsDatabase ??= []
483
+ globalThis.newColorsDatabase.push(color)
484
+
485
+ // These two lines are not needed but you can change their values
486
+ // response.statusCode = 201 // default derived from filename
487
+ // response.setHeader('Content-Type', 'application/json') // unconditional default
488
+
489
+ return JSON.stringify({ msg: 'CREATED' })
490
+ }
491
+ ```
492
+
493
+ `api/colors.GET.200.js`
494
+ ```js
495
+ import colorsFixture from './colors.json' with { type: 'json' }
496
+
497
+
498
+ export default function listColors() {
499
+ return JSON.stringify([
500
+ ...colorsFixture,
501
+ ...(globalThis.newColorsDatabase || [])
502
+ ])
503
+ }
504
+ ```
505
+ </details>
506
+
507
+ <br/>
508
+
509
+ **What if I need to serve a static .js or .ts?**
510
+
511
+ **Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
512
+
513
+ **Option B:** Read it and return it. For example:
514
+ ```js
515
+ export default function (_, response) {
516
+ response.setHeader('Content-Type', 'application/javascript')
517
+ return readFileSync('./some-dir/foo.js', 'utf8')
518
+ }
519
+ ```
520
+
521
+ <br/>
522
+
523
+ ## Mock Filename Convention
524
+
525
+ ### Extension
526
+
527
+ The last three dots are reserved for the HTTP Method,
528
+ Response Status Code, and File Extension.
529
+
530
+ ```
531
+ api/user.GET.200.json
532
+ ```
533
+
534
+ You can also use `.empty` or `.unknown` if you don’t
535
+ want a `Content-Type` header in the response.
536
+
537
+ <details>
538
+ <summary>Supported Methods</summary>
539
+ <p>From <code>require('node:http').METHODS</code></p>
540
+ <p>
541
+ ACL, BIND, CHECKOUT,
542
+ CONNECT, COPY, DELETE,
543
+ GET, HEAD, LINK,
544
+ LOCK, M-SEARCH, MERGE,
545
+ MKACTIVITY, MKCALENDAR, MKCOL,
546
+ MOVE, NOTIFY, OPTIONS,
547
+ PATCH, POST, PROPFIND,
548
+ PROPPATCH, PURGE, PUT,
549
+ QUERY, REBIND, REPORT,
550
+ SEARCH, SOURCE, SUBSCRIBE,
551
+ TRACE, UNBIND, UNLINK,
552
+ UNLOCK, UNSUBSCRIBE
553
+ </p>
554
+ </details>
555
+
556
+ <br/>
557
+
558
+ ### Dynamic parameters
559
+ Anything within square brackets is always matched.
560
+
561
+ For example, for <a href="#">/api/company/<b>123</b>/user/<b>789</b></a>,
562
+ the filename could be:
563
+
564
+ <pre><code>api/company/<b>[id]</b>/user/<b>[uid]</b>.GET.200.json</code></pre>
565
+
566
+ <br/>
567
+
568
+ ### Comments
569
+ Comments are anything within parentheses, including them.
570
+ They are ignored for routing purposes, so they have no effect
571
+ on the URL mask. For example, these two are for `/api/foo`
572
+ <pre>
573
+ api/foo<b>(my comment)</b>.GET.200.json
574
+ api/foo.GET.200.json
575
+ </pre>
576
+
577
+ A filename can have many comments.
578
+
579
+ <br/>
580
+
581
+ ### Default mock for a route
582
+ You can add the comment: `(default)`.
583
+ Otherwise, the first file in **alphabetical order** wins.
584
+
585
+ <pre>
586
+ api/user<b>(default)</b>.GET.200.json
587
+ </pre>
588
+
589
+ <br/>
590
+
591
+ ### Query string params
592
+ The query string is ignored for routing purposes. In other words, it’s only used for
593
+ documenting the URL contract.
594
+ <pre>
595
+ api/video<b>?limit=[limit]</b>.GET.200.json
596
+ </pre>
597
+
598
+ On Windows, filenames containing "?" are [not
599
+ permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query
600
+ string it’s ignored anyway.
601
+
602
+ <br/>
603
+
604
+ ### Index-like routes
605
+ If you have <a href="#">api/foo</a> and <a href="#">api/foo/bar</a>, you have two options:
606
+
607
+ **Option A.** Standard naming:
608
+ ```
609
+ api/foo.GET.200.json
610
+ api/foo/bar.GET.200.json
611
+ ```
612
+
613
+ **Option B.** Omit the URL on the filename:
614
+ ```text
615
+ api/foo/.GET.200.json
616
+ api/foo/bar.GET.200.json
617
+ ```
618
+
619
+ <br/>
620
+
601
621
  <br/>
602
622
 
603
623
  ## Commander API
@@ -694,4 +714,4 @@ hijack the client (e.g., `fetch`) in Node.js and browsers.
694
714
 
695
715
  ---
696
716
 
697
- ![](./fixtures-mocks/api/user/avatar.GET.200.png)
717
+ ![](mockaton-mocks/api/user/avatar.GET.200.png)
package/index.d.ts CHANGED
@@ -10,7 +10,7 @@ type Plugin = (
10
10
  }>
11
11
 
12
12
  interface Config {
13
- mocksDir: string
13
+ mocksDir?: string
14
14
  staticDir?: string
15
15
  ignore?: RegExp
16
16
 
@@ -44,7 +44,8 @@ interface Config {
44
44
  }
45
45
 
46
46
 
47
- export function Mockaton(options: Config): Server
47
+ export function Mockaton(options: Partial<Config>): Server
48
+ export function defineConfig(options: Partial<Config>): Config
48
49
 
49
50
  export const jsToJsonPlugin: Plugin
50
51
 
package/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export { Mockaton } from './src/Mockaton.js'
2
- export { Commander } from './src/Commander.js'
2
+ export { Commander } from './src/ApiCommander.js'
3
3
 
4
4
  export { jwtCookie } from './src/utils/jwt.js'
5
- export { parseJSON } from './src/utils/http-request.js'
5
+ export { openInBrowser } from './src/utils/openInBrowser.js'
6
6
  export { jsToJsonPlugin } from './src/MockDispatcher.js'
7
+ export { parseJSON, SUPPORTED_METHODS } from './src/utils/http-request.js'
8
+
9
+ /** @param {Partial<Config>} opts */
10
+ export const defineConfig = opts => opts
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.22.0",
5
+ "version": "8.23.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -14,10 +14,13 @@
14
14
  "api",
15
15
  "testing"
16
16
  ],
17
+ "bin": {
18
+ "mockaton": "src/cli.js"
19
+ },
17
20
  "scripts": {
18
21
  "test": "node --test \"src/**/*.test.js\"",
19
22
  "coverage": "node --test --test-reporter=lcov --test-reporter-destination=.coverage/lcov.info --experimental-test-coverage \"src/**/*.test.js\"",
20
- "start": "node --watch dev-mockaton.js",
23
+ "start": "node --watch src/cli.js -c dev.config.js",
21
24
  "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
22
25
  "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
23
26
  },
package/src/Api.js CHANGED
@@ -16,11 +16,11 @@ import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/ht
16
16
 
17
17
  const dashboardAssets = [
18
18
  '/ApiConstants.js',
19
- '/Commander.js',
19
+ '/ApiCommander.js',
20
20
  '/Dashboard.css',
21
21
  '/Dashboard.js',
22
22
  '/Filename.js',
23
- '/logo.svg'
23
+ '/Logo.svg'
24
24
  ]
25
25
 
26
26
  export const apiGetRequests = new Map([
package/src/Dashboard.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_500_COMMENT, HEADER_FOR_502 } from './ApiConstants.js'
2
2
  import { parseFilename } from './Filename.js'
3
- import { Commander } from './Commander.js'
3
+ import { Commander } from './ApiCommander.js'
4
4
 
5
5
 
6
6
  const Strings = {
@@ -144,7 +144,7 @@ function Header() {
144
144
  r('header', null,
145
145
  r('img', {
146
146
  alt: Strings.title,
147
- src: 'mockaton/logo.svg',
147
+ src: 'mockaton/Logo.svg',
148
148
  width: 160
149
149
  }),
150
150
  r('div', null,
@@ -885,7 +885,7 @@ function syntaxJSON(json) {
885
885
  text(json.slice(lastIndex))
886
886
  return frag
887
887
  }
888
- syntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:]+)|\S+/g
888
+ syntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:\s]+)|\S+/g
889
889
  // Capture group order: [string, optional colon, punc]
890
890
 
891
891
 
package/src/Mockaton.js CHANGED
@@ -16,15 +16,24 @@ import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from
16
16
  process.on('unhandledRejection', error => { throw error })
17
17
 
18
18
  export function Mockaton(options) {
19
- setup(options)
19
+ const error = setup(options)
20
+ if (error) {
21
+ console.error(error)
22
+ process.exitCode = 1
23
+ return
24
+ }
25
+
20
26
  mockBrokerCollection.init()
21
27
  staticCollection.init()
22
28
  watchMocksDir()
23
29
  watchStaticDir()
24
30
 
25
- return createServer(onRequest).listen(config.port, config.host, function (error) {
31
+ const server = createServer(onRequest)
32
+
33
+ server.listen(config.port, config.host, function (error) {
26
34
  if (error) {
27
35
  console.error(error)
36
+ process.exit(1)
28
37
  return
29
38
  }
30
39
  const { address, port } = this.address()
@@ -33,8 +42,16 @@ export function Mockaton(options) {
33
42
  console.log('Dashboard', url + API.dashboard)
34
43
  config.onReady(url + API.dashboard)
35
44
  })
45
+
46
+ server.on('error', error => {
47
+ console.error(error.message)
48
+ process.exit(1)
49
+ })
50
+
51
+ return server
36
52
  }
37
53
 
54
+
38
55
  async function onRequest(req, response) {
39
56
  response.on('error', console.error)
40
57
 
package/src/cli.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { join } from 'node:path'
4
+ import { parseArgs } from 'node:util'
5
+
6
+ import { isFile } from './utils/fs.js'
7
+ import { Mockaton } from '../index.js'
8
+ import pkgJSON from '../package.json' with { type: 'json' }
9
+
10
+
11
+ const args = parseArgs({
12
+ options: {
13
+ config: { short: 'c', type: 'string' },
14
+
15
+ port: { short: 'p', type: 'string' },
16
+ host: { short: 'H', type: 'string' },
17
+
18
+ 'mocks-dir': { short: 'm', type: 'string' },
19
+ 'static-dir': { short: 's', type: 'string' },
20
+
21
+ help: { short: 'h', type: 'boolean' },
22
+ version: { short: 'v', type: 'boolean' }
23
+ }
24
+ }).values
25
+
26
+
27
+ if (args.version)
28
+ console.log(pkgJSON.version)
29
+
30
+ else if (args.help)
31
+ console.log(`
32
+ Usage: mockaton [options]
33
+
34
+ Options:
35
+ -c, --config <file> (default: ./mockaton.config.js)
36
+
37
+ -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
38
+ -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
39
+
40
+ -H, --host <host> (default: 127.0.0.1)
41
+ -p, --port <port> (default: 0) which means auto-assigned
42
+
43
+ -h, --help Show this help
44
+ -v, --version Show version
45
+
46
+ Notes:
47
+ * mockaton.config.js supports more options, see:
48
+ https://github.com/ericfortis/mockaton?tab=readme-ov-file#config
49
+ * CLI options override their mockaton.config.js counterparts`)
50
+
51
+ else if (args.config && !isFile(args.config)) {
52
+ console.error(`Invalid config file: ${args.config}`)
53
+ process.exitCode = 1
54
+ }
55
+ else {
56
+ const userConf = join(process.cwd(), args.config ?? 'mockaton.config.js')
57
+ const opts = isFile(userConf)
58
+ ? (await import(userConf)).default ?? {}
59
+ : {}
60
+
61
+ if (args.host) opts.host = args.host
62
+ if (args.port) opts.port = Number(args.port)
63
+
64
+ if (args['mocks-dir']) opts.mocksDir = args['mocks-dir']
65
+ if (args['static-dir']) opts.staticDir = args['static-dir']
66
+
67
+ Mockaton(opts)
68
+ }
package/src/config.js CHANGED
@@ -1,4 +1,5 @@
1
- import { realpathSync } from 'node:fs'
1
+ import { join, isAbsolute } from 'node:path'
2
+
2
3
  import { isDirectory } from './utils/fs.js'
3
4
  import { openInBrowser } from './utils/openInBrowser.js'
4
5
  import { jsToJsonPlugin } from './MockDispatcher.js'
@@ -14,8 +15,8 @@ import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/
14
15
  * ]
15
16
  * }} */
16
17
  const schema = {
17
- mocksDir: ['', isDirectory],
18
- staticDir: ['', optional(isDirectory)],
18
+ mocksDir: [join(process.cwd(), 'mockaton-mocks'), isDirectory],
19
+ staticDir: [join(process.cwd(), 'mockaton-static-mocks'), optional(isDirectory)],
19
20
  ignore: [/(\.DS_Store|~)$/, is(RegExp)],
20
21
 
21
22
  host: ['127.0.0.1', is(String)],
@@ -65,18 +66,30 @@ export const config = Object.seal(defaults)
65
66
  export const ConfigValidator = Object.freeze(validators)
66
67
 
67
68
 
69
+ /** @param {Partial<Config>} options */
70
+ export function setup(options) {
71
+ if (options.mocksDir && !isAbsolute(options.mocksDir))
72
+ options.mocksDir = join(process.cwd(), options.mocksDir)
73
+
74
+ if (options.staticDir && !isAbsolute(options.staticDir))
75
+ options.staticDir = join(process.cwd(), options.staticDir)
76
+
77
+ if (!options.staticDir && !isDirectory(defaults.staticDir))
78
+ options.staticDir = ''
79
+
80
+ try {
81
+ Object.assign(config, options)
82
+ validate(config, ConfigValidator)
83
+ }
84
+ catch (err) {
85
+ return err.message
86
+ }
87
+ }
88
+
89
+
68
90
  export const isFileAllowed = f => !config.ignore.test(f)
69
91
 
70
92
  export const calcDelay = () => config.delayJitter
71
93
  ? config.delay * (1 + Math.random() * config.delayJitter)
72
94
  : config.delay
73
95
 
74
-
75
- export function setup(options) {
76
- Object.assign(config, options)
77
- validate(config, ConfigValidator)
78
-
79
- config.mocksDir = realpathSync(config.mocksDir)
80
- if (config.staticDir)
81
- config.staticDir = realpathSync(config.staticDir)
82
- }
File without changes
File without changes