mirror of
https://github.com/arthur-pbty/portfolio2023.git
synced 2026-06-10 19:04:26 +02:00
306 lines
7.4 KiB
JavaScript
306 lines
7.4 KiB
JavaScript
'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')
|
||
}
|