Skip to content

Commit f2d826d

Browse files
committed
Add option log
1 parent d9383db commit f2d826d

4 files changed

Lines changed: 29 additions & 10 deletions

File tree

src/main.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,24 @@ const handleCliError = (error, opts) => {
1111
const errorA = normalizeException(error)
1212
const {
1313
error: errorB,
14-
opts: { silent, exitCode, timeout },
14+
opts: { silent, exitCode, timeout, log },
1515
beautifulErrorOpts,
1616
} = getOpts(errorA, opts)
1717

18-
printError(errorB, silent, beautifulErrorOpts)
18+
printError({ error: errorB, silent, log, beautifulErrorOpts })
1919
exitProcess(exitCode, timeout)
2020
}
2121

2222
// We pass the `error` instance to `console.error()`, so it prints not only its
2323
// `message` and `stack` but also its properties, `cause`, aggregate `errors`,
2424
// add colors, inline preview, etc. using `util.inspect()`
25-
const printError = (error, silent, beautifulErrorOpts) => {
25+
const printError = ({ error, silent, log, beautifulErrorOpts }) => {
2626
if (silent) {
2727
return
2828
}
2929

3030
const errorString = beautifulError(error, beautifulErrorOpts)
31-
// eslint-disable-next-line no-restricted-globals, no-console
32-
console.error(errorString)
31+
log(errorString)
3332
}
3433

3534
export default handleCliError

src/main.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import test from 'ava'
22
import figures from 'figures'
33
import { validateOptions } from 'handle-cli-error'
4+
import { spy } from 'sinon'
45
import { each } from 'test-each'
56

67
import { handleError } from './helpers/main.test.js'
@@ -39,3 +40,12 @@ test.serial('Does not log if "silent" is true', (t) => {
3940
const { consoleArg } = handleError(error, { silent: true })
4041
t.is(consoleArg, undefined)
4142
})
43+
44+
test.serial('Can pass custom log function', (t) => {
45+
const error = new TypeError('test')
46+
const log = spy()
47+
handleError(error, { log })
48+
t.true(log.calledOnce)
49+
t.is(log.args.length, 1)
50+
t.true(log.firstCall.args[0].includes('TypeError: test'))
51+
})

src/options/default.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export const DEFAULT_OPTS = {
1414
silent: false,
1515
exitCode: DEFAULT_EXIT_CODE,
1616
timeout: DEFAULT_TIMEOUT,
17+
// Do not pass `console.error` directly, to allow monkey-patching
18+
log(message) {
19+
// eslint-disable-next-line no-console, no-restricted-globals
20+
console.error(message)
21+
},
1722
}
1823

1924
// Remove `undefined` values of an object

src/options/validate.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ export const validateOptions = (opts = {}) => {
2222
}
2323

2424
export const normalizeOptions = (name, opts) => {
25-
const { silent, exitCode, timeout, ...beautifulErrorOpts } = applyClassesOpts(
26-
name,
27-
opts,
28-
)
29-
const optsA = { silent, exitCode, timeout }
25+
const { silent, exitCode, timeout, log, ...beautifulErrorOpts } =
26+
applyClassesOpts(name, opts)
27+
const optsA = { silent, exitCode, timeout, log }
3028
Object.entries(optsA).forEach(validateOpt)
3129
validateBeautifulOptions(beautifulErrorOpts)
3230
return { opts: optsA, beautifulErrorOpts }
@@ -44,8 +42,15 @@ const validateBooleanOpt = (value, optName) => {
4442
}
4543
}
4644

45+
const validateFunction = (value, optName) => {
46+
if (typeof value !== 'function') {
47+
throw new TypeError(`"${optName}" must be a function: ${value}`)
48+
}
49+
}
50+
4751
const VALIDATORS = {
4852
silent: validateBooleanOpt,
4953
exitCode: validateExitCode,
5054
timeout: validateTimeout,
55+
log: validateFunction,
5156
}

0 commit comments

Comments
 (0)