mockaton 8.24.0 → 8.25.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 +32 -7
- package/index.d.ts +3 -1
- package/package.json +1 -1
- package/src/Filename.js +1 -8
- package/src/MockDispatcher.js +3 -2
- package/src/Mockaton.js +7 -6
- package/src/StaticDispatcher.js +3 -1
- package/src/cli.js +14 -8
- package/src/config.js +9 -5
- package/src/mockBrokersCollection.js +9 -1
- package/src/utils/fs.js +2 -1
- package/src/utils/http-response.js +4 -3
- package/src/utils/log.js +34 -0
package/README.md
CHANGED
|
@@ -132,13 +132,17 @@ npx mockaton --port 2345
|
|
|
132
132
|
CLI options override their counterparts in `mockaton.config.js`
|
|
133
133
|
|
|
134
134
|
```txt
|
|
135
|
-
-c, --config <file>
|
|
135
|
+
-c, --config <file> (default: ./mockaton.config.js)
|
|
136
136
|
|
|
137
|
-
-
|
|
138
|
-
-
|
|
137
|
+
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
|
|
138
|
+
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
|
|
139
139
|
|
|
140
|
-
-
|
|
141
|
-
-
|
|
140
|
+
-H, --host <host> (default: 127.0.0.1)
|
|
141
|
+
-p, --port <port> (default: 0) which means auto-assigned
|
|
142
|
+
|
|
143
|
+
-q, --quiet Errors only
|
|
144
|
+
-h, --help Show this help
|
|
145
|
+
-v, --version Show version
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
|
|
@@ -152,7 +156,6 @@ import {
|
|
|
152
156
|
SUPPORTED_METHODS
|
|
153
157
|
} from 'mockaton'
|
|
154
158
|
|
|
155
|
-
|
|
156
159
|
export default defineConfig({
|
|
157
160
|
mocksDir: 'mockaton-mocks',
|
|
158
161
|
staticDir: 'mockaton-static-mocks',
|
|
@@ -186,7 +189,9 @@ export default defineConfig({
|
|
|
186
189
|
corsCredentials: true,
|
|
187
190
|
corsMaxAge: 0,
|
|
188
191
|
|
|
189
|
-
onReady: await openInBrowser
|
|
192
|
+
onReady: await openInBrowser,
|
|
193
|
+
|
|
194
|
+
logLevel: 'normal'
|
|
190
195
|
})
|
|
191
196
|
```
|
|
192
197
|
|
|
@@ -421,9 +426,29 @@ config.onReady = () => {}
|
|
|
421
426
|
|
|
422
427
|
At any rate, you can trigger any command besides opening a browser.
|
|
423
428
|
|
|
429
|
+
<br/>
|
|
430
|
+
|
|
431
|
+
### `logLevel?: 'quiet' | 'normal'`
|
|
432
|
+
Defaults to `'normal'`.
|
|
433
|
+
|
|
434
|
+
- `quiet`: only errors (stderr)
|
|
435
|
+
- `normal`: info, access, warnings, and errors
|
|
436
|
+
|
|
437
|
+
</details>
|
|
438
|
+
|
|
424
439
|
|
|
440
|
+
<details>
|
|
441
|
+
<summary>Programmatic Launch (Optional)</summary>
|
|
425
442
|
|
|
443
|
+
```js
|
|
444
|
+
import { Mockaton } from 'mockaton'
|
|
445
|
+
import mockatonConfig from './mockaton.config.js'
|
|
426
446
|
|
|
447
|
+
Mockaton({
|
|
448
|
+
...mockatonConfig, // Not required, but it’s not read by default.
|
|
449
|
+
port: 3333, // etc.
|
|
450
|
+
})
|
|
451
|
+
```
|
|
427
452
|
</details>
|
|
428
453
|
|
|
429
454
|
|
package/index.d.ts
CHANGED
|
@@ -41,10 +41,12 @@ interface Config {
|
|
|
41
41
|
corsMaxAge?: number
|
|
42
42
|
|
|
43
43
|
onReady?: (address: string) => void
|
|
44
|
+
|
|
45
|
+
logLevel?: 'normal' | 'quiet'
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
export function Mockaton(options: Partial<Config>): Server
|
|
49
|
+
export function Mockaton(options: Partial<Config>): Server | undefined
|
|
48
50
|
export function defineConfig(options: Partial<Config>): Config
|
|
49
51
|
|
|
50
52
|
export const jsToJsonPlugin: Plugin
|
package/package.json
CHANGED
package/src/Filename.js
CHANGED
|
@@ -22,15 +22,8 @@ export const includesComment = (filename, search) =>
|
|
|
22
22
|
extractComments(filename).some(comment => comment.includes(search))
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
export function filenameIsValid(file) {
|
|
26
|
-
const error = validateFilename(file)
|
|
27
|
-
if (error)
|
|
28
|
-
console.error(error, file)
|
|
29
|
-
return !error
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
// TODO ThinkAbout 206 (reject, handle, or send in full?)
|
|
33
|
-
function validateFilename(file) {
|
|
26
|
+
export function validateFilename(file) {
|
|
34
27
|
const tokens = file.replace(reComments, '').split('.')
|
|
35
28
|
if (tokens.length < 4)
|
|
36
29
|
return 'Invalid Filename Convention'
|
package/src/MockDispatcher.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import { pathToFileURL } from 'node:url'
|
|
4
4
|
|
|
5
|
+
import { log } from './utils/log.js'
|
|
5
6
|
import { proxy } from './ProxyRelay.js'
|
|
6
7
|
import { cookie } from './cookie.js'
|
|
7
8
|
import { mimeFor } from './utils/mime.js'
|
|
@@ -22,7 +23,7 @@ export async function dispatchMock(req, response) {
|
|
|
22
23
|
return
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
log.access(req.url, broker.file)
|
|
26
27
|
response.statusCode = broker.status
|
|
27
28
|
|
|
28
29
|
if (cookie.getCurrent())
|
|
@@ -45,7 +46,7 @@ export async function dispatchMock(req, response) {
|
|
|
45
46
|
sendNotFound(response)
|
|
46
47
|
else if (error.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
|
|
47
48
|
if (error.toString().includes('Unknown file extension ".ts'))
|
|
48
|
-
|
|
49
|
+
log.warn('\nLooks like you need a TypeScript compiler\n')
|
|
49
50
|
sendInternalServerError(response, error)
|
|
50
51
|
}
|
|
51
52
|
else
|
package/src/Mockaton.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http'
|
|
2
2
|
|
|
3
|
+
import { log } from './utils/log.js'
|
|
3
4
|
import { API } from './ApiConstants.js'
|
|
4
5
|
import { config, setup } from './config.js'
|
|
5
6
|
import { dispatchMock } from './MockDispatcher.js'
|
|
@@ -18,7 +19,7 @@ process.on('unhandledRejection', error => { throw error })
|
|
|
18
19
|
export function Mockaton(options) {
|
|
19
20
|
const error = setup(options)
|
|
20
21
|
if (error) {
|
|
21
|
-
|
|
22
|
+
log.error(error)
|
|
22
23
|
process.exitCode = 1
|
|
23
24
|
return
|
|
24
25
|
}
|
|
@@ -32,19 +33,19 @@ export function Mockaton(options) {
|
|
|
32
33
|
|
|
33
34
|
server.listen(config.port, config.host, function (error) {
|
|
34
35
|
if (error) {
|
|
35
|
-
|
|
36
|
+
log.error(error)
|
|
36
37
|
process.exit(1)
|
|
37
38
|
return
|
|
38
39
|
}
|
|
39
40
|
const { address, port } = this.address()
|
|
40
41
|
const url = `http://${address}:${port}`
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
log.info('Listening', url)
|
|
43
|
+
log.info('Dashboard', url + API.dashboard)
|
|
43
44
|
config.onReady(url + API.dashboard)
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
server.on('error', error => {
|
|
47
|
-
|
|
48
|
+
log.error(error.message)
|
|
48
49
|
process.exit(1)
|
|
49
50
|
})
|
|
50
51
|
|
|
@@ -53,7 +54,7 @@ export function Mockaton(options) {
|
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
async function onRequest(req, response) {
|
|
56
|
-
response.on('error',
|
|
57
|
+
response.on('error', log.warn)
|
|
57
58
|
|
|
58
59
|
try {
|
|
59
60
|
response.setHeader('Server', 'Mockaton')
|
package/src/StaticDispatcher.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
|
+
import { log } from './utils/log.js'
|
|
4
5
|
import { mimeFor } from './utils/mime.js'
|
|
5
6
|
import { brokerByRoute } from './staticCollection.js'
|
|
6
7
|
import { config, calcDelay } from './config.js'
|
|
@@ -12,11 +13,12 @@ export async function dispatchStatic(req, response) {
|
|
|
12
13
|
|
|
13
14
|
setTimeout(async () => {
|
|
14
15
|
if (!broker || broker.status === 404) { // TESTME
|
|
16
|
+
log.access(req.url, 'static404')
|
|
15
17
|
sendNotFound(response)
|
|
16
18
|
return
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
log.access(req.url, 'static200')
|
|
20
22
|
|
|
21
23
|
const file = join(config.staticDir, broker.route)
|
|
22
24
|
if (req.headers.range)
|
package/src/cli.js
CHANGED
|
@@ -19,7 +19,10 @@ const args = parseArgs({
|
|
|
19
19
|
'static-dir': { short: 's', type: 'string' },
|
|
20
20
|
|
|
21
21
|
help: { short: 'h', type: 'boolean' },
|
|
22
|
-
version: { short: 'v', type: 'boolean' }
|
|
22
|
+
version: { short: 'v', type: 'boolean' },
|
|
23
|
+
|
|
24
|
+
quiet: { short: 'q', type: 'boolean' },
|
|
25
|
+
debug: { type: 'boolean' }
|
|
23
26
|
}
|
|
24
27
|
}).values
|
|
25
28
|
|
|
@@ -32,16 +35,17 @@ else if (args.help)
|
|
|
32
35
|
Usage: mockaton [options]
|
|
33
36
|
|
|
34
37
|
Options:
|
|
35
|
-
-c, --config <file>
|
|
38
|
+
-c, --config <file> (default: ./mockaton.config.js)
|
|
36
39
|
|
|
37
|
-
-m, --mocks-dir <dir>
|
|
38
|
-
-s, --static-dir <dir>
|
|
40
|
+
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
|
|
41
|
+
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
|
|
39
42
|
|
|
40
|
-
-H, --host <host>
|
|
41
|
-
-p, --port <port>
|
|
43
|
+
-H, --host <host> (default: 127.0.0.1)
|
|
44
|
+
-p, --port <port> (default: 0) which means auto-assigned
|
|
42
45
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
46
|
+
-q, --quiet Errors only
|
|
47
|
+
-h, --help Show this help
|
|
48
|
+
-v, --version Show version
|
|
45
49
|
|
|
46
50
|
Notes:
|
|
47
51
|
* mockaton.config.js supports more options, see:
|
|
@@ -64,5 +68,7 @@ else {
|
|
|
64
68
|
if (args['mocks-dir']) opts.mocksDir = args['mocks-dir']
|
|
65
69
|
if (args['static-dir']) opts.staticDir = args['static-dir']
|
|
66
70
|
|
|
71
|
+
if (args.quiet) opts.logLevel = 'quiet'
|
|
72
|
+
|
|
67
73
|
Mockaton(opts)
|
|
68
74
|
}
|
package/src/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, isAbsolute } from 'node:path'
|
|
2
2
|
|
|
3
|
+
import { log } from './utils/log.js'
|
|
3
4
|
import { isDirectory } from './utils/fs.js'
|
|
4
5
|
import { openInBrowser } from './utils/openInBrowser.js'
|
|
5
6
|
import { jsToJsonPlugin } from './MockDispatcher.js'
|
|
@@ -48,7 +49,9 @@ const schema = {
|
|
|
48
49
|
corsCredentials: [true, is(Boolean)],
|
|
49
50
|
corsMaxAge: [0, is(Number)],
|
|
50
51
|
|
|
51
|
-
onReady: [await openInBrowser, is(Function)]
|
|
52
|
+
onReady: [await openInBrowser, is(Function)],
|
|
53
|
+
|
|
54
|
+
logLevel: ['normal', val => ['normal', 'quiet'].includes(val)]
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
|
|
@@ -68,18 +71,19 @@ export const ConfigValidator = Object.freeze(validators)
|
|
|
68
71
|
|
|
69
72
|
/** @param {Partial<Config>} options */
|
|
70
73
|
export function setup(options) {
|
|
71
|
-
if (options.mocksDir && !isAbsolute(options.mocksDir))
|
|
74
|
+
if (options.mocksDir && !isAbsolute(options.mocksDir))
|
|
72
75
|
options.mocksDir = join(process.cwd(), options.mocksDir)
|
|
73
|
-
|
|
76
|
+
|
|
74
77
|
if (options.staticDir && !isAbsolute(options.staticDir))
|
|
75
78
|
options.staticDir = join(process.cwd(), options.staticDir)
|
|
76
|
-
|
|
77
|
-
if (!options.staticDir && !isDirectory(defaults.staticDir))
|
|
79
|
+
|
|
80
|
+
if (!options.staticDir && !isDirectory(defaults.staticDir))
|
|
78
81
|
options.staticDir = ''
|
|
79
82
|
|
|
80
83
|
try {
|
|
81
84
|
Object.assign(config, options)
|
|
82
85
|
validate(config, ConfigValidator)
|
|
86
|
+
log.setLevel(config.logLevel)
|
|
83
87
|
}
|
|
84
88
|
catch (err) {
|
|
85
89
|
return err.message
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { basename } from 'node:path'
|
|
2
2
|
|
|
3
|
+
import { log } from './utils/log.js'
|
|
3
4
|
import { cookie } from './cookie.js'
|
|
4
5
|
import { MockBroker } from './MockBroker.js'
|
|
5
6
|
import { listFilesRecursively } from './utils/fs.js'
|
|
6
7
|
import { config, isFileAllowed } from './config.js'
|
|
7
|
-
import { parseFilename,
|
|
8
|
+
import { parseFilename, validateFilename } from './Filename.js'
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -64,6 +65,13 @@ export function registerMock(file, isFromWatcher = false) {
|
|
|
64
65
|
return true
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function filenameIsValid(file) {
|
|
69
|
+
const error = validateFilename(file)
|
|
70
|
+
if (error)
|
|
71
|
+
log.warn(error, file)
|
|
72
|
+
return !error
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
export function unregisterMock(file) {
|
|
68
76
|
const broker = brokerByFilename(file)
|
|
69
77
|
if (!broker)
|
package/src/utils/fs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, dirname, sep, posix } from 'node:path'
|
|
2
2
|
import { lstatSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
3
|
+
import { log } from './log.js'
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
export const isFile = path => lstatSync(path, { throwIfNoEntry: false })?.isFile()
|
|
@@ -24,6 +25,6 @@ export const write = (path, body) => {
|
|
|
24
25
|
writeFileSync(path, body)
|
|
25
26
|
}
|
|
26
27
|
catch (err) {
|
|
27
|
-
|
|
28
|
+
log.warn('Write access denied', err)
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs, { readFileSync } from 'node:fs'
|
|
2
|
+
import { log } from './log.js'
|
|
2
3
|
import { mimeFor } from './mime.js'
|
|
3
4
|
import { HEADER_FOR_502 } from '../ApiConstants.js'
|
|
4
5
|
|
|
@@ -28,19 +29,19 @@ export function sendNotFound(response) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function sendUnprocessableContent(response, error) {
|
|
31
|
-
|
|
32
|
+
log.warn(error)
|
|
32
33
|
response.statusCode = 422
|
|
33
34
|
response.end(error)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function sendInternalServerError(response, error) {
|
|
37
|
-
|
|
38
|
+
log.error(error)
|
|
38
39
|
response.statusCode = 500
|
|
39
40
|
response.end()
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function sendBadGateway(response, error) {
|
|
43
|
-
|
|
44
|
+
log.warn('Fallback Proxy Error:', error.cause.message)
|
|
44
45
|
response.statusCode = 502
|
|
45
46
|
response.setHeader(HEADER_FOR_502, 1)
|
|
46
47
|
response.end()
|
package/src/utils/log.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const log = new class {
|
|
2
|
+
#level = 'normal'
|
|
3
|
+
|
|
4
|
+
setLevel(level) {
|
|
5
|
+
this.#level = level
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
info(...msg) {
|
|
9
|
+
if (this.#level !== 'quiet')
|
|
10
|
+
console.info([this.#date, 'INFO', ...msg].join('::'))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
access(url, ...msg) {
|
|
14
|
+
if (this.#level !== 'quiet')
|
|
15
|
+
console.log([this.#date, 'ACCESS', this.#sanitizeURL(url), ...msg].join('::'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
warn(...msg) {
|
|
19
|
+
console.warn([this.#date, 'WARN', ...msg].join('::'))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error(...msg) {
|
|
23
|
+
console.error([this.#date, 'ERROR', ...msg].join('::'))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
get #date() {
|
|
28
|
+
return new Date().toISOString()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#sanitizeURL(url) {
|
|
32
|
+
return decodeURIComponent(url).replace(/[\x00-\x1F\x7F\x9B]/g, '')
|
|
33
|
+
}
|
|
34
|
+
}
|