mockaton 11.2.1 → 11.2.2
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 +17 -18
- package/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/Api.js +39 -13
- package/src/ApiConstants.js +2 -1
- package/src/Dashboard.js +11 -9
- package/src/DashboardDevHotReload.js +34 -0
- package/src/DashboardHtml.js +2 -1
- package/src/MockDispatcher.js +10 -8
- package/src/Mockaton.js +7 -2
- package/src/Watcher.js +3 -3
- package/src/WatcherDev.js +19 -0
- package/src/cli.js +1 -1
- package/src/config.js +11 -9
- package/Makefile +0 -53
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<img src="src/Logo.svg" alt="Mockaton Logo" width="210" style="margin-top: 30px"/>
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-

|
|
5
4
|
[](https://github.com/ericfortis/mockaton/actions/workflows/test.yml)
|
|
6
5
|
[](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
|
|
7
6
|
[](https://codecov.io/github/ericfortis/mockaton)
|
|
@@ -17,16 +16,17 @@ following a convention similar to the URLs.
|
|
|
17
16
|
For example, for [/api/company/123](#), the filename could be:
|
|
18
17
|
|
|
19
18
|
<pre>
|
|
20
|
-
<code>my-mocks-dir/<b>api/company</b>/[
|
|
19
|
+
<code>my-mocks-dir/<b>api/company</b>/[id].GET.200.json</code>
|
|
21
20
|
</pre>
|
|
22
21
|
|
|
23
22
|
<br/>
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
## Quick Start (Docker)
|
|
27
|
-
This will spin up Mockaton with the
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
This will spin up Mockaton with the sample directories
|
|
27
|
+
included in this repo mounted on the container.
|
|
28
|
+
|
|
29
|
+
_[mockaton-mocks/](./mockaton-mocks) and [mockaton-static-mocks/](./mockaton-static-mocks)_
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
32
|
git clone https://github.com/ericfortis/mockaton.git --depth 1
|
|
@@ -117,7 +117,6 @@ They will be saved in your `config.mocksDir` following the filename convention.
|
|
|
117
117
|
</details>
|
|
118
118
|
|
|
119
119
|
<br/>
|
|
120
|
-
|
|
121
120
|
<br/>
|
|
122
121
|
|
|
123
122
|
|
|
@@ -156,9 +155,9 @@ make run-standalone-demo
|
|
|
156
155
|
|
|
157
156
|
### Testing scenarios that would otherwise be skipped
|
|
158
157
|
- Trigger dynamic states on an API. You can do this by using comments on mock filenames, for example, for polled alerts or notifications.
|
|
159
|
-
- Testing retries,
|
|
158
|
+
- Testing retries, you can change an endpoint from a 500 to a 200 on the fly.
|
|
160
159
|
- Simulate errors on third-party APIs, or on your project’s backend (if you are a frontend dev, or unfamiliar with that code)
|
|
161
|
-
- Generating dynamic responses
|
|
160
|
+
- Generating dynamic responses. Mockaton lets you use Node’s HTTP handlers (see function mocks) when using function mocks.
|
|
162
161
|
So you can, e.g.:
|
|
163
162
|
- have an in-memory database
|
|
164
163
|
- read from disk
|
|
@@ -194,9 +193,9 @@ backends when checking out long-lived branches or bisecting bugs.
|
|
|
194
193
|
|
|
195
194
|
_For Docker, see the Quick-Start section above._
|
|
196
195
|
|
|
197
|
-
Requires Node.js **v22.18+**, which supports mocks
|
|
196
|
+
Requires Node.js **v22.18+**, which supports TypeScript mocks.
|
|
198
197
|
|
|
199
|
-
1. Create a mock in the default
|
|
198
|
+
1. Create a mock in the default directory (`./mockaton-mocks`)
|
|
200
199
|
```sh
|
|
201
200
|
mkdir -p mockaton-mocks/api
|
|
202
201
|
echo "[1,2,3]" > mockaton-mocks/api/foo.GET.200.json
|
|
@@ -239,7 +238,7 @@ The CLI options override their counterparts in `mockaton.config.js`
|
|
|
239
238
|
-p, --port <port> (default: 0) which means auto-assigned
|
|
240
239
|
|
|
241
240
|
-q, --quiet Errors only
|
|
242
|
-
--no-open Don’t open dashboard in a browser
|
|
241
|
+
--no-open Don’t open dashboard in a browser
|
|
243
242
|
|
|
244
243
|
-h, --help
|
|
245
244
|
-v, --version
|
|
@@ -266,7 +265,7 @@ export default defineConfig({
|
|
|
266
265
|
|
|
267
266
|
logLevel: 'normal',
|
|
268
267
|
|
|
269
|
-
delay: 1200, //
|
|
268
|
+
delay: 1200, // ms. Applies to routes with the Delay Checkbox "ON"
|
|
270
269
|
delayJitter: 0,
|
|
271
270
|
|
|
272
271
|
proxyFallback: '',
|
|
@@ -285,7 +284,6 @@ export default defineConfig({
|
|
|
285
284
|
corsCredentials: true,
|
|
286
285
|
corsMaxAge: 0,
|
|
287
286
|
|
|
288
|
-
|
|
289
287
|
plugins: [
|
|
290
288
|
[/\.(js|ts)$/, jsToJsonPlugin]
|
|
291
289
|
],
|
|
@@ -575,7 +573,7 @@ const server = await Mockaton(
|
|
|
575
573
|
|
|
576
574
|
|
|
577
575
|
## You can write JSON mocks in JavaScript or TypeScript
|
|
578
|
-
_TypeScript
|
|
576
|
+
_TypeScript needs **Node 22.18+ or 23.6+**_
|
|
579
577
|
|
|
580
578
|
For example, `api/foo.GET.200.js`
|
|
581
579
|
|
|
@@ -675,7 +673,6 @@ want a `Content-Type` header in the response.
|
|
|
675
673
|
|
|
676
674
|
<details>
|
|
677
675
|
<summary>Supported Methods</summary>
|
|
678
|
-
<p>From <code>require('node:http').METHODS</code></p>
|
|
679
676
|
<p>
|
|
680
677
|
ACL, BIND, CHECKOUT,
|
|
681
678
|
CONNECT, COPY, DELETE,
|
|
@@ -715,8 +712,6 @@ api/foo.GET.200.json
|
|
|
715
712
|
|
|
716
713
|
A filename can have many comments.
|
|
717
714
|
|
|
718
|
-
<br/>
|
|
719
|
-
|
|
720
715
|
### Default mock for a route
|
|
721
716
|
You can add the comment: `(default)`.
|
|
722
717
|
Otherwise, the first file in **alphabetical order** wins.
|
|
@@ -736,7 +731,7 @@ api/video<b>?limit=[limit]</b>.GET.200.json
|
|
|
736
731
|
|
|
737
732
|
On Windows, filenames containing "?" are [not
|
|
738
733
|
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query
|
|
739
|
-
string it’s ignored anyway.
|
|
734
|
+
string, it’s ignored anyway.
|
|
740
735
|
|
|
741
736
|
<br/>
|
|
742
737
|
|
|
@@ -864,6 +859,9 @@ await mockaton.reset()
|
|
|
864
859
|
|
|
865
860
|
## Alternatives worth learning as well
|
|
866
861
|
|
|
862
|
+
<details>
|
|
863
|
+
<summary>Learn more…</summary>
|
|
864
|
+
|
|
867
865
|
### Proxy-like
|
|
868
866
|
These are similar to Mockaton in the sense that you can modify the
|
|
869
867
|
mock response without loosing or risking your frontend code state. For
|
|
@@ -889,6 +887,7 @@ programs hijack your browser’s HTTP client (and Node’s).
|
|
|
889
887
|
- [Swagger](https://swagger.io/)
|
|
890
888
|
- [Mockoon](https://mockoon.com)
|
|
891
889
|
|
|
890
|
+
</details>
|
|
892
891
|
|
|
893
892
|
<br/>
|
|
894
893
|
<br/>
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { join } from 'node:path'
|
|
7
7
|
|
|
8
8
|
import { cookie } from './cookie.js'
|
|
9
|
+
import { devWatcher } from './WatcherDev.js'
|
|
9
10
|
import { parseJSON } from './utils/http-request.js'
|
|
10
11
|
import { uiSyncVersion } from './Watcher.js'
|
|
11
12
|
import * as staticCollection from './staticCollection.js'
|
|
@@ -16,23 +17,31 @@ import { sendOK, sendJSON, sendUnprocessable, sendFile, sendHTML } from './utils
|
|
|
16
17
|
import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_SYNC_VERSION } from './ApiConstants.js'
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
const DEV = process.env.NODE_ENV === 'development'
|
|
21
|
+
|
|
22
|
+
export const DASHBOARD_ASSETS = [
|
|
23
|
+
'Dashboard.css',
|
|
24
|
+
'Dashboard.js',
|
|
25
|
+
'DashboardDom.js',
|
|
26
|
+
'DashboardStore.js',
|
|
27
|
+
'DashboardDevHotReload.js',
|
|
28
|
+
'ApiCommander.js',
|
|
29
|
+
'Logo.svg',
|
|
30
|
+
'Filename.js', // used on server too
|
|
31
|
+
'ApiConstants.js', // used on server too
|
|
32
|
+
]
|
|
33
|
+
|
|
19
34
|
export const apiGetRequests = new Map([
|
|
20
35
|
[API.dashboard, serveDashboard],
|
|
21
|
-
...[
|
|
22
|
-
|
|
23
|
-
'Dashboard.css',
|
|
24
|
-
'ApiCommander.js',
|
|
25
|
-
'ApiConstants.js',
|
|
26
|
-
'Dashboard.js',
|
|
27
|
-
'DashboardDom.js',
|
|
28
|
-
'DashboardStore.js',
|
|
29
|
-
'Filename.js'
|
|
30
|
-
].map(f => [API.dashboard + '/' + f, serveStatic(f)]),
|
|
31
|
-
|
|
36
|
+
...DASHBOARD_ASSETS.map(f => [API.dashboard + '/' + f, serveStatic(f)]),
|
|
37
|
+
|
|
32
38
|
[API.state, getState],
|
|
33
39
|
[API.syncVersion, longPollClientSyncVersion],
|
|
34
|
-
[API.throws, () => { throw new Error('Test500') }]
|
|
35
40
|
])
|
|
41
|
+
if (DEV) {
|
|
42
|
+
apiGetRequests.set(API.throws, () => { throw new Error('Test500') })
|
|
43
|
+
apiGetRequests.set(API.watchHotReload, longPollDevHotReload)
|
|
44
|
+
}
|
|
36
45
|
|
|
37
46
|
export const apiPatchRequests = new Map([
|
|
38
47
|
[API.cors, setCorsAllowed],
|
|
@@ -54,7 +63,7 @@ export const apiPatchRequests = new Map([
|
|
|
54
63
|
/** # GET */
|
|
55
64
|
|
|
56
65
|
function serveDashboard(_, response) {
|
|
57
|
-
sendHTML(response, DashboardHtml, CSP)
|
|
66
|
+
sendHTML(response, DashboardHtml(config.hotReload), CSP)
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
function serveStatic(f) {
|
|
@@ -101,6 +110,23 @@ function longPollClientSyncVersion(req, response) {
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
|
|
113
|
+
function longPollDevHotReload(req, response) {
|
|
114
|
+
function onDevChange(file) {
|
|
115
|
+
devWatcher.unsubscribe(onDevChange)
|
|
116
|
+
sendJSON(response, file)
|
|
117
|
+
}
|
|
118
|
+
response.setTimeout(LONG_POLL_SERVER_TIMEOUT, () => {
|
|
119
|
+
devWatcher.unsubscribe(onDevChange)
|
|
120
|
+
sendJSON(response, '')
|
|
121
|
+
})
|
|
122
|
+
req.on('error', () => {
|
|
123
|
+
devWatcher.unsubscribe(onDevChange)
|
|
124
|
+
response.destroy()
|
|
125
|
+
})
|
|
126
|
+
devWatcher.subscribe(onDevChange)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
104
130
|
|
|
105
131
|
/** # PATCH */
|
|
106
132
|
|
package/src/ApiConstants.js
CHANGED
|
@@ -17,7 +17,8 @@ export const API = {
|
|
|
17
17
|
staticStatus: MOUNT + '/static-status',
|
|
18
18
|
syncVersion: MOUNT + '/sync-version',
|
|
19
19
|
throws: MOUNT + '/throws',
|
|
20
|
-
toggle500: MOUNT + '/toggle500'
|
|
20
|
+
toggle500: MOUNT + '/toggle500',
|
|
21
|
+
watchHotReload: MOUNT + '/watch-hot-reload',
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export const HEADER_502 = 'Mockaton502'
|
package/src/Dashboard.js
CHANGED
|
@@ -737,7 +737,7 @@ function SettingsIcon() {
|
|
|
737
737
|
* The version increments when a mock file is added, removed, or renamed.
|
|
738
738
|
*/
|
|
739
739
|
function initRealTimeUpdates() {
|
|
740
|
-
let oldVersion = undefined // undefined waits until next event or timeout
|
|
740
|
+
let oldVersion = undefined // undefined so it waits until next event or timeout
|
|
741
741
|
let controller = new AbortController()
|
|
742
742
|
|
|
743
743
|
longPoll()
|
|
@@ -823,8 +823,12 @@ function initKeyboardNavigation() {
|
|
|
823
823
|
|
|
824
824
|
|
|
825
825
|
function SyntaxJSON(json) {
|
|
826
|
+
// Capture groups: [string, optional colon, punc]
|
|
827
|
+
const regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:\s]+)|\S+/g
|
|
828
|
+
|
|
826
829
|
const MAX_NODES = 50_000
|
|
827
830
|
let nNodes = 0
|
|
831
|
+
|
|
828
832
|
const frag = new DocumentFragment()
|
|
829
833
|
|
|
830
834
|
function span(className, textContent) {
|
|
@@ -842,8 +846,7 @@ function SyntaxJSON(json) {
|
|
|
842
846
|
|
|
843
847
|
let match
|
|
844
848
|
let lastIndex = 0
|
|
845
|
-
|
|
846
|
-
while ((match = SyntaxJSON.regex.exec(json)) !== null) {
|
|
849
|
+
while ((match = regex.exec(json)) !== null) {
|
|
847
850
|
if (nNodes > MAX_NODES)
|
|
848
851
|
break
|
|
849
852
|
|
|
@@ -865,13 +868,15 @@ function SyntaxJSON(json) {
|
|
|
865
868
|
text(json.slice(lastIndex))
|
|
866
869
|
return frag
|
|
867
870
|
}
|
|
868
|
-
SyntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:\s]+)|\S+/g
|
|
869
|
-
// Capture group order: [string, optional colon, punc]
|
|
870
871
|
|
|
871
872
|
|
|
872
873
|
function SyntaxXML(xml) {
|
|
874
|
+
// Capture groups: [tagPunc, tagName, attrName, attrVal]
|
|
875
|
+
const regex = /(<\/?|\/?>|\?>)|(?<=<\??\/?)([A-Za-z_:][\w:.-]*)|([A-Za-z_:][\w:.-]*)(?==)|("(?:[^"\\]|\\.)*")/g
|
|
876
|
+
|
|
873
877
|
const MAX_NODES = 50_000
|
|
874
878
|
let nNodes = 0
|
|
879
|
+
|
|
875
880
|
const frag = new DocumentFragment()
|
|
876
881
|
|
|
877
882
|
function span(className, textContent) {
|
|
@@ -889,8 +894,7 @@ function SyntaxXML(xml) {
|
|
|
889
894
|
|
|
890
895
|
let match
|
|
891
896
|
let lastIndex = 0
|
|
892
|
-
|
|
893
|
-
while ((match = SyntaxXML.regex.exec(xml)) !== null) {
|
|
897
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
894
898
|
if (nNodes > MAX_NODES)
|
|
895
899
|
break
|
|
896
900
|
|
|
@@ -908,6 +912,4 @@ function SyntaxXML(xml) {
|
|
|
908
912
|
frag.normalize()
|
|
909
913
|
return frag
|
|
910
914
|
}
|
|
911
|
-
SyntaxXML.regex = /(<\/?|\/?>|\?>)|(?<=<\??\/?)([A-Za-z_:][\w:.-]*)|([A-Za-z_:][\w:.-]*)(?==)|("(?:[^"\\]|\\.)*")/g
|
|
912
|
-
// Capture groups order: [tagPunc, tagName, attrName, attrVal]
|
|
913
915
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { API } from './ApiConstants.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
longPoll()
|
|
5
|
+
async function longPoll() {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(API.watchHotReload)
|
|
8
|
+
if (response.ok) {
|
|
9
|
+
const editedFile = await response.json() || ''
|
|
10
|
+
if (editedFile.endsWith('.css')) {
|
|
11
|
+
hotReloadCSS(editedFile)
|
|
12
|
+
longPoll()
|
|
13
|
+
}
|
|
14
|
+
else if (editedFile)
|
|
15
|
+
location.reload()
|
|
16
|
+
else
|
|
17
|
+
longPoll()
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
throw response.statusText
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error('hot reload', error?.message || error)
|
|
24
|
+
setTimeout(longPoll, 3000)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hotReloadCSS(editedFile) {
|
|
29
|
+
const link = document.querySelector(`link[href*="${editedFile}"]`)
|
|
30
|
+
if (link) {
|
|
31
|
+
const href = link.href.split('?')[0]
|
|
32
|
+
link.href = href + '?t=' + Date.now()
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/DashboardHtml.js
CHANGED
|
@@ -6,7 +6,7 @@ export const CSP = [
|
|
|
6
6
|
].join(';')
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
export const DashboardHtml = `<!DOCTYPE html>
|
|
9
|
+
export const DashboardHtml = hotReloadEnabled => `<!DOCTYPE html>
|
|
10
10
|
<html lang="en-US">
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="UTF-8">
|
|
@@ -30,6 +30,7 @@ export const DashboardHtml = `<!DOCTYPE html>
|
|
|
30
30
|
<title>Mockaton</title>
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
33
|
+
${hotReloadEnabled ? `<script type="module" src="DashboardDevHotReload.js"></script>` : '' }
|
|
33
34
|
</body>
|
|
34
35
|
</html>
|
|
35
36
|
`
|
package/src/MockDispatcher.js
CHANGED
|
@@ -31,8 +31,8 @@ export async function dispatchMock(req, response) {
|
|
|
31
31
|
if (cookie.getCurrent())
|
|
32
32
|
response.setHeader('Set-Cookie', cookie.getCurrent())
|
|
33
33
|
|
|
34
|
-
response.statusCode = broker.auto500
|
|
35
|
-
? 500
|
|
34
|
+
response.statusCode = broker.auto500
|
|
35
|
+
? 500
|
|
36
36
|
: broker.status
|
|
37
37
|
const { mime, body } = broker.auto500
|
|
38
38
|
? { mime: '', body: '' }
|
|
@@ -40,10 +40,13 @@ export async function dispatchMock(req, response) {
|
|
|
40
40
|
|
|
41
41
|
response.setHeader('Content-Type', mime)
|
|
42
42
|
response.setHeader('Content-Length', length(body))
|
|
43
|
-
|
|
44
|
-
setTimeout(() =>
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
|
|
44
|
+
setTimeout(() =>
|
|
45
|
+
response.end(isHead
|
|
46
|
+
? null
|
|
47
|
+
: body
|
|
48
|
+
), Number(broker.delayed && calcDelay()))
|
|
49
|
+
|
|
47
50
|
logger.accessMock(req.url, broker.file)
|
|
48
51
|
}
|
|
49
52
|
catch (error) { // TESTME
|
|
@@ -77,7 +80,6 @@ export async function jsToJsonPlugin(filePath, req, response) {
|
|
|
77
80
|
|
|
78
81
|
function length(body) {
|
|
79
82
|
if (typeof body === 'string') return Buffer.byteLength(body)
|
|
80
|
-
if (
|
|
81
|
-
if (body instanceof Uint8Array) return body.byteLength
|
|
83
|
+
if (body instanceof Uint8Array) return body.byteLength // Buffers are u8
|
|
82
84
|
return 0
|
|
83
85
|
}
|
package/src/Mockaton.js
CHANGED
|
@@ -9,15 +9,18 @@ import { dispatchStatic } from './StaticDispatcher.js'
|
|
|
9
9
|
import * as staticCollection from './staticCollection.js'
|
|
10
10
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
11
11
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
12
|
-
import { watchMocksDir, watchStaticDir } from './Watcher.js'
|
|
13
12
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
14
13
|
import { BodyReaderError, hasControlChars } from './utils/http-request.js'
|
|
15
14
|
import {
|
|
16
15
|
setHeaders, sendNoContent, sendInternalServerError,
|
|
17
16
|
sendUnprocessable, sendTooLongURI, sendBadRequest
|
|
18
17
|
} from './utils/http-response.js'
|
|
18
|
+
import { watchDevSPA } from './WatcherDev.js'
|
|
19
|
+
import { watchMocksDir, watchStaticDir } from './Watcher.js'
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
const DEV = process.env.NODE_ENV === 'development'
|
|
23
|
+
|
|
21
24
|
export function Mockaton(options) {
|
|
22
25
|
return new Promise((resolve, reject) => {
|
|
23
26
|
setup(options)
|
|
@@ -29,6 +32,8 @@ export function Mockaton(options) {
|
|
|
29
32
|
watchMocksDir()
|
|
30
33
|
watchStaticDir()
|
|
31
34
|
}
|
|
35
|
+
if (DEV && config.hotReload)
|
|
36
|
+
watchDevSPA()
|
|
32
37
|
|
|
33
38
|
const server = createServer(onRequest)
|
|
34
39
|
server.on('error', reject)
|
|
@@ -45,7 +50,7 @@ export function Mockaton(options) {
|
|
|
45
50
|
|
|
46
51
|
async function onRequest(req, response) {
|
|
47
52
|
response.on('error', logger.warn)
|
|
48
|
-
|
|
53
|
+
|
|
49
54
|
setHeaders(response, ['Server', `Mockaton ${pkgJSON.version}`])
|
|
50
55
|
setHeaders(response, config.extraHeaders)
|
|
51
56
|
|
package/src/Watcher.js
CHANGED
|
@@ -30,7 +30,7 @@ export const uiSyncVersion = new class extends EventEmitter {
|
|
|
30
30
|
this.removeListener('ARR', listener)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
#debounce(fn) {
|
|
33
|
+
#debounce(fn) { // TESTME
|
|
34
34
|
let timer
|
|
35
35
|
return () => {
|
|
36
36
|
clearTimeout(timer)
|
|
@@ -45,7 +45,7 @@ export function watchMocksDir() {
|
|
|
45
45
|
if (!file)
|
|
46
46
|
return
|
|
47
47
|
|
|
48
|
-
if (isDirectory(join(dir, file))) {
|
|
48
|
+
if (isDirectory(join(dir, file))) {
|
|
49
49
|
mockBrokerCollection.init()
|
|
50
50
|
uiSyncVersion.increment()
|
|
51
51
|
}
|
|
@@ -61,7 +61,7 @@ export function watchMocksDir() {
|
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function watchStaticDir() {
|
|
64
|
+
export function watchStaticDir() {
|
|
65
65
|
const dir = config.staticDir
|
|
66
66
|
if (!dir)
|
|
67
67
|
return
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { watch } from 'node:fs'
|
|
2
|
+
import { EventEmitter } from 'node:events'
|
|
3
|
+
import { DASHBOARD_ASSETS } from './Api.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const devWatcher = new class extends EventEmitter {
|
|
7
|
+
emit(file) { super.emit('RELOAD', file) }
|
|
8
|
+
subscribe(listener) { this.once('RELOAD', listener) }
|
|
9
|
+
unsubscribe(listener) { this.removeListener('RELOAD', listener) }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// DashboardHtml.js is not watched.
|
|
13
|
+
// It would need dynamic import + cache busting
|
|
14
|
+
export function watchDevSPA() {
|
|
15
|
+
watch('src', (_, file) => {
|
|
16
|
+
if (DASHBOARD_ASSETS.includes(file))
|
|
17
|
+
devWatcher.emit(file)
|
|
18
|
+
})
|
|
19
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -53,7 +53,7 @@ Options:
|
|
|
53
53
|
-p, --port <port> (default: 0) which means auto-assigned
|
|
54
54
|
|
|
55
55
|
-q, --quiet Errors only
|
|
56
|
-
--no-open Don’t open dashboard in a browser
|
|
56
|
+
--no-open Don’t open dashboard in a browser
|
|
57
57
|
|
|
58
58
|
-h, --help Show this help
|
|
59
59
|
-v, --version Show version
|
package/src/config.js
CHANGED
|
@@ -50,7 +50,9 @@ const schema = {
|
|
|
50
50
|
[/\.(js|ts)$/, jsToJsonPlugin]
|
|
51
51
|
], Array.isArray],
|
|
52
52
|
|
|
53
|
-
onReady: [await openInBrowser, is(Function)]
|
|
53
|
+
onReady: [await openInBrowser, is(Function)],
|
|
54
|
+
|
|
55
|
+
hotReload: [false, is(Boolean)]
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
|
|
@@ -68,17 +70,17 @@ export const config = Object.seal(defaults)
|
|
|
68
70
|
export const ConfigValidator = Object.freeze(validators)
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
/** @param {Partial<Config>}
|
|
72
|
-
export function setup(
|
|
73
|
-
if (
|
|
74
|
-
|
|
73
|
+
/** @param {Partial<Config>} opts */
|
|
74
|
+
export function setup(opts) {
|
|
75
|
+
if (opts.mocksDir)
|
|
76
|
+
opts.mocksDir = resolve(opts.mocksDir)
|
|
75
77
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
+
if (opts.staticDir)
|
|
79
|
+
opts.staticDir = resolve(opts.staticDir)
|
|
78
80
|
else if (!isDirectory(defaults.staticDir))
|
|
79
|
-
|
|
81
|
+
opts.staticDir = ''
|
|
80
82
|
|
|
81
|
-
Object.assign(config,
|
|
83
|
+
Object.assign(config, opts)
|
|
82
84
|
validate(config, ConfigValidator)
|
|
83
85
|
logger.setLevel(config.logLevel)
|
|
84
86
|
}
|
package/Makefile
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
docker: docker-build docker-run
|
|
2
|
-
|
|
3
|
-
docker-build:
|
|
4
|
-
@docker build --no-cache --tag mockaton $(PWD)
|
|
5
|
-
|
|
6
|
-
docker-run: docker-stop
|
|
7
|
-
@docker run --name mockaton \
|
|
8
|
-
--publish 127.0.0.1:2020:2020 \
|
|
9
|
-
--volume $(PWD)/mockaton.config.js:/app/mockaton.config.js \
|
|
10
|
-
--volume $(PWD)/mockaton-mocks:/app/mockaton-mocks \
|
|
11
|
-
--volume $(PWD)/mockaton-static-mocks:/app/mockaton-static-mocks \
|
|
12
|
-
mockaton
|
|
13
|
-
|
|
14
|
-
docker-stop:
|
|
15
|
-
@docker stop mockaton >/dev/null 2>&1 || true
|
|
16
|
-
@docker rm mockaton >/dev/null 2>&1 || true
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
start:
|
|
20
|
-
@node src/cli.js
|
|
21
|
-
|
|
22
|
-
watch:
|
|
23
|
-
@node --watch src/cli.js
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
test:
|
|
27
|
-
@MOCKATON_WATCHER_DEBOUNCE_MS=0 node --test 'src/**/*.test.js'
|
|
28
|
-
|
|
29
|
-
test-docker:
|
|
30
|
-
@docker run --rm --interactive --tty \
|
|
31
|
-
--volume $(PWD):/app \
|
|
32
|
-
--workdir /app \
|
|
33
|
-
node:24 \
|
|
34
|
-
make test
|
|
35
|
-
|
|
36
|
-
coverage:
|
|
37
|
-
@MOCKATON_WATCHER_DEBOUNCE_MS=0 node --test --experimental-test-coverage \
|
|
38
|
-
--test-reporter=spec --test-reporter-destination=stdout \
|
|
39
|
-
--test-reporter=lcov --test-reporter-destination=lcov.info \
|
|
40
|
-
'src/**/*.test.js'
|
|
41
|
-
|
|
42
|
-
pixaton:
|
|
43
|
-
@node --test --experimental-test-isolation=none \
|
|
44
|
-
--import=./pixaton-tests/_setup.js \
|
|
45
|
-
'pixaton-tests/**/*.test.js'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
outdated:
|
|
49
|
-
@npm outdated --parseable |\
|
|
50
|
-
awk -F: '{ printf "npm i %-30s ;# %s\n", $$4, $$2 }'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.PHONY: *
|