mockaton 11.0.2 → 11.1.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
@@ -298,13 +298,14 @@ export default defineConfig({
298
298
  ],
299
299
 
300
300
  onReady: await openInBrowser,
301
+ watcherEnabled: true
301
302
  })
302
303
  ```
303
304
 
304
305
  </details>
305
306
 
306
307
  <details>
307
- <summary>Config Documentation…</summary>
308
+ <summary><b>Config Documentation…</b></summary>
308
309
 
309
310
  ### `mocksDir?: string`
310
311
  Defaults to `'mockaton-mocks'`.
@@ -548,6 +549,13 @@ Defaults to `'normal'`.
548
549
  - `normal`: info, mock access, warnings, and errors
549
550
  - `verbose`: normal + API access
550
551
 
552
+ <br/>
553
+
554
+
555
+ ### `watcherEnabled?: boolean`
556
+ Defaults to `true`. When `true`, newly added mocks get registered,
557
+ or unregistered when deleting them.
558
+
551
559
  </details>
552
560
 
553
561
 
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@ type Plugin = (
5
5
  request: IncomingMessage,
6
6
  response: OutgoingMessage
7
7
  ) => Promise<{
8
- mime: string,
8
+ mime: string
9
9
  body: string | Uint8Array
10
10
  }>
11
11
 
@@ -30,7 +30,7 @@ interface Config {
30
30
  extraHeaders?: string[]
31
31
  extraMimes?: { [fileExt: string]: string }
32
32
 
33
- corsAllowed?: boolean,
33
+ corsAllowed?: boolean
34
34
  corsOrigins?: string[]
35
35
  corsMethods?: string[]
36
36
  corsHeaders?: string[]
@@ -42,10 +42,13 @@ interface Config {
42
42
  plugins?: [filenameTester: RegExp, plugin: Plugin][]
43
43
 
44
44
  onReady?: (address: string) => void
45
+
46
+ watcherEnabled?: boolean
45
47
  }
46
48
 
47
49
 
48
50
  export function Mockaton(options: Partial<Config>): Promise<Server | undefined>
51
+
49
52
  export function defineConfig(options: Partial<Config>): Partial<Config>
50
53
 
51
54
  export const jsToJsonPlugin: Plugin
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "11.0.2",
5
+ "version": "11.1.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -12,7 +12,7 @@ import * as staticCollection from './staticCollection.js'
12
12
  import * as mockBrokersCollection from './mockBrokersCollection.js'
13
13
  import { config, ConfigValidator } from './config.js'
14
14
  import { DashboardHtml, CSP } from './DashboardHtml.js'
15
- import { sendOK, sendJSON, sendUnprocessableContent, sendFile, sendHTML } from './utils/http-response.js'
15
+ import { sendOK, sendJSON, sendUnprocessable, sendFile, sendHTML } from './utils/http-response.js'
16
16
  import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_SYNC_VERSION } from './ApiConstants.js'
17
17
 
18
18
 
@@ -116,7 +116,7 @@ async function selectCookie(req, response) {
116
116
 
117
117
  const error = cookie.setCurrent(cookieKey)
118
118
  if (error)
119
- sendUnprocessableContent(response, error?.message || error)
119
+ sendUnprocessable(response, error?.message || error)
120
120
  else
121
121
  sendJSON(response, cookie.list())
122
122
  }
@@ -127,7 +127,7 @@ async function selectMock(req, response) {
127
127
 
128
128
  const broker = mockBrokersCollection.brokerByFilename(file)
129
129
  if (!broker || !broker.hasMock(file))
130
- sendUnprocessableContent(response, `Missing Mock: ${file}`)
130
+ sendUnprocessable(response, `Missing Mock: ${file}`)
131
131
  else {
132
132
  broker.selectFile(file)
133
133
  sendJSON(response, broker)
@@ -140,7 +140,7 @@ async function toggle500(req, response) {
140
140
 
141
141
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
142
142
  if (!broker)
143
- sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
143
+ sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
144
144
  else {
145
145
  broker.toggle500()
146
146
  sendJSON(response, broker)
@@ -153,9 +153,9 @@ async function setRouteIsDelayed(req, response) {
153
153
 
154
154
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
155
155
  if (!broker)
156
- sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
156
+ sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
157
157
  else if (typeof delayed !== 'boolean')
158
- sendUnprocessableContent(response, `Expected boolean for "delayed"`)
158
+ sendUnprocessable(response, `Expected boolean for "delayed"`)
159
159
  else {
160
160
  broker.setDelayed(delayed)
161
161
  sendJSON(response, broker)
@@ -168,11 +168,11 @@ async function setRouteIsProxied(req, response) {
168
168
 
169
169
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
170
170
  if (!broker)
171
- sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
171
+ sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
172
172
  else if (typeof proxied !== 'boolean')
173
- sendUnprocessableContent(response, `Expected boolean for "proxied"`)
173
+ sendUnprocessable(response, `Expected boolean for "proxied"`)
174
174
  else if (proxied && !config.proxyFallback)
175
- sendUnprocessableContent(response, `There’s no proxy fallback`)
175
+ sendUnprocessable(response, `There’s no proxy fallback`)
176
176
  else {
177
177
  broker.setProxied(proxied)
178
178
  sendJSON(response, broker)
@@ -184,7 +184,7 @@ async function updateProxyFallback(req, response) {
184
184
  const fallback = await parseJSON(req)
185
185
 
186
186
  if (!ConfigValidator.proxyFallback(fallback))
187
- sendUnprocessableContent(response, `Invalid Proxy Fallback URL`)
187
+ sendUnprocessable(response, `Invalid Proxy Fallback URL`)
188
188
  else {
189
189
  config.proxyFallback = fallback
190
190
  sendOK(response)
@@ -196,7 +196,7 @@ async function setCollectProxied(req, response) {
196
196
  const collectProxied = await parseJSON(req)
197
197
 
198
198
  if (!ConfigValidator.collectProxied(collectProxied))
199
- sendUnprocessableContent(response, `Expected a boolean for "collectProxied"`)
199
+ sendUnprocessable(response, `Expected a boolean for "collectProxied"`)
200
200
  else {
201
201
  config.collectProxied = collectProxied
202
202
  sendOK(response)
@@ -216,7 +216,7 @@ async function setCorsAllowed(req, response) {
216
216
  const corsAllowed = await parseJSON(req)
217
217
 
218
218
  if (!ConfigValidator.corsAllowed(corsAllowed))
219
- sendUnprocessableContent(response, `Expected boolean for "corsAllowed"`)
219
+ sendUnprocessable(response, `Expected boolean for "corsAllowed"`)
220
220
  else {
221
221
  config.corsAllowed = corsAllowed
222
222
  sendOK(response)
@@ -228,7 +228,7 @@ async function setGlobalDelay(req, response) {
228
228
  const delay = await parseJSON(req)
229
229
 
230
230
  if (!ConfigValidator.delay(delay))
231
- sendUnprocessableContent(response, `Expected non-negative integer for "delay"`)
231
+ sendUnprocessable(response, `Expected non-negative integer for "delay"`)
232
232
  else {
233
233
  config.delay = delay
234
234
  sendOK(response)
@@ -242,9 +242,9 @@ async function setStaticRouteStatusCode(req, response) {
242
242
 
243
243
  const broker = staticCollection.brokerByRoute(urlMask)
244
244
  if (!broker)
245
- sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
245
+ sendUnprocessable(response, `Static route does not exist: ${urlMask}`)
246
246
  else if (!(status === 200 || status === 404))
247
- sendUnprocessableContent(response, `Expected 200 or 404 status code`)
247
+ sendUnprocessable(response, `Expected 200 or 404 status code`)
248
248
  else {
249
249
  broker.setStatus(status)
250
250
  sendOK(response)
@@ -257,9 +257,9 @@ async function setStaticRouteIsDelayed(req, response) {
257
257
 
258
258
  const broker = staticCollection.brokerByRoute(urlMask)
259
259
  if (!broker)
260
- sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
260
+ sendUnprocessable(response, `Static route does not exist: ${urlMask}`)
261
261
  else if (typeof delayed !== 'boolean')
262
- sendUnprocessableContent(response, `Expected boolean for "delayed"`)
262
+ sendUnprocessable(response, `Expected boolean for "delayed"`)
263
263
  else {
264
264
  broker.setDelayed(delayed)
265
265
  sendOK(response)
package/src/Dashboard.js CHANGED
@@ -734,7 +734,7 @@ function SettingsIcon() {
734
734
  * The version increments when a mock file is added, removed, or renamed.
735
735
  */
736
736
  function initRealTimeUpdates() {
737
- let oldVersion = undefined
737
+ let oldVersion = undefined // undefined waits until next event or timeout
738
738
  let controller = new AbortController()
739
739
 
740
740
  longPoll()
package/src/MockBroker.js CHANGED
@@ -2,8 +2,8 @@ import { includesComment, extractComments, parseFilename } from './Filename.js'
2
2
  import { DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
3
3
 
4
4
 
5
- /**
6
- * MockBroker is a state for a particular route. It knows the available mock
5
+ /**
6
+ * MockBroker is a state for a particular route. It knows the available mock
7
7
  * files that can be served for the route, the currently selected file, etc.
8
8
  */
9
9
  export class MockBroker {
@@ -27,7 +27,7 @@ export class MockBroker {
27
27
  }
28
28
 
29
29
  register(file) {
30
- if (this.auto500 && this.#is500(file)) // TESTME
30
+ if (this.auto500 && this.#is500(file))
31
31
  this.selectFile(file)
32
32
  this.mocks.push(file)
33
33
  this.#sortMocks()
@@ -14,11 +14,11 @@ import { sendInternalServerError, sendMockNotFound } from './utils/http-response
14
14
  export async function dispatchMock(req, response) {
15
15
  try {
16
16
  const isHead = req.method === 'HEAD'
17
-
17
+
18
18
  let broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
19
19
  if (!broker && isHead)
20
20
  broker = mockBrokerCollection.brokerByRoute('GET', req.url)
21
-
21
+
22
22
  if (config.proxyFallback && (!broker || broker.proxied)) {
23
23
  await proxy(req, response, broker?.delayed ? calcDelay() : 0)
24
24
  return
@@ -42,6 +42,7 @@ export async function dispatchMock(req, response) {
42
42
  logger.accessMock(req.url, broker.file)
43
43
  response.setHeader('Content-Type', mime)
44
44
  response.setHeader('Content-Length', length(body))
45
+
45
46
  setTimeout(() => response.end(isHead ? null : body),
46
47
  Number(broker.delayed && calcDelay()))
47
48
  }
package/src/Mockaton.js CHANGED
@@ -11,7 +11,7 @@ import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
11
11
  import { watchMocksDir, watchStaticDir } from './Watcher.js'
12
12
  import { apiPatchRequests, apiGetRequests } from './Api.js'
13
13
  import { BodyReaderError, hasControlChars } from './utils/http-request.js'
14
- import { sendNoContent, sendInternalServerError, sendUnprocessableContent, sendTooLongURI, sendBadRequest } from './utils/http-response.js'
14
+ import { sendNoContent, sendInternalServerError, sendUnprocessable, sendTooLongURI, sendBadRequest } from './utils/http-response.js'
15
15
 
16
16
 
17
17
  export function Mockaton(options) {
@@ -20,8 +20,11 @@ export function Mockaton(options) {
20
20
 
21
21
  mockBrokerCollection.init()
22
22
  staticCollection.init()
23
- watchMocksDir()
24
- watchStaticDir()
23
+
24
+ if (options.watcherEnabled) {
25
+ watchMocksDir()
26
+ watchStaticDir()
27
+ }
25
28
 
26
29
  const server = createServer(onRequest)
27
30
  server.on('error', reject)
@@ -75,7 +78,7 @@ async function onRequest(req, response) {
75
78
  }
76
79
  catch (error) {
77
80
  if (error instanceof BodyReaderError)
78
- sendUnprocessableContent(response, `${error.name}: ${error.message}`)
81
+ sendUnprocessable(response, `${error.name}: ${error.message}`)
79
82
  else
80
83
  sendInternalServerError(response, error)
81
84
  }
package/src/ProxyRelay.js CHANGED
@@ -6,7 +6,7 @@ import { extFor } from './utils/mime.js'
6
6
  import { write, isFile } from './utils/fs.js'
7
7
  import { makeMockFilename } from './Filename.js'
8
8
  import { readBody, BodyReaderError } from './utils/http-request.js'
9
- import { sendUnprocessableContent, sendBadGateway } from './utils/http-response.js'
9
+ import { sendUnprocessable, sendBadGateway } from './utils/http-response.js'
10
10
 
11
11
 
12
12
  export async function proxy(req, response, delay) {
@@ -22,7 +22,7 @@ export async function proxy(req, response, delay) {
22
22
  }
23
23
  catch (error) { // TESTME
24
24
  if (error instanceof BodyReaderError)
25
- sendUnprocessableContent(response, error.name)
25
+ sendUnprocessable(response, error.name)
26
26
  else
27
27
  sendBadGateway(response, error)
28
28
  return
@@ -1,13 +1,12 @@
1
1
  import { join } from 'node:path'
2
2
  import { readFileSync } from 'node:fs'
3
3
 
4
+ import { isFile } from './utils/fs.js'
4
5
  import { logger } from './utils/logger.js'
5
6
  import { mimeFor } from './utils/mime.js'
6
7
  import { brokerByRoute } from './staticCollection.js'
7
8
  import { config, calcDelay } from './config.js'
8
9
  import { sendMockNotFound, sendPartialContent } from './utils/http-response.js'
9
- import { execFileSync } from 'node:child_process'
10
- import { isFile } from './utils/fs.js'
11
10
 
12
11
 
13
12
  export async function dispatchStatic(req, response) {
package/src/Watcher.js CHANGED
@@ -18,7 +18,7 @@ export const uiSyncVersion = new class extends EventEmitter {
18
18
  delay = Number(process.env.MOCKATON_WATCHER_DEBOUNCE_MS ?? 80)
19
19
  version = 0
20
20
 
21
- increment = this.#debounce(() => {
21
+ increment = /** @type {function} */ this.#debounce(() => {
22
22
  this.version++
23
23
  super.emit('ARR')
24
24
  })
package/src/config.js CHANGED
@@ -49,7 +49,9 @@ const schema = {
49
49
  [/\.(js|ts)$/, jsToJsonPlugin]
50
50
  ], Array.isArray],
51
51
 
52
- onReady: [await openInBrowser, is(Function)]
52
+ onReady: [await openInBrowser, is(Function)],
53
+
54
+ watcherEnabled: [true, is(Boolean)],
53
55
  }
54
56
 
55
57
 
@@ -41,20 +41,22 @@ export function init() {
41
41
  /** @returns {boolean} registered */
42
42
  export function registerMock(file, isFromWatcher = false) {
43
43
  if (brokerByFilename(file)?.hasMock(file)
44
- || !isFileAllowed(basename(file)) // TESTME
44
+ || !isFileAllowed(basename(file))
45
45
  || !filenameIsValid(file))
46
46
  return false
47
47
 
48
48
  const { method, urlMask } = parseFilename(file)
49
49
  collection[method] ??= {}
50
50
 
51
- if (!collection[method][urlMask])
52
- collection[method][urlMask] = new MockBroker(file)
51
+ let broker = collection[method][urlMask]
52
+
53
+ if (!broker)
54
+ broker = collection[method][urlMask] = new MockBroker(file)
53
55
  else
54
- collection[method][urlMask].register(file)
56
+ broker.register(file)
55
57
 
56
- if (isFromWatcher && !collection[method][urlMask].file) // TESTME e.g. auto500 is selected and adding a new one
57
- collection[method][urlMask].selectDefaultFile()
58
+ if (isFromWatcher && !broker.file)
59
+ broker.selectDefaultFile()
58
60
 
59
61
  return true
60
62
  }
@@ -83,8 +85,7 @@ export function unregisterMock(file) {
83
85
  /** @returns {MockBroker | undefined} */
84
86
  export function brokerByFilename(file) {
85
87
  const { method, urlMask } = parseFilename(file)
86
- if (collection[method])
87
- return collection[method][urlMask]
88
+ return collection[method]?.[urlMask]
88
89
  }
89
90
 
90
91
  /**
@@ -113,7 +114,7 @@ export function extractAllComments() {
113
114
  }
114
115
 
115
116
  export function setMocksMatchingComment(comment) {
116
- forEachBroker(b =>
117
+ forEachBroker(b =>
117
118
  b.setByMatchingComment(comment))
118
119
  }
119
120
 
@@ -53,7 +53,7 @@ export function sendTooLongURI(response) {
53
53
  response.end()
54
54
  }
55
55
 
56
- export function sendUnprocessableContent(response, error) {
56
+ export function sendUnprocessable(response, error) {
57
57
  logger.access(response, error)
58
58
  response.statusCode = 422
59
59
  response.end(error)