mockaton 8.15.0 → 8.16.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
@@ -6,7 +6,7 @@
6
6
  [![CodeQL](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
7
7
 
8
8
  An HTTP mock server for simulating APIs with minimal setup
9
- — ideal for triggering difficult to reproduce backend states.
9
+ — ideal for testing difficult to reproduce backend states.
10
10
 
11
11
 
12
12
  ## Overview
@@ -96,7 +96,7 @@ Mockaton({
96
96
  node my-mockaton.js
97
97
  ```
98
98
 
99
- ### Node < 23.6 + TypeScript
99
+ ### TypeScript with Node < 23.6
100
100
  If you want to write mocks in TypeScript in a version older than Node 23.6:
101
101
  ```shell
102
102
  npm install tsx
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.15.0",
5
+ "version": "8.16.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Dashboard.css CHANGED
@@ -257,9 +257,9 @@ table {
257
257
 
258
258
  .PayloadViewer {
259
259
  position: sticky;
260
- margin-top: 62px;
261
260
  top: 62px;
262
261
  width: 50%;
262
+ margin-top: 62px;
263
263
  margin-left: 20px;
264
264
 
265
265
  h2 {
@@ -434,10 +434,11 @@ table {
434
434
  }
435
435
  }
436
436
 
437
- .InternalServerErrorToggler {
437
+ .InternalServerErrorToggler,
438
+ .NotFoundToggler {
438
439
  display: flex;
439
- margin-left: 8px;
440
440
  margin-right: 12px;
441
+ margin-left: 8px;
441
442
  cursor: pointer;
442
443
 
443
444
  > input {
@@ -472,32 +473,33 @@ table {
472
473
  }
473
474
  }
474
475
 
475
- .ProgressBar {
476
- position: relative;
477
- width: 100%;
478
- height: 2px;
479
- background: var(--colorComboBoxHeaderBackground);
480
-
481
- div {
482
- position: absolute;
483
- top: 0;
484
- left: 0;
485
- height: 100%;
486
- background: var(--colorAccent);
487
- animation-name: _kfProgress;
488
- /*animation-duration: It's in JavaScript */
476
+ .SpinnerClock {
477
+ display: flex;
478
+ width: 36px;
479
+ height: 36px;
480
+ margin-top: 92px;
481
+ justify-self: center;
482
+ fill: none;
483
+ stroke: var(--colorSecondaryAction);
484
+ stroke-width: 2px;
485
+
486
+ .HourHand,
487
+ .MinuteHand {
488
+ stroke-linecap: round;
489
+ transform-origin: center;
490
+ animation: _kfRotate 9s linear infinite
491
+ }
492
+ .MinuteHand {
493
+ animation-duration: .75s;
489
494
  }
490
-
491
495
  }
492
- @keyframes _kfProgress {
493
- 0% {
494
- width: 0;
495
- }
496
+ @keyframes _kfRotate {
496
497
  100% {
497
- width: 100%;
498
+ transform: rotate(360deg)
498
499
  }
499
500
  }
500
501
 
502
+
501
503
  .StaticFilesList {
502
504
  margin-top: 8px;
503
505
 
package/src/Dashboard.js CHANGED
@@ -40,19 +40,22 @@ const CSS = {
40
40
  DelayToggler: 'DelayToggler',
41
41
  FallbackBackend: 'FallbackBackend',
42
42
  Field: 'Field',
43
+ GlobalDelayField: 'GlobalDelayField',
43
44
  Header: 'Header',
44
45
  InternalServerErrorToggler: 'InternalServerErrorToggler',
46
+ Main: 'Main',
45
47
  MockList: 'MockList',
46
48
  MockSelector: 'MockSelector',
49
+ NotFoundToggler: 'NotFoundToggler',
47
50
  PayloadViewer: 'PayloadViewer',
48
51
  PreviewLink: 'PreviewLink',
49
- ProgressBar: 'ProgressBar',
50
52
  ProxyToggler: 'ProxyToggler',
51
53
  ResetButton: 'ResetButton',
52
- GlobalDelayField: 'GlobalDelayField',
53
54
  SaveProxiedCheckbox: 'SaveProxiedCheckbox',
55
+ SpinnerClock: 'SpinnerClock',
56
+ SpinnerClockHourHand: 'HourHand',
57
+ SpinnerClockMinuteHand: 'MinuteHand',
54
58
  StaticFilesList: 'StaticFilesList',
55
- Main: 'Main',
56
59
 
57
60
  red: 'red',
58
61
  empty: 'empty',
@@ -65,7 +68,7 @@ const r = createElement
65
68
  const s = createSvgElement
66
69
  const mockaton = new Commander(window.location.origin)
67
70
 
68
- const PROGRESS_BAR_DELAY = 180
71
+ const SPINNER_DELAY = 180
69
72
  let globalDelay = 1200
70
73
 
71
74
 
@@ -449,7 +452,7 @@ function NotFoundToggler({ broker }) {
449
452
  }
450
453
  return (
451
454
  r('label', {
452
- className: CSS.InternalServerErrorToggler, // TODO rename
455
+ className: CSS.NotFoundToggler,
453
456
  title: Strings.not_found
454
457
  },
455
458
  r('input', {
@@ -492,10 +495,13 @@ function PayloadViewer() {
492
495
  r('code', { ref: payloadViewerRef }, Strings.click_link_to_preview))))
493
496
  }
494
497
 
495
- function PayloadViewerProgressBar() {
498
+ function PayloadViewerSpinner() {
496
499
  return (
497
- r('div', { className: CSS.ProgressBar },
498
- r('div', { style: { animationDuration: globalDelay - PROGRESS_BAR_DELAY + 'ms' } })))
500
+ s('svg', { viewBox: '0 0 24 24', class: CSS.SpinnerClock },
501
+ s('circle', { cx: 12, cy: 12, r: 10 }),
502
+ s('line', { class: CSS.SpinnerClockHourHand, x1: 12, y1: 12, x2: 12, y2: 8 }),
503
+ s('line', { class: CSS.SpinnerClockMinuteHand, x1: 12, y1: 12, x2: 12, y2: 5 })
504
+ ))
499
505
  }
500
506
 
501
507
  function PayloadViewerTitle({ file, status, statusText }) {
@@ -517,13 +523,13 @@ function PayloadViewerTitleWhenProxied({ mime, status, statusText, gatewayIsBad
517
523
  }
518
524
 
519
525
  async function previewMock(method, urlMask, href) {
520
- const timer = setTimeout(renderProgressBar, PROGRESS_BAR_DELAY)
526
+ const timer = setTimeout(renderSpinner, SPINNER_DELAY)
521
527
  const response = await fetch(href, { method })
522
528
  clearTimeout(timer)
523
529
  await updatePayloadViewer(method, urlMask, response)
524
530
 
525
- function renderProgressBar() {
526
- payloadViewerRef.current.replaceChildren(PayloadViewerProgressBar())
531
+ function renderSpinner() {
532
+ payloadViewerRef.current.replaceChildren(PayloadViewerSpinner())
527
533
  }
528
534
  }
529
535
 
package/src/Mockaton.js CHANGED
@@ -3,11 +3,11 @@ 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 { watchMocksDir } from './Watcher.js'
7
6
  import { BodyReaderError } from './utils/http-request.js'
8
7
  import * as mockBrokerCollection from './mockBrokersCollection.js'
9
8
  import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
10
9
  import { apiPatchRequests, apiGetRequests } from './Api.js'
10
+ import { watchMocksDir, watchStaticMocksDir } from './Watcher.js'
11
11
  import { dispatchStatic, initStaticCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
12
12
  import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from './utils/http-response.js'
13
13
 
@@ -17,8 +17,9 @@ process.on('unhandledRejection', error => { throw error })
17
17
  export function Mockaton(options) {
18
18
  setup(options)
19
19
  mockBrokerCollection.init()
20
- watchMocksDir()
21
20
  initStaticCollection()
21
+ watchMocksDir()
22
+ watchStaticMocksDir()
22
23
 
23
24
  return createServer(onRequest).listen(config.port, config.host, function (error) {
24
25
  if (error) {
@@ -1,4 +1,4 @@
1
- import { join } from 'node:path'
1
+ import { join, basename } from 'node:path'
2
2
  import { readFileSync } from 'node:fs'
3
3
 
4
4
  import { mimeFor } from './utils/mime.js'
@@ -37,14 +37,25 @@ let collection = {}
37
37
  export function initStaticCollection() {
38
38
  collection = {}
39
39
  listFilesRecursively(config.staticDir)
40
- .filter(isFileAllowed)
41
40
  .sort()
42
- .forEach(f => registerStatic(f))
41
+ .forEach(registerStaticMock)
43
42
  }
44
43
 
45
- function registerStatic(file) {
44
+ /** @returns {boolean} registered */
45
+ export function registerStaticMock(file) {
46
+ if (!isFileAllowed(basename(file)))
47
+ return false
48
+
46
49
  file = '/' + file
50
+ if (findStaticBrokerByRoute(file))
51
+ return false
52
+
47
53
  collection[file] = new StaticBroker(file)
54
+ return true
55
+ }
56
+
57
+ export function unregisterStaticMock(file) {
58
+ delete collection['/' + file]
48
59
  }
49
60
 
50
61
  export function findStaticBrokerByRoute(route) {
package/src/Watcher.js CHANGED
@@ -5,6 +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
9
 
9
10
 
10
11
  /** # AR = Add or Remove Mock Event */
@@ -39,5 +40,23 @@ export function watchMocksDir() {
39
40
  })
40
41
  }
41
42
 
42
- // TODO staticDir, config changes
43
+ export function watchStaticMocksDir() {
44
+ const dir = config.staticDir
45
+ if (!dir)
46
+ return
47
+ watch(dir, { recursive: true, persistent: false }, (_, file) => {
48
+ if (!file)
49
+ return
50
+ if (isFile(join(dir, file))) {
51
+ if (registerStaticMock(file))
52
+ uiSyncVersion.increment()
53
+ }
54
+ else {
55
+ unregisterStaticMock(file)
56
+ uiSyncVersion.increment()
57
+ }
58
+ })
59
+ }
60
+
61
+ // TODO config changes
43
62
  // TODO think about throttling e.g. bulk deletes/remove files