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 +9 -1
- package/index.d.ts +5 -2
- package/package.json +1 -1
- package/src/Api.js +17 -17
- package/src/Dashboard.js +1 -1
- package/src/MockBroker.js +3 -3
- package/src/MockDispatcher.js +3 -2
- package/src/Mockaton.js +7 -4
- package/src/ProxyRelay.js +2 -2
- package/src/StaticDispatcher.js +1 -2
- package/src/Watcher.js +1 -1
- package/src/config.js +3 -1
- package/src/mockBrokersCollection.js +10 -9
- package/src/utils/http-response.js +1 -1
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
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
+
sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
|
|
157
157
|
else if (typeof delayed !== 'boolean')
|
|
158
|
-
|
|
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
|
-
|
|
171
|
+
sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
|
|
172
172
|
else if (typeof proxied !== 'boolean')
|
|
173
|
-
|
|
173
|
+
sendUnprocessable(response, `Expected boolean for "proxied"`)
|
|
174
174
|
else if (proxied && !config.proxyFallback)
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
+
sendUnprocessable(response, `Static route does not exist: ${urlMask}`)
|
|
246
246
|
else if (!(status === 200 || status === 404))
|
|
247
|
-
|
|
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
|
-
|
|
260
|
+
sendUnprocessable(response, `Static route does not exist: ${urlMask}`)
|
|
261
261
|
else if (typeof delayed !== 'boolean')
|
|
262
|
-
|
|
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))
|
|
30
|
+
if (this.auto500 && this.#is500(file))
|
|
31
31
|
this.selectFile(file)
|
|
32
32
|
this.mocks.push(file)
|
|
33
33
|
this.#sortMocks()
|
package/src/MockDispatcher.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
25
|
+
sendUnprocessable(response, error.name)
|
|
26
26
|
else
|
|
27
27
|
sendBadGateway(response, error)
|
|
28
28
|
return
|
package/src/StaticDispatcher.js
CHANGED
|
@@ -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
|
@@ -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))
|
|
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
|
-
|
|
52
|
-
|
|
51
|
+
let broker = collection[method][urlMask]
|
|
52
|
+
|
|
53
|
+
if (!broker)
|
|
54
|
+
broker = collection[method][urlMask] = new MockBroker(file)
|
|
53
55
|
else
|
|
54
|
-
|
|
56
|
+
broker.register(file)
|
|
55
57
|
|
|
56
|
-
if (isFromWatcher && !
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
56
|
+
export function sendUnprocessable(response, error) {
|
|
57
57
|
logger.access(response, error)
|
|
58
58
|
response.statusCode = 422
|
|
59
59
|
response.end(error)
|