mockaton 13.11.2 → 13.12.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 +42 -27
- package/package.json +1 -1
- package/skills/mockaton/SKILL.md +21 -19
- package/src/client/ApiCommander.js +20 -19
- package/src/client/{app.js → App.js} +6 -6
- package/src/client/{app-header.js → Header.js} +4 -4
- package/src/client/IndexHtml.js +1 -1
- package/src/client/{app-mock-list.js → MockList.js} +7 -8
- package/src/client/{app-payload-viewer.js → PayloadViewer.js} +3 -3
- package/src/client/{app-store.test.js → store.test.js} +1 -1
- package/src/server/Api.js +57 -54
- package/src/server/MockBroker.js +2 -8
- package/src/server/MockDispatcher.js +6 -5
- package/src/server/Mockaton.js +14 -17
- package/src/server/Mockaton.test.js +61 -48
- package/src/server/ProxyRelay.js +5 -4
- package/src/server/UrlParsers.js +1 -1
- package/src/server/UrlParsers.test.js +1 -1
- package/src/server/cli.js +6 -6
- package/src/server/{Watcher.js → stores/Watcher.js} +18 -9
- package/src/server/{mockBrokersCollection.js → stores/brokers.js} +4 -7
- package/src/server/{config.js → stores/config.js} +30 -16
- package/src/server/utils/HttpIncomingMessage.js +17 -16
- package/src/server/utils/HttpServerResponse.test.js +2 -2
- package/src/server/utils/logger.js +7 -1
- package/src/server/utils/mime.js +2 -0
- package/www/src/assets/openapi.json +1 -1
- /package/src/client/{app.css → App.css} +0 -0
- /package/src/client/{app-header.css → Header.css} +0 -0
- /package/src/client/{app-mock-list.css → MockList.css} +0 -0
- /package/src/client/{app-payload-viewer.css → PayloadViewer.css} +0 -0
- /package/src/client/{graphics.js → icons.js} +0 -0
- /package/src/client/{app-store.js → store.js} +0 -0
- /package/src/server/{resolverBypassImportCache.js → ResolverBypassImportCache.js} +0 -0
- /package/src/server/{resolverResolveExtensionless.js → ResolverResolveExtensionless.js} +0 -0
- /package/src/server/{cookie.js → stores/cookies.js} +0 -0
package/README.md
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
<!-- SKILLS_IGNORE_BEGIN -->
|
|
2
|
-
<img src="logo.svg" alt="Mockaton Logo" width="180" style="margin-top: 30px"/>
|
|
3
2
|
|
|
4
3
|

|
|
5
4
|
[](https://github.com/ericfortis/mockaton/actions/workflows/test.yml)
|
|
6
5
|
[](https://codecov.io/github/ericfortis/mockaton)
|
|
7
6
|
|
|
7
|
+
<img src="logo.svg" alt="Mockaton Logo" width="180" style="margin-top: 30px"/>
|
|
8
|
+
|
|
9
|
+
No API state should be too difficult to test.
|
|
10
|
+
|
|
8
11
|
## [Docs ↗](https://mockaton.com) | [Changelog ↗](https://mockaton.com/changelog) | [Skills](skills/mockaton/SKILL.md)
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
Simulate API states that are normally ignored. For example,
|
|
15
|
+
click the 500 button to trigger an error on an endpoint. Then
|
|
16
|
+
unclick it to test your retry logic. Similarly, click the clock
|
|
17
|
+
button 🕓 to delay a response so you can test spinners —
|
|
18
|
+
you‘ve seen them in production at the top left, or restarting
|
|
19
|
+
their animation midway.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Dashboard
|
|
23
|
+
Besides the dashboard UI, there’s [an API](https://mockaton.com/api)
|
|
24
|
+
you can use to set up tests.
|
|
12
25
|
|
|
13
26
|
<picture>
|
|
14
27
|
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.light.gold.png">
|
|
@@ -35,47 +48,44 @@ curl localhost:2020/api/user
|
|
|
35
48
|
|
|
36
49
|
|
|
37
50
|
# Installation ([more options ↗](https://mockaton.com/installation))
|
|
38
|
-
Mockaton has no dependencies.
|
|
39
|
-
|
|
40
51
|
```sh
|
|
41
52
|
npm install -g mockaton
|
|
42
53
|
```
|
|
54
|
+
<i>Mockaton is a Node.js app with no dependencies.</i>
|
|
55
|
+
|
|
56
|
+
<!-- SKILLS_IGNORE_BEGIN -->
|
|
57
|
+
## Skills
|
|
58
|
+
```sh
|
|
59
|
+
npx skills add ericfortis/mockaton
|
|
60
|
+
```
|
|
61
|
+
<!-- SKILLS_IGNORE_END -->
|
|
62
|
+
|
|
63
|
+
|
|
43
64
|
|
|
44
65
|
## Basic Usage
|
|
45
66
|
```sh
|
|
46
|
-
mockaton --port 2020 my-mocks-dir
|
|
67
|
+
mockaton --port 2020 my-mocks-dir
|
|
47
68
|
```
|
|
48
69
|
|
|
49
|
-
Mockaton will serve the files on the given directory. It's a file-system based router
|
|
50
|
-
filenames can have dynamic parameters.
|
|
51
|
-
|
|
52
|
-
Similarly, each route can have different response status
|
|
70
|
+
Mockaton will serve the files on the given directory. It's a file-system based router in which
|
|
71
|
+
filenames can have dynamic parameters and comments. For paraments use square brackets `[]`,
|
|
72
|
+
and for comments use parentheses `()`. Comments are handy because this way each route
|
|
73
|
+
can have different mock file variants. Similarly, each route can have different response status
|
|
74
|
+
code variants.
|
|
53
75
|
|
|
54
76
|
|
|
55
77
|
| Route | Filename | Description |
|
|
56
78
|
| -----| -----| ---|
|
|
57
|
-
| /api/company/123 | api/company/[id].GET.200.ts | `[id]` is a dynamic parameter. `.ts`, and `.js` are sent as JSON by default |
|
|
58
|
-
| /media/avatar.png | media/avatar.png | Statics assets don't need the above extension |
|
|
59
|
-
| /api/login | api/login(invalid attempt).POST.401.ts |
|
|
60
|
-
| /api/login | api/login(default).GET.200.ts | `(default)` is a special comment; otherwise, the first mock variant in alphabetical order wins |
|
|
61
|
-
| /api/login | api/login(locked out user).POST.423.json | `.json` is allowed too |
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## Docs
|
|
65
|
-
- [Configuration: CLI and mockaton.config.js](https://mockaton.com/config).
|
|
66
|
-
- [Programmatic API](https://mockaton.com/api), in which
|
|
67
|
-
you can delay a route, select a different mock file, such as a 500 error, among other options.
|
|
79
|
+
| /api/company/123 | api/company/[id].GET.200.ts | `[id]` is a dynamic parameter. `.ts`, and `.js` are sent as JSON by default. |
|
|
80
|
+
| /media/avatar.png | media/avatar.png | Statics assets don't need the above extension. |
|
|
81
|
+
| /api/login | api/login(invalid attempt).POST.401.ts | Anything within parenthesis is a comment. They are ignored when routing. |
|
|
82
|
+
| /api/login | api/login(default).GET.200.ts | `(default)` is a special comment; otherwise, the first mock variant in alphabetical order wins. |
|
|
83
|
+
| /api/login | api/login(locked out user).POST.423.json | `.json` is allowed too. |
|
|
68
84
|
|
|
69
85
|
<!-- SKILLS_IGNORE_BEGIN -->
|
|
70
86
|
## How to scrape your backend APIs?
|
|
71
87
|
Mockaton has a [Browser Extension](https://mockaton.com/scraping) that lets
|
|
72
88
|
you download in bulk all your API responses following Mockaton's filename convention.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
## Skills
|
|
76
|
-
```sh
|
|
77
|
-
npx skills add ericfortis/mockaton
|
|
78
|
-
```
|
|
79
89
|
<!-- SKILLS_IGNORE_END -->
|
|
80
90
|
|
|
81
91
|
|
|
@@ -135,3 +145,8 @@ export default async function (req: IncomingMessage, response: OutgoingMessage)
|
|
|
135
145
|
})
|
|
136
146
|
}
|
|
137
147
|
```
|
|
148
|
+
|
|
149
|
+
## Docs
|
|
150
|
+
- [Configuration](https://mockaton.com/config): CLI and mockaton.config.js
|
|
151
|
+
- [API](https://mockaton.com/api): Programatically, you can delay a route, select a different mock file, etc.
|
|
152
|
+
|
package/package.json
CHANGED
package/skills/mockaton/SKILL.md
CHANGED
|
@@ -4,36 +4,34 @@ description: Generates and serves mock HTTP APIs. Use when creating, editing, or
|
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
# Installation ([more options ↗](https://mockaton.com/installation))
|
|
7
|
-
Mockaton has no dependencies.
|
|
8
|
-
|
|
9
7
|
```sh
|
|
10
8
|
npm install -g mockaton
|
|
11
9
|
```
|
|
10
|
+
<i>Mockaton is a Node.js app with no dependencies.</i>
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
## Basic Usage
|
|
14
17
|
```sh
|
|
15
|
-
mockaton --port 2020 my-mocks-dir
|
|
18
|
+
mockaton --port 2020 my-mocks-dir
|
|
16
19
|
```
|
|
17
20
|
|
|
18
|
-
Mockaton will serve the files on the given directory. It's a file-system based router
|
|
19
|
-
filenames can have dynamic parameters.
|
|
20
|
-
|
|
21
|
-
Similarly, each route can have different response status
|
|
21
|
+
Mockaton will serve the files on the given directory. It's a file-system based router in which
|
|
22
|
+
filenames can have dynamic parameters and comments. For paraments use square brackets `[]`,
|
|
23
|
+
and for comments use parentheses `()`. Comments are handy because this way each route
|
|
24
|
+
can have different mock file variants. Similarly, each route can have different response status
|
|
25
|
+
code variants.
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
| Route | Filename | Description |
|
|
25
29
|
| -----| -----| ---|
|
|
26
|
-
| /api/company/123 | api/company/[id].GET.200.ts | `[id]` is a dynamic parameter. `.ts`, and `.js` are sent as JSON by default |
|
|
27
|
-
| /media/avatar.png | media/avatar.png | Statics assets don't need the above extension |
|
|
28
|
-
| /api/login | api/login(invalid attempt).POST.401.ts |
|
|
29
|
-
| /api/login | api/login(default).GET.200.ts | `(default)` is a special comment; otherwise, the first mock variant in alphabetical order wins |
|
|
30
|
-
| /api/login | api/login(locked out user).POST.423.json | `.json` is allowed too |
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## Docs
|
|
34
|
-
- [Configuration: CLI and mockaton.config.js](https://mockaton.com/config).
|
|
35
|
-
- [Programmatic API](https://mockaton.com/api), in which
|
|
36
|
-
you can delay a route, select a different mock file, such as a 500 error, among other options.
|
|
30
|
+
| /api/company/123 | api/company/[id].GET.200.ts | `[id]` is a dynamic parameter. `.ts`, and `.js` are sent as JSON by default. |
|
|
31
|
+
| /media/avatar.png | media/avatar.png | Statics assets don't need the above extension. |
|
|
32
|
+
| /api/login | api/login(invalid attempt).POST.401.ts | Anything within parenthesis is a comment. They are ignored when routing. |
|
|
33
|
+
| /api/login | api/login(default).GET.200.ts | `(default)` is a special comment; otherwise, the first mock variant in alphabetical order wins. |
|
|
34
|
+
| /api/login | api/login(locked out user).POST.423.json | `.json` is allowed too. |
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
|
|
@@ -93,4 +91,8 @@ export default async function (req: IncomingMessage, response: OutgoingMessage)
|
|
|
93
91
|
name: 'Acme, Inc.'
|
|
94
92
|
})
|
|
95
93
|
}
|
|
96
|
-
```
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Docs
|
|
97
|
+
- [Configuration](https://mockaton.com/config): CLI and mockaton.config.js
|
|
98
|
+
- [API](https://mockaton.com/api): Programatically, you can delay a route, select a different mock file, etc.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { API } from './ApiConstants.js'
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Client for controlling Mockaton via its HTTP API
|
|
6
|
+
* @see https://mockaton.com/api
|
|
7
|
+
* @example const mockaton = new Commander('http://localhost:2020')
|
|
8
|
+
*/
|
|
5
9
|
export class Commander {
|
|
6
|
-
/** @param {string}
|
|
7
|
-
constructor(
|
|
8
|
-
this.addr =
|
|
10
|
+
/** @param {string} mockatonServerAddr */
|
|
11
|
+
constructor(mockatonServerAddr) {
|
|
12
|
+
this.addr = mockatonServerAddr
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
/** @returns {Promise<Response>} */
|
|
@@ -16,38 +20,35 @@ export class Commander {
|
|
|
16
20
|
|
|
17
21
|
reset = () => this.#patch(API.reset)
|
|
18
22
|
|
|
19
|
-
setGlobalDelay = delay => this.#patch(API.globalDelay, delay)
|
|
20
|
-
setGlobalDelayJitter = jitterPct => this.#patch(API.globalDelayJitter, jitterPct)
|
|
21
|
-
setCorsAllowed = value => this.#patch(API.cors, value)
|
|
22
|
-
setWatchMocks = enabled => this.#patch(API.watchMocks, enabled)
|
|
23
|
-
setProxyFallback = proxyAddr => this.#patch(API.fallback, proxyAddr)
|
|
24
|
-
setCollectProxied = shouldCollect => this.#patch(API.collectProxied, shouldCollect)
|
|
25
|
-
|
|
26
|
-
/** @returns {JsonPromise<State.cookies>} */
|
|
27
|
-
selectCookie = label => this.#patch(API.cookies, label)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
bulkSelectByComment = comment => this.#patch(API.bulkSelect, comment)
|
|
31
23
|
|
|
32
24
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
33
25
|
select = file => this.#patch(API.select, file)
|
|
34
26
|
|
|
27
|
+
bulkSelectByComment = comment => this.#patch(API.bulkSelect, comment)
|
|
35
28
|
|
|
36
29
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
37
30
|
toggleStatus = (method, urlMask, status) => this.#patch(API.toggleStatus, [method, urlMask, status])
|
|
38
31
|
|
|
32
|
+
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
33
|
+
setRouteIsDelayed = (method, urlMask, delayed) => this.#patch(API.delay, [method, urlMask, delayed])
|
|
39
34
|
|
|
40
35
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
41
36
|
setRouteIsProxied = (method, urlMask, proxied) => this.#patch(API.proxied, [method, urlMask, proxied])
|
|
42
37
|
|
|
43
|
-
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
44
|
-
setRouteIsDelayed = (method, urlMask, delayed) => this.#patch(API.delay, [method, urlMask, delayed])
|
|
45
38
|
|
|
39
|
+
/** @returns {JsonPromise<State.cookies>} */
|
|
40
|
+
selectCookie = label => this.#patch(API.cookies, label)
|
|
41
|
+
|
|
42
|
+
setGlobalDelay = delay => this.#patch(API.globalDelay, delay)
|
|
43
|
+
setGlobalDelayJitter = jitterPct => this.#patch(API.globalDelayJitter, jitterPct)
|
|
44
|
+
setCorsAllowed = value => this.#patch(API.cors, value)
|
|
45
|
+
setWatchMocks = enabled => this.#patch(API.watchMocks, enabled)
|
|
46
|
+
setProxyFallback = proxyAddr => this.#patch(API.fallback, proxyAddr)
|
|
47
|
+
setCollectProxied = shouldCollect => this.#patch(API.collectProxied, shouldCollect)
|
|
46
48
|
|
|
47
49
|
writeMock = (file, content) => this.#patch(API.writeMock, [file, content])
|
|
48
50
|
deleteMock = file => this.#patch(API.deleteMock, file)
|
|
49
51
|
|
|
50
|
-
|
|
51
52
|
/** @returns {JsonPromise<State>} */
|
|
52
53
|
getState = () => fetch(this.addr + API.state)
|
|
53
54
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createElement as r, t, restoreFocus, Fragment } from './utils/dom.js'
|
|
2
2
|
|
|
3
|
-
import { store } from './
|
|
3
|
+
import { store } from './store.js'
|
|
4
4
|
import { API } from './ApiConstants.js'
|
|
5
|
-
import { Header } from './
|
|
5
|
+
import { Header } from './Header.js'
|
|
6
6
|
import { adoptSheet } from './utils/css.js'
|
|
7
|
-
import { PayloadViewer, previewMock } from './
|
|
8
|
-
import { MockList, initKeyboardNavigation, renderRow } from './
|
|
7
|
+
import { PayloadViewer, previewMock } from './PayloadViewer.js'
|
|
8
|
+
import { MockList, initKeyboardNavigation, renderRow } from './MockList.js'
|
|
9
9
|
|
|
10
|
-
import CSS from './
|
|
11
|
-
adoptSheet(CSS, './
|
|
10
|
+
import CSS from './App.css' with { type: 'css' }
|
|
11
|
+
adoptSheet(CSS, './App.css')
|
|
12
12
|
|
|
13
13
|
store.onError = onError
|
|
14
14
|
store.render = render
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createElement as r, t } from './utils/dom.js'
|
|
2
|
-
import { Logo, HelpIcon } from './
|
|
2
|
+
import { Logo, HelpIcon } from './icons.js'
|
|
3
3
|
import { adoptSheet } from './utils/css.js'
|
|
4
|
-
import { store } from './
|
|
4
|
+
import { store } from './store.js'
|
|
5
5
|
|
|
6
|
-
import CSS from './
|
|
7
|
-
adoptSheet(CSS, './
|
|
6
|
+
import CSS from './Header.css' with { type: 'css' }
|
|
7
|
+
adoptSheet(CSS, './Header.css')
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
export function Header() {
|
package/src/client/IndexHtml.js
CHANGED
|
@@ -13,7 +13,7 @@ export const IndexHtml = (hotReloadEnabled, version) => `
|
|
|
13
13
|
<meta charset="UTF-8">
|
|
14
14
|
<base href="${API.root}/">
|
|
15
15
|
|
|
16
|
-
<script type="module" src="
|
|
16
|
+
<script type="module" src="App.js"></script>
|
|
17
17
|
<link rel="preload" href="${API.state}" as="fetch" crossorigin>
|
|
18
18
|
|
|
19
19
|
<link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createElement as r, t, Fragment } from './utils/dom.js'
|
|
2
2
|
|
|
3
|
-
import { store } from './
|
|
4
|
-
import { previewMock } from './
|
|
3
|
+
import { store } from './store.js'
|
|
4
|
+
import { previewMock } from './PayloadViewer.js'
|
|
5
5
|
import { classNames, adoptSheet } from './utils/css.js'
|
|
6
|
-
import { TimerIcon, CloudIcon, ChevronDownIcon } from './
|
|
6
|
+
import { TimerIcon, CloudIcon, ChevronDownIcon } from './icons.js'
|
|
7
7
|
|
|
8
|
-
import CSS from './
|
|
9
|
-
adoptSheet(CSS, './
|
|
8
|
+
import CSS from './MockList.css' with { type: 'css' }
|
|
9
|
+
adoptSheet(CSS, './MockList.css')
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
export function MockList() {
|
|
@@ -76,9 +76,8 @@ function TableContent() {
|
|
|
76
76
|
|
|
77
77
|
if (store.groupByMethod)
|
|
78
78
|
return Object.keys(store.brokersByMethod).map(method => Fragment(
|
|
79
|
-
r('div', {
|
|
80
|
-
|
|
81
|
-
}, method),
|
|
79
|
+
r('div', { className: classNames(CSS.TableHeading, store.canProxy && CSS.canProxy) },
|
|
80
|
+
method),
|
|
82
81
|
FolderGroups(store.folderGroupsByMethod(method))))
|
|
83
82
|
|
|
84
83
|
return FolderGroups(store.folderGroupsByMethod('*'))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createElement as r, t } from './utils/dom.js'
|
|
2
2
|
import { parseFilename } from './Filename.js'
|
|
3
|
-
import { store } from './
|
|
3
|
+
import { store } from './store.js'
|
|
4
4
|
import { adoptSheet } from './utils/css.js'
|
|
5
5
|
|
|
6
|
-
import CSS from './
|
|
7
|
-
adoptSheet(CSS, './
|
|
6
|
+
import CSS from './PayloadViewer.css' with { type: 'css' }
|
|
7
|
+
adoptSheet(CSS, './PayloadViewer.css')
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
const titleRef = {}
|
package/src/server/Api.js
CHANGED
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { join, relative } from 'node:path'
|
|
7
|
+
|
|
7
8
|
import { write, rm, isFile, resolveIn } from './utils/fs.js'
|
|
9
|
+
import { removeQueryStringAndFragment } from './utils/HttpIncomingMessage.js'
|
|
10
|
+
import { sseClientHotReload } from './utils/WatcherDevClient.js'
|
|
8
11
|
|
|
9
|
-
import openapi from '../../www/src/assets/openapi.json' with { type: 'json' }
|
|
10
12
|
import pkgJSON from '../../package.json' with { type: 'json' }
|
|
11
|
-
|
|
12
|
-
import { sseClientHotReload } from './utils/WatcherDevClient.js'
|
|
13
|
-
import { stopMocksDirWatcher, sseClientSyncVersion, uiSyncVersion, watchMocksDir } from './Watcher.js'
|
|
13
|
+
import openapi from '../../www/src/assets/openapi.json' with { type: 'json' }
|
|
14
14
|
|
|
15
15
|
import { API } from '../client/ApiConstants.js'
|
|
16
16
|
import { IndexHtml, CSP } from '../client/IndexHtml.js'
|
|
17
17
|
|
|
18
|
-
import { cookie } from './
|
|
19
|
-
import { config, ConfigValidator } from './config.js'
|
|
20
|
-
import * as
|
|
21
|
-
import
|
|
18
|
+
import { cookie } from './stores/cookies.js'
|
|
19
|
+
import { config, ConfigValidator, reinitConfig } from './stores/config.js'
|
|
20
|
+
import * as brokers from './stores/brokers.js'
|
|
21
|
+
import * as Watcher from './stores/Watcher.js'
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
export const CLIENT_ASSETS = join(import.meta.dirname, '../client')
|
|
@@ -32,7 +32,7 @@ const getReqs = new Map([
|
|
|
32
32
|
|
|
33
33
|
[API.root, serveDashboard],
|
|
34
34
|
[API.state, getState],
|
|
35
|
-
[API.syncVersion, sseClientSyncVersion],
|
|
35
|
+
[API.syncVersion, Watcher.sseClientSyncVersion],
|
|
36
36
|
|
|
37
37
|
[API.watchHotReload, onDevWatch],
|
|
38
38
|
[API.openAPI, (_, response) => response.json(openapi)],
|
|
@@ -94,9 +94,9 @@ function serveDashboard(_, response) {
|
|
|
94
94
|
function getState(_, response) {
|
|
95
95
|
response.json({
|
|
96
96
|
cookies: cookie.list(),
|
|
97
|
-
comments:
|
|
97
|
+
comments: brokers.extractAllComments(),
|
|
98
98
|
|
|
99
|
-
brokersByMethod:
|
|
99
|
+
brokersByMethod: brokers.all(),
|
|
100
100
|
|
|
101
101
|
delay: config.delay,
|
|
102
102
|
delayJitter: config.delayJitter,
|
|
@@ -118,10 +118,11 @@ function onDevWatch(req, response) {
|
|
|
118
118
|
/** # PATCH */
|
|
119
119
|
|
|
120
120
|
function reset(_, response) {
|
|
121
|
-
|
|
121
|
+
reinitConfig()
|
|
122
|
+
brokers.init()
|
|
122
123
|
cookie.init(config.cookies)
|
|
123
124
|
response.ok()
|
|
124
|
-
|
|
125
|
+
Watcher.emitChange()
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
|
|
@@ -133,7 +134,7 @@ async function setCorsAllowed(req, response) {
|
|
|
133
134
|
else {
|
|
134
135
|
config.corsAllowed = corsAllowed
|
|
135
136
|
response.ok()
|
|
136
|
-
|
|
137
|
+
Watcher.emitChange()
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -146,7 +147,7 @@ async function setGlobalDelay(req, response) {
|
|
|
146
147
|
else {
|
|
147
148
|
config.delay = delay
|
|
148
149
|
response.ok()
|
|
149
|
-
|
|
150
|
+
Watcher.emitChange()
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
@@ -158,7 +159,7 @@ async function setGlobalDelayJitter(req, response) {
|
|
|
158
159
|
else {
|
|
159
160
|
config.delayJitter = jitter
|
|
160
161
|
response.ok()
|
|
161
|
-
|
|
162
|
+
Watcher.emitChange()
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -170,7 +171,7 @@ async function selectCookie(req, response) {
|
|
|
170
171
|
response.unprocessable(error?.message || error)
|
|
171
172
|
else {
|
|
172
173
|
response.json(cookie.list())
|
|
173
|
-
|
|
174
|
+
Watcher.emitChange()
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
@@ -183,7 +184,7 @@ async function setProxyFallback(req, response) {
|
|
|
183
184
|
else {
|
|
184
185
|
config.proxyFallback = fallback
|
|
185
186
|
response.ok()
|
|
186
|
-
|
|
187
|
+
Watcher.emitChange()
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
@@ -195,7 +196,7 @@ async function setCollectProxied(req, response) {
|
|
|
195
196
|
else {
|
|
196
197
|
config.collectProxied = collectProxied
|
|
197
198
|
response.ok()
|
|
198
|
-
|
|
199
|
+
Watcher.emitChange()
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -203,41 +204,41 @@ async function setCollectProxied(req, response) {
|
|
|
203
204
|
|
|
204
205
|
async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
205
206
|
const comment = await req.json()
|
|
206
|
-
|
|
207
|
+
brokers.setMocksMatchingComment(comment)
|
|
207
208
|
response.ok()
|
|
208
|
-
|
|
209
|
+
Watcher.emitChange()
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
async function selectMock(req, response) {
|
|
213
214
|
const file = await req.json()
|
|
214
|
-
const broker =
|
|
215
|
+
const broker = brokers.brokerByFilename(file)
|
|
215
216
|
if (!broker || !broker.hasMock(file))
|
|
216
217
|
response.unprocessable(`Missing Mock: ${file}`)
|
|
217
218
|
else {
|
|
218
219
|
broker.selectFile(file)
|
|
219
220
|
response.json(broker)
|
|
220
|
-
|
|
221
|
+
Watcher.emitChange()
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
|
|
225
226
|
async function toggleRouteStatus(req, response) {
|
|
226
227
|
const [method, urlMask, status] = await req.json()
|
|
227
|
-
const broker =
|
|
228
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
228
229
|
if (!broker)
|
|
229
230
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
230
231
|
else {
|
|
231
232
|
broker.toggleStatus(status)
|
|
232
233
|
response.json(broker)
|
|
233
|
-
|
|
234
|
+
Watcher.emitChange()
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
|
|
238
239
|
async function setRouteIsDelayed(req, response) {
|
|
239
240
|
const [method, urlMask, delayed] = await req.json()
|
|
240
|
-
const broker =
|
|
241
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
241
242
|
if (!broker)
|
|
242
243
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
243
244
|
else if (typeof delayed !== 'boolean')
|
|
@@ -245,14 +246,14 @@ async function setRouteIsDelayed(req, response) {
|
|
|
245
246
|
else {
|
|
246
247
|
broker.setDelayed(delayed)
|
|
247
248
|
response.json(broker)
|
|
248
|
-
|
|
249
|
+
Watcher.emitChange()
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
|
|
253
254
|
async function setRouteIsProxied(req, response) {
|
|
254
255
|
const [method, urlMask, proxied] = await req.json()
|
|
255
|
-
const broker =
|
|
256
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
256
257
|
if (!broker)
|
|
257
258
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
258
259
|
else if (typeof proxied !== 'boolean')
|
|
@@ -262,51 +263,53 @@ async function setRouteIsProxied(req, response) {
|
|
|
262
263
|
else {
|
|
263
264
|
broker.setProxied(proxied)
|
|
264
265
|
response.json(broker)
|
|
265
|
-
|
|
266
|
+
Watcher.emitChange()
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
|
|
270
271
|
async function writeMock(req, response) {
|
|
271
|
-
if (config.readOnly)
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
if (config.readOnly) {
|
|
273
|
+
response.forbidden('Forbidden: Mockaton is in read-only mode. See config.readOnly')
|
|
274
|
+
return
|
|
275
|
+
}
|
|
274
276
|
const [file, content] = await req.json()
|
|
275
|
-
if (typeof file !== 'string')
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
if (typeof file !== 'string') {
|
|
278
|
+
response.unprocessable('Invalid or missing filename. Expected: JSON [filename, content]')
|
|
279
|
+
return
|
|
280
|
+
}
|
|
278
281
|
const path = await resolveIn(config.mocksDir, file)
|
|
279
|
-
if (!path)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
if (!path) {
|
|
283
|
+
response.forbidden('Filename path resolves outside config.mocksDir')
|
|
284
|
+
return
|
|
285
|
+
}
|
|
282
286
|
await write(path, content)
|
|
283
|
-
|
|
284
287
|
if (!config.watcherEnabled) {
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
brokers.registerMock(file, true)
|
|
289
|
+
Watcher.emitChange()
|
|
287
290
|
}
|
|
288
291
|
response.ok()
|
|
289
292
|
}
|
|
290
293
|
|
|
291
294
|
|
|
292
295
|
async function deleteMock(req, response) {
|
|
293
|
-
if (config.readOnly)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
if (config.readOnly) {
|
|
297
|
+
response.forbidden('Forbidden: Mockaton is in read-only mode. See config.readOnly')
|
|
298
|
+
return
|
|
299
|
+
}
|
|
296
300
|
const file = await req.json()
|
|
297
301
|
const path = await resolveIn(config.mocksDir, file)
|
|
298
302
|
|
|
299
303
|
if (!path)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await rm(path)
|
|
304
|
+
response.forbidden('Filename path resolves outside config.mocksDir')
|
|
305
|
+
else if (!isFile(path))
|
|
306
|
+
response.unprocessable(`Missing Mock: ${file}`)
|
|
307
|
+
else
|
|
308
|
+
await rm(path)
|
|
306
309
|
|
|
307
310
|
if (!config.watcherEnabled) {
|
|
308
|
-
|
|
309
|
-
|
|
311
|
+
brokers.unregisterMock(file)
|
|
312
|
+
Watcher.emitChange()
|
|
310
313
|
}
|
|
311
314
|
response.ok()
|
|
312
315
|
}
|
|
@@ -319,9 +322,9 @@ async function setWatchMocks(req, response) {
|
|
|
319
322
|
response.unprocessable(`Expected boolean for "watchMocks"`)
|
|
320
323
|
else {
|
|
321
324
|
if (enabled)
|
|
322
|
-
watchMocksDir()
|
|
325
|
+
Watcher.watchMocksDir()
|
|
323
326
|
else
|
|
324
|
-
|
|
327
|
+
Watcher.unwatchMocksDir()
|
|
325
328
|
response.ok()
|
|
326
329
|
}
|
|
327
330
|
}
|
package/src/server/MockBroker.js
CHANGED
|
@@ -108,21 +108,15 @@ class UrlMatcher {
|
|
|
108
108
|
this.#urlRegex = this.#buildUrlRegex(file)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
#escapeRegex(str) {
|
|
112
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
111
|
#buildUrlRegex(file) {
|
|
116
112
|
let { urlMask } = parseFilename(file)
|
|
117
113
|
urlMask = removeQueryStringAndFragment(urlMask)
|
|
118
|
-
urlMask =
|
|
114
|
+
urlMask = RegExp.escape(urlMask)
|
|
119
115
|
urlMask = this.#disregardVariables(urlMask)
|
|
120
116
|
return new RegExp('^' + urlMask + '/*$')
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
#disregardVariables(
|
|
124
|
-
return str.replace(/\\\[.*?\\]/g, '[^/]+')
|
|
125
|
-
}
|
|
119
|
+
#disregardVariables = str => str.replace(/\\\[.*?\\]/g, '[^/]+')
|
|
126
120
|
|
|
127
121
|
// Appending a '/' so URLs ending with variables don't match
|
|
128
122
|
// URLs that have a path after that variable. For example,
|