This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+43
View File
@@ -0,0 +1,43 @@
# Youch terminal
![](youch-terminal.jpeg)
This package converts the [youch](https://npmjs.com/package/youch) error message to a string to be displayed on terminal. The output of the function is colorized using [chalk](https://npmjs.com/package/chalk).
## Install
```
npm i youch-terminal
```
## Usage
Make sure you pass the output `toJSON` to the youch terminal function.
```js
const Youch = require('youch')
const forTerminal = require('youch-terminal')
const error = new Error('Some weird error')
const jsonResponse = await new Youch(error, {}).toJSON()
const options = {
// Defaults to false
displayShortPath: false,
// Defaults to single whitspace
prefix: ' ',
// Defaults to false
hideErrorTitle: false,
// Defaults to false
hideMessage: false,
// Defaults to false
displayMainFrameOnly: false,
// Defaults to 3
framesMaxLimit: 3,
}
console.log(forTerminal(output, options))
```
+305
View File
@@ -0,0 +1,305 @@
'use strict'
/**
* youch-terminal
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const { platform, cwd } = process
const { inspect } = require('util')
const wordwrap = require('wordwrap')
const { relative } = require('path')
const { fileURLToPath } = require('url')
const stringWidth = require('string-width')
const { dim, yellow, green, red, cyan } = require('kleur')
const TERMINAL_SIZE = process.stdout.columns
const POINTER = platform === 'win32' && !process.env.WT_SESSION ? '>' : ''
const DASH = platform === 'win32' && !process.env.WT_SESSION ? '' : ''
const CWD = cwd()
function getRelativePath(filePath) {
/**
* Node.js error stack is all messed up. Some lines have file info
* enclosed in parenthesis and some are not
*/
filePath = filePath.replace('async file:', 'file:')
return relative(CWD, filePath.startsWith('file:') ? fileURLToPath(filePath) : filePath)
}
/**
* Pulls the main frame from the frames stack
*
* @method mainFrame
*
* @param {Array} frames
*
* @return {Object|Null}
*/
function mainFrame (frames) {
return frames.find((frame) => frame.isApp) || null
}
/**
* Filter only relevant frames that are supposed to
* be printed on the screen
*
* @method filterNativeFrames
*
* @param {Array} frames
* @param {Object} mainFrame
*
* @return {void}
*/
function filterNativeFrames (frames, mainFrame) {
return frames.filter((frame) => {
return (frame.isApp || frame.isModule) && (!mainFrame || frame.file !== mainFrame.file || frame.line !== mainFrame.line)
})
}
/**
* Returns the method name for a given frame
*
* @method frameMethod
*
* @param {Object} frame
*
* @return {String}
*/
function frameMethod (frame) {
return frame.callee || 'anonymous'
}
/**
* Returns the white space for a given char based
* upon the biggest char.
*
* This is done to keep rows symmetrical.
*
* @method whiteSpace
*
* @param {String} biggestChar
* @param {String} currentChar
*
* @return {String}
*/
function whiteSpace (biggestChar, currentChar) {
let whiteSpace = ''
const whiteSpaceLength = biggestChar.length - currentChar.length
for (let i = 0; i <= whiteSpaceLength; i++) {
whiteSpace += ' '
}
return whiteSpace
}
/**
* Returns the line of code with the line number
*
* @method codeLine
*
* @param {String} line
* @param {Number} counter
* @param {Number} maxCounter
* @param {Boolean} isMain
*
* @return {String}
*/
function codeLine (line, counter, maxCounter, isMain, prefix) {
const space = whiteSpace(String(maxCounter), String(counter))
if (isMain) {
return `${prefix}${red(POINTER)}${space}${red(counter)}${red('|')}${space} ${red(line)}`
}
return `${prefix} ${space}${dim(counter)}${dim('|')}${space} ${dim(line)}`
}
/**
* Returns the error message
*/
function getMessage(error, prefix, hideErrorTitle) {
let message
const wrapper = wordwrap(stringWidth(prefix) + 2, TERMINAL_SIZE)
if (!hideErrorTitle) {
message = `${prefix} ${red(wrapper(`${error.name}: ${error.message}`).trim())}`
} else {
message = `${prefix} ${red(wrapper(`${error.message}`).trim())}`
}
return [message, prefix]
}
/**
* Returns the error help text
*/
function getHelpText(error, prefix) {
let help = error.help
if (!help) {
return []
}
const wrapper = wordwrap(stringWidth(prefix) + 4, TERMINAL_SIZE)
if (Array.isArray(help)) {
return help.map((line) => {
return `${prefix} ${cyan(wrapper(`- ${line}`).trim())}`
}).concat([prefix])
}
return [`${prefix} ${cyan(help)}`, prefix]
}
/**
* Get the relative path for a given file path, from the current working directory
*
* @param {String} filePath
*
* @return {String}
*/
function getShortPath(filePath) {
const posixCwd = cwd().replace(/\\/g, '/')
return filePath.replace(`${posixCwd}/`, '')
}
/**
* Returns the main frame location with line number
*
* @method getMainFrameLocation
*
* @param {Object} frame
*
* @return {Array}
*/
function getMainFrameLocation (frame, prefix, displayShortPath) {
if (!frame) {
return []
}
const filePath = displayShortPath ? getRelativePath(frame.filePath) : frame.filePath
return [`${prefix} at ${yellow(`${frameMethod(frame)}`)} ${green(filePath)}:${green(frame.line)}`]
}
/**
* Returns the main frame code lines
*
* @method getCodeLines
*
* @param {Object} frame
*
* @return {Array}
*/
function getCodeLines (frame, prefix) {
if (!frame || !frame.context || !frame.context.line) {
return []
}
let counter = frame.context.start - 1
const pre = frame.context.pre.split('\n')
const post = frame.context.post.split('\n')
const maxCounter = counter + (pre.length + post.length + 1)
return []
.concat(pre.map((line) => {
counter++
return codeLine(line, counter, maxCounter, false, prefix)
}))
.concat([frame.context.line].map((line) => {
counter++
return codeLine(line, counter, maxCounter, true, prefix)
}))
.concat(post.map((line) => {
counter++
return codeLine(line, counter, maxCounter, false, prefix)
}))
}
/**
* Returns info for all other secondary frames
*
* @method getFramesInfo
*
* @param {Array} frames
*
* @return {Array}
*/
function getFramesInfo (frames, prefix, displayShortPath) {
const totalFrames = String(frames.length)
const padding = whiteSpace(String(totalFrames.length), '')
return frames.map((frame) => {
const filePath = displayShortPath
? getRelativePath(frame.filePath)
: frame.filePath
return [
`${prefix}${padding}${yellow(`${DASH} ${frameMethod(frame)}`)}`,
`${prefix}${padding} ${green(filePath)}${':' + green(frame.line)}`
].join('\n')
})
}
function getErrorCause(errorCause, prefix) {
return [
'',
`${prefix} ${cyan('[cause] {')}`,
inspect(errorCause).split('\n').map((line) => {
return `${prefix} ${cyan(line)}`
}).join('\n'),
`${prefix} ${cyan('}')}`
]
}
/**
* Returns a multi-line string all ready to be printed
* on console.
*
* Everything will break if error is not the output of
* youch.toJSON()
*
* @method
*
* @param {Object} json.error
* @param {String} options.prefix
* @param {Number} options.framesMaxLimit
* @param {Boolean} options.displayShortPath
* @param {Boolean} options.hideErrorTitle
* @param {Boolean} options.hideMessage
* @param {Boolean} options.displayMainFrameOnly
*
* @return {String}
*/
module.exports = ({ error }, options) => {
const firstFrame = mainFrame(error.frames)
options = { prefix: ' ', framesMaxLimit: 3, ...options }
const otherFrames = options.displayMainFrameOnly && firstFrame
? []
: getFramesInfo(
filterNativeFrames(error.frames, firstFrame),
options.prefix,
options.displayShortPath
)
return ['']
.concat(options.hideMessage ? [] : getMessage(error, options.prefix, options.hideErrorTitle))
.concat(getHelpText(error, options.prefix))
.concat(getMainFrameLocation(firstFrame, options.prefix, options.displayShortPath))
.concat(getCodeLines(firstFrame, options.prefix))
.concat(error.cause ? getErrorCause(error.cause, options.prefix) : [])
.concat(otherFrames.length ? [''] : [])
.concat(
Number.isFinite(options.framesMaxLimit)
? otherFrames.slice(0, options.framesMaxLimit)
: otherFrames
)
.concat([''])
.join('\n')
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "youch-terminal",
"version": "2.2.3",
"description": "Show youch error on terminal",
"main": "index.js",
"files": [
"index.js"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "virk",
"license": "MIT",
"dependencies": {
"kleur": "^4.1.5",
"string-width": "^4.2.3",
"wordwrap": "^1.0.0"
},
"devDependencies": {
"youch": "^3.3.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/youch-terminal.git"
},
"bugs": {
"url": "https://github.com/poppinss/youch-terminal/issues"
},
"homepage": "https://github.com/poppinss/youch-terminal#readme"
}