diff --git a/src/errors/pretty-print.ts b/src/errors/pretty-print.ts index a5f8ee3..e1de26d 100644 --- a/src/errors/pretty-print.ts +++ b/src/errors/pretty-print.ts @@ -23,40 +23,49 @@ export interface PrettyPrintableError { /** * a suggestion that may be useful or provide additional context */ - suggestion?: string; + suggestions?: string[]; } // These exist for backwards compatibility with CLIError type CLIErrorDisplayOptions = { name?: string; bang?: string } export function applyPrettyPrintOptions(error: Error, options: PrettyPrintableError): PrettyPrintableError { - const prettyErrorKeys: (keyof PrettyPrintableError)[] = ['message', 'code', 'ref', 'suggestion'] + const prettyErrorKeys: (keyof PrettyPrintableError)[] = ['message', 'code', 'ref', 'suggestions'] prettyErrorKeys.forEach(key => { const applyOptionsKey = !(key in error) && options[key] if (applyOptionsKey) { - (error as PrettyPrintableError)[key] = options[key] + (error as any)[key] = options[key] } }) return error } +const formatSuggestions = (suggestions?: string[]): string | undefined => { + const label = 'Try this:' + if (!suggestions || suggestions.length === 0) return undefined + if (suggestions.length === 1) return `${label} ${suggestions[0]}` + + const multiple = suggestions.map(suggestion => `* ${suggestion}`).join('\n') + return `${label}\n${indent(multiple, 2)}` +} + export default function prettyPrint(error: Error & PrettyPrintableError & CLIErrorDisplayOptions) { if (config.debug) { return error.stack } - const {message, code, suggestion, ref, name: errorSuffix, bang} = error + const {message, code, suggestions, ref, name: errorSuffix, bang} = error // errorSuffix is pulled from the 'name' property on CLIError // and is like either Error or Warning const formattedHeader = message ? `${errorSuffix || 'Error'}: ${message}` : undefined const formattedCode = code ? `Code: ${code}` : undefined - const formattedSuggestion = suggestion ? `Suggestion: ${suggestion}` : undefined + const formattedSuggestions = formatSuggestions(suggestions) const formattedReference = ref ? `Reference: ${ref}` : undefined - const formatted = [formattedHeader, formattedCode, formattedSuggestion, formattedReference] + const formatted = [formattedHeader, formattedCode, formattedSuggestions, formattedReference] .filter(Boolean) .join('\n') diff --git a/test/error.test.ts b/test/error.test.ts index 5134c16..0bdca26 100644 --- a/test/error.test.ts +++ b/test/error.test.ts @@ -14,25 +14,25 @@ describe('error', () => { fancy .do(() => { - error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'}) + error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) }) .catch((error: PrettyPrintableError) => { expect(error.message).to.equal('An error happened!') expect(error.code).to.equal('ERR') expect(error.ref).to.equal('https://oclif.com/error') - expect(error.suggestion).to.equal('rm -rf node_modules') + expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) }) .it('attaches pretty print properties to a new error from options') fancy .do(() => { - error(new Error('An existing error object error!'), {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'}) + error(new Error('An existing error object error!'), {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) }) .catch((error: PrettyPrintableError) => { expect(error.message).to.equal('An existing error object error!') expect(error.code).to.equal('ERR') expect(error.ref).to.equal('https://oclif.com/error') - expect(error.suggestion).to.equal('rm -rf node_modules') + expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) }) .it('attached pretty print properties from options to an existing error object') @@ -41,13 +41,13 @@ describe('error', () => { const e: any = new Error('An existing error object error!') e.code = 'ORIG_ERR' e.ref = 'ORIG_REF' - e.suggestion = 'ORIG_SUGGESTION' - error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'}) + e.suggestions = ['ORIG_SUGGESTION'] + error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) }) .catch((error: PrettyPrintableError) => { expect(error.code).to.equal('ORIG_ERR') expect(error.ref).to.equal('ORIG_REF') - expect(error.suggestion).to.equal('ORIG_SUGGESTION') + expect(error.suggestions).to.deep.equal(['ORIG_SUGGESTION']) }) .it('preserves original pretty printable properties and is not overwritten by options') diff --git a/test/pretty-print.test.ts b/test/pretty-print.test.ts index 19c2e7f..c35af95 100644 --- a/test/pretty-print.test.ts +++ b/test/pretty-print.test.ts @@ -10,16 +10,28 @@ describe('pretty-print', () => { const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!') sampleError.ref = 'https://oclif.io/docs/flags' sampleError.code = 'OCLIF_BAD_FLAG' - sampleError.suggestion = 'Try using using a good flag' + sampleError.suggestions = ['Try using using a good flag'] expect( stripAnsi(prettyPrint(sampleError)), ).to.equal(` Error: Something very serious has gone wrong with the flags! Code: OCLIF_BAD_FLAG - Suggestion: Try using using a good flag + Try this: Try using using a good flag Reference: https://oclif.io/docs/flags`) }) + fancy + .it('pretty prints multiple suggestions', async () => { + const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!') + sampleError.suggestions = ['Use a good flag', 'Use no flags'] + expect( + stripAnsi(prettyPrint(sampleError)), + ).to.equal(` Error: Something very serious has gone wrong with the flags! + Try this: + * Use a good flag + * Use no flags`) + }) + fancy .it('pretty prints with omitted fields', async () => { const sampleError = new Error('Something very serious has gone wrong with the flags!')