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 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> (default: ./mockaton.config.js)
135
+ -c, --config <file> (default: ./mockaton.config.js)
136
136
 
137
- -H, --host <host> (default: 127.0.0.1)
138
- -p, --port <port> (default: 0) which means auto-assigned
137
+ -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
138
+ -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
139
139
 
140
- -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
141
- -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
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
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "8.24.0",
5
+ "version": "8.25.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
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'
@@ -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
- console.log('%s %s → %s', new Date().toISOString(), decodeURIComponent(req.url), broker.file)
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
- console.error('\nLooks like you need a TypeScript compiler\n')
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
- console.error(error)
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
- console.error(error)
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
- console.log('Listening', url)
42
- console.log('Dashboard', url + API.dashboard)
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
- console.error(error.message)
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', console.error)
57
+ response.on('error', log.warn)
57
58
 
58
59
  try {
59
60
  response.setHeader('Server', 'Mockaton')
@@ -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
- console.log('%s %s (static)', new Date().toISOString(), decodeURIComponent(req.url))
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> (default: ./mockaton.config.js)
38
+ -c, --config <file> (default: ./mockaton.config.js)
36
39
 
37
- -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
38
- -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
40
+ -m, --mocks-dir <dir> (default: ./mockaton-mocks/)
41
+ -s, --static-dir <dir> (default: ./mockaton-static-mocks/)
39
42
 
40
- -H, --host <host> (default: 127.0.0.1)
41
- -p, --port <port> (default: 0) which means auto-assigned
43
+ -H, --host <host> (default: 127.0.0.1)
44
+ -p, --port <port> (default: 0) which means auto-assigned
42
45
 
43
- -h, --help Show this help
44
- -v, --version Show version
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, filenameIsValid } from './Filename.js'
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
- console.error('Write access denied', err)
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
- console.error(error)
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
- console.error(error)
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
- console.error('Fallback Proxy Error:', error.cause.message)
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()
@@ -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
+ }