mockaton 13.11.1 → 13.11.3

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.
Files changed (35) hide show
  1. package/README.md +11 -8
  2. package/package.json +1 -1
  3. package/skills/mockaton/SKILL.md +8 -5
  4. package/src/client/ApiConstants.js +1 -1
  5. package/src/client/IndexHtml.js +1 -1
  6. package/src/client/app-header.css +231 -0
  7. package/src/client/app-header.js +13 -6
  8. package/src/client/app-mock-list.css +340 -0
  9. package/src/client/app-mock-list.js +370 -0
  10. package/src/client/app-payload-viewer.css +90 -0
  11. package/src/client/app-payload-viewer.js +4 -4
  12. package/src/client/app-store.js +1 -0
  13. package/src/client/app.css +0 -654
  14. package/src/client/app.js +18 -360
  15. package/src/client/utils/css.js +7 -0
  16. package/src/client/utils/watcherDev.js +4 -1
  17. package/src/server/Api.js +54 -53
  18. package/src/server/MockDispatcher.js +6 -5
  19. package/src/server/Mockaton.js +11 -12
  20. package/src/server/Mockaton.test.js +65 -51
  21. package/src/server/ProxyRelay.js +3 -2
  22. package/src/server/UrlParsers.js +1 -1
  23. package/src/server/UrlParsers.test.js +1 -1
  24. package/src/server/cli.js +37 -33
  25. package/src/server/cli.test.js +2 -4
  26. package/src/server/{Watcher.js → stores/Watcher.js} +18 -9
  27. package/src/server/{mockBrokersCollection.js → stores/brokers.js} +3 -3
  28. package/src/server/{config.js → stores/config.js} +28 -14
  29. package/src/server/utils/HttpServerResponse.test.js +5 -7
  30. package/src/server/utils/WatcherDevClient.js +1 -1
  31. package/src/server/utils/openInBrowser.js +15 -10
  32. package/www/src/assets/openapi.json +1 -1
  33. /package/src/server/{resolverBypassImportCache.js → ResolverBypassImportCache.js} +0 -0
  34. /package/src/server/{resolverResolveExtensionless.js → ResolverResolveExtensionless.js} +0 -0
  35. /package/src/server/{cookie.js → stores/cookies.js} +0 -0
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  ## [Docs ↗](https://mockaton.com) | [Changelog ↗](https://mockaton.com/changelog) | [Skills](skills/mockaton/SKILL.md)
9
9
 
10
- Mockaton is an HTTP mock server for simulating APIs, designed
11
- for testing difficult to reproduce backend states with minimal setup.
10
+ Mockaton is an HTTP mock server for simulating APIs. With it, you can
11
+ test API errors, edge cases, and difficult to reproduce states in general.
12
12
 
13
13
  <picture>
14
14
  <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">
@@ -30,13 +30,20 @@ Test it:
30
30
  ```sh
31
31
  curl localhost:2020/api/user
32
32
  ```
33
- Dashboard: [localhost:2020/mockaton](http://localhost:2020/mockaton)
34
33
 
35
34
  <!-- SKILLS_IGNORE_END -->
36
35
 
36
+
37
+ # Installation ([more options ↗](https://mockaton.com/installation))
38
+ Mockaton has no dependencies.
39
+
40
+ ```sh
41
+ npm install -g mockaton
42
+ ```
43
+
37
44
  ## Basic Usage
38
45
  ```sh
39
- npx mockaton --port 2020 my-mocks-dir/
46
+ mockaton --port 2020 my-mocks-dir/
40
47
  ```
41
48
 
42
49
  Mockaton will serve the files on the given directory. It's a file-system based router, so
@@ -71,10 +78,6 @@ npx skills add ericfortis/mockaton
71
78
  ```
72
79
  <!-- SKILLS_IGNORE_END -->
73
80
 
74
- ## Installation ([more options ↗](https://mockaton.com/installation))
75
- ```sh
76
- npm install mockaton
77
- ```
78
81
 
79
82
  ## How to create mocks?
80
83
  Write it to your mocks directory. `.ts` files are served as JSON by default.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "13.11.1",
5
+ "version": "13.11.3",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -3,9 +3,16 @@ name: Mockaton
3
3
  description: Generates and serves mock HTTP APIs. Use when creating, editing, or reasoning about mock endpoints.
4
4
  user-invocable: false
5
5
  ---
6
+ # Installation ([more options ↗](https://mockaton.com/installation))
7
+ Mockaton has no dependencies.
8
+
9
+ ```sh
10
+ npm install -g mockaton
11
+ ```
12
+
6
13
  ## Basic Usage
7
14
  ```sh
8
- npx mockaton --port 2020 my-mocks-dir/
15
+ mockaton --port 2020 my-mocks-dir/
9
16
  ```
10
17
 
11
18
  Mockaton will serve the files on the given directory. It's a file-system based router, so
@@ -30,10 +37,6 @@ Similarly, each route can have different response status code variants.
30
37
 
31
38
 
32
39
 
33
- ## Installation ([more options ↗](https://mockaton.com/installation))
34
- ```sh
35
- npm install mockaton
36
- ```
37
40
 
38
41
  ## How to create mocks?
39
42
  Write it to your mocks directory. `.ts` files are served as JSON by default.
@@ -3,7 +3,7 @@
3
3
  const MOUNT = '/mockaton'
4
4
 
5
5
  export const API = {
6
- dashboard: MOUNT,
6
+ root: MOUNT,
7
7
 
8
8
  reset: MOUNT + '/reset',
9
9
  select: MOUNT + '/select',
@@ -11,7 +11,7 @@ export const IndexHtml = (hotReloadEnabled, version) => `
11
11
  <html lang="en-US">
12
12
  <head>
13
13
  <meta charset="UTF-8">
14
- <base href="${API.dashboard}/">
14
+ <base href="${API.root}/">
15
15
 
16
16
  <script type="module" src="app.js"></script>
17
17
  <link rel="preload" href="${API.state}" as="fetch" crossorigin>
@@ -0,0 +1,231 @@
1
+ .Header {
2
+ display: flex;
3
+ flex: 0 0 100%;
4
+ padding: 16px;
5
+ background: var(--colorBgHeader);
6
+
7
+ .Logo {
8
+ align-self: end;
9
+ margin-right: 18px;
10
+ margin-bottom: 2px;
11
+ transition: opacity 240ms ease-in-out;
12
+
13
+ svg {
14
+ width: 122px;
15
+ pointer-events: none;
16
+ fill: var(--colorText);
17
+ }
18
+ }
19
+
20
+ @media (max-width: 570px) {
21
+ .Logo {
22
+ display: none;
23
+ }
24
+ }
25
+
26
+ &:has(.FallbackBackend) {
27
+ @media (max-width: 740px) {
28
+ .Logo {
29
+ display: none;
30
+ }
31
+ }
32
+ }
33
+
34
+ > div {
35
+ display: flex;
36
+ width: 100%;
37
+ flex-wrap: wrap;
38
+ align-items: flex-end;
39
+ gap: 16px 12px;
40
+
41
+ &:has(.FallbackBackend) {
42
+ @media (max-width: 600px) {
43
+ .HelpLink {
44
+ margin-left: unset;
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ .GlobalDelayWrap {
51
+ display: flex;
52
+ }
53
+
54
+
55
+ .ResetButton {
56
+ padding: 6px 12px;
57
+ border: 1px solid var(--colorRed);
58
+ margin-right: 8px;
59
+ margin-left: 4px;
60
+ background: transparent;
61
+ border-radius: 50px;
62
+ color: var(--colorRed);
63
+ box-shadow: var(--boxShadowRimDrop);
64
+
65
+ @media (prefers-color-scheme: dark) {
66
+ color: var(--colorText);
67
+ }
68
+
69
+ &:hover {
70
+ background: var(--colorRed);
71
+ color: white;
72
+ }
73
+ }
74
+
75
+ .HelpLink {
76
+ width: 24px;
77
+ height: 24px;
78
+ flex-shrink: 0;
79
+ align-self: end;
80
+ margin-bottom: 3px;
81
+ margin-left: auto;
82
+ box-shadow: var(--boxShadowRimDrop);
83
+ opacity: 0.8;
84
+ border-radius: 50%;
85
+ fill: var(--colorBgHeader);
86
+ background: var(--colorLabel);
87
+
88
+ &:hover {
89
+ background: var(--colorAccent);
90
+ opacity: 1;
91
+ }
92
+
93
+ svg {
94
+ transform: scale(.7);
95
+ }
96
+ }
97
+ }
98
+
99
+ .GlobalDelay,
100
+ .GlobalDelayJitter,
101
+ .CookieSelector,
102
+ .FallbackBackend {
103
+ width: 116px;
104
+
105
+ span {
106
+ display: flex;
107
+ align-items: center;
108
+ margin-left: 8px;
109
+ white-space: nowrap;
110
+ color: var(--colorLabel);
111
+ font-size: 11px;
112
+ gap: 4px;
113
+ transition: color 200ms ease-out;
114
+ }
115
+
116
+ &:has(input:focus-visible),
117
+ &:hover {
118
+ span {
119
+ color: var(--colorText);
120
+ }
121
+ }
122
+
123
+ input[type=url],
124
+ input[type=number],
125
+ select {
126
+ width: 100%;
127
+ height: 28px;
128
+ padding: 4px 8px;
129
+ margin-top: 2px;
130
+ color: var(--colorText);
131
+ font-size: 11px;
132
+ background-color: var(--colorBgHeaderField);
133
+ border-radius: var(--radius);
134
+ box-shadow: var(--boxShadowRimDrop);
135
+ transition: background-color ease-in-out 120ms;
136
+
137
+ &:hover {
138
+ background-color: var(--colorBgFieldHover);
139
+ }
140
+ }
141
+
142
+ &.GlobalDelay,
143
+ &.GlobalDelayJitter {
144
+ input {
145
+ &&::selection {
146
+ background-color: transparent;
147
+ }
148
+
149
+ &::-webkit-outer-spin-button,
150
+ &::-webkit-inner-spin-button {
151
+ -webkit-appearance: none;
152
+ margin: 0;
153
+ }
154
+ }
155
+ }
156
+
157
+ &.GlobalDelay {
158
+ position: relative;
159
+ width: 76px;
160
+
161
+ input {
162
+ padding-right: 6px;
163
+ border-bottom-right-radius: 0;
164
+ border-top-right-radius: 0;
165
+ }
166
+
167
+ svg {
168
+ width: 12px;
169
+ height: 12px;
170
+ border: 1px solid var(--colorLabel);
171
+ stroke-width: 3px;
172
+ border-radius: 50%;
173
+ }
174
+
175
+ &:focus-within {
176
+ z-index: 100;
177
+ }
178
+ }
179
+
180
+ &.GlobalDelayJitter {
181
+ width: 80px;
182
+
183
+ &:focus-within {
184
+ z-index: 100;
185
+ }
186
+
187
+ span {
188
+ margin-left: 8px;
189
+ }
190
+ input {
191
+ border-bottom-left-radius: 0;
192
+ border-top-left-radius: 0;
193
+ }
194
+ }
195
+
196
+ &.CookieSelector {
197
+ width: 100px;
198
+ }
199
+
200
+ &.FallbackBackend {
201
+ position: relative;
202
+ width: 160px;
203
+
204
+ .SaveProxiedCheckbox {
205
+ position: absolute;
206
+ top: 0;
207
+ right: 0;
208
+ display: flex;
209
+ width: auto;
210
+ min-width: unset;
211
+ align-items: center;
212
+ gap: 4px;
213
+
214
+ input + .checkboxBody {
215
+ margin: 0;
216
+ margin-right: 4px;
217
+ }
218
+ input:enabled + .checkboxBody {
219
+ cursor: pointer;
220
+ }
221
+
222
+ input:disabled {
223
+ cursor: not-allowed;
224
+ }
225
+ input:disabled + .checkboxBody {
226
+ opacity: 0.8;
227
+ cursor: not-allowed;
228
+ }
229
+ }
230
+ }
231
+ }
@@ -1,15 +1,15 @@
1
1
  import { createElement as r, t } from './utils/dom.js'
2
2
  import { Logo, HelpIcon } from './graphics.js'
3
+ import { adoptSheet } from './utils/css.js'
3
4
  import { store } from './app-store.js'
4
5
 
5
- import CSS from './app.css' with { type: 'css' }
6
- import { extractClassNames } from './utils/css.js'
7
- Object.assign(CSS, extractClassNames(CSS))
6
+ import CSS from './app-header.css' with { type: 'css' }
7
+ adoptSheet(CSS, './app-header.css')
8
8
 
9
9
 
10
10
  export function Header() {
11
11
  return (
12
- r('header', null,
12
+ r('header', { className: CSS.Header },
13
13
  r('a', {
14
14
  className: CSS.Logo,
15
15
  href: 'https://mockaton.com',
@@ -56,7 +56,7 @@ function GlobalDelayJitterField() {
56
56
  min: 0,
57
57
  max: 300,
58
58
  step: 10,
59
- value: (store.delayJitter * 100).toFixed(0),
59
+ value: parseInt((store.delayJitter * 100).toFixed(0), 10),
60
60
  onChange
61
61
  })
62
62
  }
@@ -145,6 +145,12 @@ function SlidableNumberField({ name, className, label, onChange, min, max, step,
145
145
  return Math.min(Math.max(val, min), max)
146
146
  }
147
147
 
148
+ function cursorFor(val) {
149
+ if (val === max) return 'w-resize'
150
+ if (val === min) return 'e-resize'
151
+ return 'col-resize'
152
+ }
153
+
148
154
  function onPointerDown(event) {
149
155
  let lastX = event.clientX
150
156
  const input = /** @type {HTMLInputElement} */ event.target
@@ -171,6 +177,7 @@ function SlidableNumberField({ name, className, label, onChange, min, max, step,
171
177
  else if (ev.altKey) s /= 2
172
178
 
173
179
  input.valueAsNumber = clamp(input.valueAsNumber + Math.sign(diff) * s)
180
+ input.style.cursor = cursorFor(input.valueAsNumber)
174
181
  }
175
182
  }
176
183
  }
@@ -181,7 +188,7 @@ function SlidableNumberField({ name, className, label, onChange, min, max, step,
181
188
  r('input', {
182
189
  type: 'number',
183
190
  autocomplete: 'none',
184
- style: { cursor: 'col-resize' },
191
+ style: { cursor: cursorFor(value) },
185
192
  min,
186
193
  max,
187
194
  step,
@@ -0,0 +1,340 @@
1
+ .SubToolbar {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ padding-right: 14px;
6
+ padding-left: 16px;
7
+ border-bottom: 1px solid var(--colorBorder);
8
+ background: var(--colorBgHeader);
9
+ }
10
+
11
+ .GroupByMethod {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: 6px;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .BulkSelector {
19
+ span {
20
+ color: var(--colorLabel);
21
+ }
22
+ select {
23
+ width: 110px;
24
+ padding: 6px 8px;
25
+ margin-left: 4px;
26
+ background-image: none;
27
+ text-align-last: center;
28
+ color: var(--colorText);
29
+ font-size: 11px;
30
+ background-color: var(--colorBgHeaderField);
31
+ border-radius: var(--radius);
32
+ box-shadow: var(--boxShadowRimDrop);
33
+ transition: background-color ease-in-out 120ms;
34
+
35
+ &:hover {
36
+ background-color: var(--colorBgFieldHover);
37
+ }
38
+ }
39
+ }
40
+
41
+ .Table {
42
+ height: 100%;
43
+ padding: 16px 16px 64px 15px;
44
+ user-select: none;
45
+ overflow-y: auto;
46
+
47
+ .TableHeading {
48
+ padding-bottom: 4px;
49
+ padding-left: 1px;
50
+ border-top: 24px solid transparent;
51
+ margin-left: 94px;
52
+ font-weight: bold;
53
+ text-align: left;
54
+
55
+ &:first-of-type {
56
+ border-top: 0;
57
+ }
58
+
59
+ &.canProxy {
60
+ margin-left: 124px;
61
+ }
62
+ }
63
+
64
+ .TableRow {
65
+ display: flex;
66
+ margin-left: 24px;
67
+
68
+ &.animIn {
69
+ opacity: 0;
70
+ transform: scaleY(0);
71
+ animation: _kfRowIn 180ms ease-in-out forwards;
72
+ }
73
+ }
74
+
75
+ .FolderGroup {
76
+ position: relative;
77
+ border-radius: var(--radius);
78
+
79
+ > summary {
80
+ left: 0;
81
+ display: flex;
82
+ overflow: hidden;
83
+ align-items: center;
84
+ padding: 5px 0;
85
+ font-weight: 500;
86
+ font-size: 12px;
87
+ list-style: none;
88
+ cursor: pointer;
89
+ user-select: none;
90
+ color: var(--colorLabel);
91
+ white-space: nowrap;
92
+ text-overflow: ellipsis;
93
+ border-radius: var(--radius);
94
+
95
+ &:active {
96
+ cursor: grabbing;
97
+ }
98
+
99
+ &::-webkit-details-marker,
100
+ &::marker {
101
+ display: none;
102
+ }
103
+
104
+ &:hover {
105
+ background: var(--colorHover);
106
+ }
107
+
108
+ .FolderName {
109
+ margin-left: 125px;
110
+ &.canProxy {
111
+ margin-left: 155px;
112
+ }
113
+
114
+ &.groupedByMethod {
115
+ margin-left: 79px;
116
+ &.canProxy {
117
+ margin-left: 109px;
118
+ }
119
+ }
120
+ }
121
+
122
+ .FolderChevron {
123
+ display: flex;
124
+ width: 16px;
125
+ height: 16px;
126
+ flex-shrink: 0;
127
+ align-items: center;
128
+ justify-content: center;
129
+ opacity: .7;
130
+ transition: transform cubic-bezier(.2, .7, .8, 1.4) 400ms;
131
+ transform: rotate(-90deg);
132
+
133
+ svg {
134
+ width: 100%;
135
+ height: 100%;
136
+ }
137
+ }
138
+ }
139
+
140
+ &[open] {
141
+ &:has(summary:hover):not(:has(details:hover)) {
142
+ background: linear-gradient(90deg, var(--colorHover), var(--colorBg));
143
+ }
144
+
145
+ > summary {
146
+ position: absolute;
147
+ top: 0;
148
+ left: 0;
149
+ width: 16px;
150
+
151
+ .FolderChevron {
152
+ transform: rotate(0deg);
153
+ }
154
+ .FolderName {
155
+ display: none;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ @keyframes _kfRowIn {
163
+ to {
164
+ opacity: 1;
165
+ transform: scaleY(1);
166
+ }
167
+ }
168
+
169
+ .Method {
170
+ overflow: hidden;
171
+ min-width: 38px;
172
+ padding: 4px 0;
173
+ margin-top: 3px;
174
+ margin-right: 8px;
175
+ color: var(--colorLabel);
176
+ font-size: 11px;
177
+ text-align: center;
178
+ white-space: nowrap;
179
+ text-overflow: ellipsis;
180
+ }
181
+
182
+ .PreviewLink {
183
+ position: relative;
184
+ left: -8px;
185
+ display: inline-block;
186
+ width: auto;
187
+ min-width: 140px;
188
+ flex-grow: 1;
189
+ padding: 6px 8px;
190
+ margin-right: -2px;
191
+ margin-left: 4px;
192
+ font-weight: 500;
193
+ border-radius: var(--radius);
194
+ word-break: break-word;
195
+
196
+ &:hover {
197
+ background: var(--colorHover);
198
+ }
199
+ &.chosen {
200
+ color: white;
201
+ background: var(--colorAccent);
202
+ }
203
+ .dittoDir {
204
+ opacity: 0.8;
205
+ filter: saturate(0.1);
206
+ }
207
+ }
208
+
209
+
210
+ .MockSelector {
211
+ min-width: 58px;
212
+ height: 24px;
213
+ padding-right: 20px;
214
+ padding-left: 8px;
215
+ text-overflow: ellipsis;
216
+ text-align: right;
217
+
218
+ &:enabled {
219
+ box-shadow: var(--boxShadowRimDrop);
220
+ &:hover {
221
+ background-color: var(--colorBgFieldHover);
222
+ }
223
+ }
224
+
225
+ &.nonDefault {
226
+ font-weight: bold;
227
+ font-size: 11px;
228
+ }
229
+ &.status4xx {
230
+ background: var(--colorBg4xx);
231
+ }
232
+ &:disabled {
233
+ padding-right: 4px;
234
+ padding-left: 0;
235
+ border-color: transparent;
236
+ appearance: none;
237
+ background: transparent;
238
+ cursor: default;
239
+ color: var(--colorLabel);
240
+ opacity: 0.8;
241
+ }
242
+ }
243
+
244
+
245
+ .Toggler {
246
+ display: flex;
247
+ margin-top: 2px;
248
+
249
+ input {
250
+ /* For click drag target */
251
+ position: absolute;
252
+ width: 22px;
253
+ height: 22px;
254
+ opacity: 0;
255
+
256
+ &:focus-visible {
257
+ outline: 0;
258
+ & + .checkboxBody {
259
+ outline: 2px solid var(--colorAccent);
260
+ }
261
+ }
262
+
263
+ &:disabled + .checkboxBody {
264
+ cursor: not-allowed;
265
+ opacity: 0.7;
266
+ }
267
+
268
+ &:checked + .checkboxBody {
269
+ border-color: var(--colorAccent);
270
+ fill: var(--colorAccent);
271
+ background: var(--colorAccent);
272
+ stroke: var(--colorBg);
273
+ }
274
+
275
+ &:enabled:hover:not(:checked) + .checkboxBody {
276
+ border-color: var(--colorHover);
277
+ background: var(--colorHover);
278
+ stroke: var(--colorText);
279
+ }
280
+ }
281
+
282
+ .checkboxBody {
283
+ display: flex;
284
+ width: 22px;
285
+ height: 21px;
286
+ align-items: center;
287
+ justify-content: center;
288
+ fill: none;
289
+ stroke: var(--colorLabel);
290
+ stroke-width: 2.5px;
291
+ border-radius: 50%;
292
+ box-shadow: var(--boxShadowRimDrop);
293
+ background-color: var(--colorBgHeaderField);
294
+ }
295
+
296
+ &.canProxy {
297
+ margin-left: 30px;
298
+ }
299
+ }
300
+
301
+ .DelayToggler {
302
+ }
303
+
304
+ .ProxyToggler {
305
+ margin-right: 8px;
306
+
307
+ .checkboxBody {
308
+ svg {
309
+ width: 15px;
310
+ }
311
+ }
312
+ }
313
+
314
+ .StatusCodeToggler {
315
+ margin-right: 10px;
316
+ margin-left: 8px;
317
+
318
+ input {
319
+ width: 26px;
320
+
321
+ &:not(:checked):enabled:hover + .checkboxBody {
322
+ border-color: var(--colorRed);
323
+ color: var(--colorRed);
324
+ background: transparent;
325
+ }
326
+ &:checked + .checkboxBody {
327
+ border-color: var(--colorRed);
328
+ color: white;
329
+ background: var(--colorRed);
330
+ }
331
+ }
332
+
333
+ .checkboxBody {
334
+ width: 27px;
335
+ padding: 4px;
336
+ font-size: 10px;
337
+ font-weight: bold;
338
+ color: var(--colorLabel);
339
+ }
340
+ }