From 4e87195739f2a5d9a05451b48773c8afdc680765 Mon Sep 17 00:00:00 2001 From: akiyamn Date: Sun, 24 Sep 2023 23:22:21 +1000 Subject: Initial commit (by create-cloudflare CLI) --- node_modules/youch/LICENSE.md | 19 + node_modules/youch/README.md | 166 ++++++ node_modules/youch/index.d.ts | 66 +++ node_modules/youch/package.json | 59 ++ node_modules/youch/src/Youch.js | 381 ++++++++++++ node_modules/youch/src/error.compiled.mustache | 766 +++++++++++++++++++++++++ 6 files changed, 1457 insertions(+) create mode 100644 node_modules/youch/LICENSE.md create mode 100644 node_modules/youch/README.md create mode 100644 node_modules/youch/index.d.ts create mode 100644 node_modules/youch/package.json create mode 100644 node_modules/youch/src/Youch.js create mode 100644 node_modules/youch/src/error.compiled.mustache (limited to 'node_modules/youch') diff --git a/node_modules/youch/LICENSE.md b/node_modules/youch/LICENSE.md new file mode 100644 index 0000000..cf42283 --- /dev/null +++ b/node_modules/youch/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2018 virk.officials@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/node_modules/youch/README.md b/node_modules/youch/README.md new file mode 100644 index 0000000..1466c21 --- /dev/null +++ b/node_modules/youch/README.md @@ -0,0 +1,166 @@ +# Youch! +> Pretty error reporting for Node.js :rocket: + +
+ +

+ + + +

+ +
+ +--- + +
+ +[![NPM Version][npm-image]][npm-url] +[![Tests][gh-workflow-image]][gh-workflow-url] +[![Downloads Stats][npm-downloads-image]][npm-url] + +Youch is inspired by [Whoops](https://filp.github.io/whoops) but with a modern design. Reading stack trace of the console slows you down from active development. Instead **Youch** print those errors in structured HTML to the browser. + +## Features +1. HTML reporter +2. JSON reporter, if request accepts a json instead of text/html. +3. Sorted frames of error stack. +4. Support for ESM. + +> Checkout [youch terminal](https://github.com/poppinss/youch-terminal) to beautify errors on terminal. + +## Installation +```bash +npm i --save youch +``` + +## Basic Usage +Youch is used by [AdonisJs](http://adonisjs.com), but it can be used by express or raw HTTP server as well. + +```javascript +const Youch = require('youch') +const http = require('http') + +http.createServer(async function (req, res) { + + // PERFORM SOME ACTION + if (error) { + const youch = new Youch(error, req) + const html = await youch.toHTML() + + res.writeHead(200, {'content-type': 'text/html'}) + res.write(html) + res.end() + } + +}).listen(8000) +``` + +## Adding helpful links +Everytime an error occurs, we can help users we letting search for the error on Google, over even on the Github repo of our project. + +Youch let you define clickable links to redirect the user to a website with the error message. + +```js +const youch = new Youch(error) + +await youch + .addLink(({ message }) => { + const url = `https://stackoverflow.com/search?q=${encodeURIComponent(`[adonis.js] ${message}`)}` + + return `Search stackoverflow` + }) + .toHTML() +``` + +Also you can make use of [Font awesome brands icons](https://fontawesome.com/icons?d=gallery&s=brands&m=free) to display icons. + +**If you will use fontawesome icons, then Youch will automatically load the CSS files from the font awesome CDN for you.** + +```js +const youch = new Youch(error) + +await youch + .addLink(({ message }) => { + const url = `https://stackoverflow.com/search?q=${encodeURIComponent(`[adonis.js] ${message}`)}` + return `` + }) + .toHTML() +``` + +## Toggle show all frames checkbox +When rendering HTML you can call the `toggleShowAllFrames` method to check/uncheck the show all frames checkbox. + +By default, the checkbox is not checked and calling this method once will toggle the state. + +```js +const youch = new Youch(error) + +await youch + .toggleShowAllFrames() + .toHTML() +``` + +## Adding CSP nonce +Youch HTML output outputs inline `style` and `script` tags and therefore you will have add `nonce` attribute to them when you have enabled CSP on your website. + +You can pass the `cspNonce` property to the `toHTML` method at the time of rendering the error to an HTML output. + +```js +const youch = new Youch(error, req) +const html = await youch.toHTML({ + cspNonce: 'nonce-value' +}) +``` + +## Get stack as JSON +You can also the error stack frames as JSON by calling the `.toJSON` method. + +```js +const youch = new Youch(error, {}) +const jsonResponse = await youch.toJSON() +``` + +Following is the shape of the `toJSON` return data type. + +```ts +type JsonResponse = { + error: { + message: string; + name: string; + status: number; + frames: { + file: string, + filePath: string, + line: number, + column: number, + callee: string, + calleeShort: string, + context: { + start: number, + pre: string, + line: string, + post: string, + }, + isModule: boolean, + isNative: boolean, + isApp: boolean + }[]; + }; +} +``` + +## Release History +Checkout [CHANGELOG.md](CHANGELOG.md) file for release history. + +## Meta +Checkout [LICENSE.md](LICENSE.md) for license information +Harminder Virk (Aman) - [https://github.com/thetutlage](https://github.com/thetutlage) + +[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/youch/test.yml?style=for-the-badge +[gh-workflow-url]: https://github.com/poppinss/youch/actions/workflows/test.yml "Github action" + +[npm-image]: https://img.shields.io/npm/v/youch.svg?style=for-the-badge&logo=npm +[npm-url]: https://npmjs.org/package/youch 'npm' + +[npm-downloads-image]: https://img.shields.io/npm/dm/youch.svg?style=for-the-badge diff --git a/node_modules/youch/index.d.ts b/node_modules/youch/index.d.ts new file mode 100644 index 0000000..b83370a --- /dev/null +++ b/node_modules/youch/index.d.ts @@ -0,0 +1,66 @@ +declare module "youch" { + + interface YouchOptionsContract { + /** + * Number of lines to be displayed above the error + * in the stack trace. + */ + preLines?: number; + + /** + * Number of lines to be displayed below the error + * in the stack trace. + */ + postLines?: number; + } + + class Youch { + constructor(error: Error, request: Request, options?: YouchOptionsContract); + + /** + * Stores the link `callback` which + * will be processed when rendering + * the HTML view. + */ + addLink(callback: Function): this; + + /** + * Returns error stack as JSON. + */ + toJSON(): Promise<{ + error: { + message: string; + name: string; + cause?: any; + help?: any; + status: number; + frames: { + file: string; + filePath: string; + line: number; + column: number; + callee: string; + calleeShort: string; + context: { + start: number; + pre: string; + line: string; + post: string; + }; + isModule: boolean; + isNative: boolean; + isApp: boolean; + }[]; + }; + }>; + + /** + * Returns HTML representation of the error stack + * by parsing the stack into frames and getting + * important info out of it. + */ + toHTML(data: Record): Promise; + } + + export default Youch; +} diff --git a/node_modules/youch/package.json b/node_modules/youch/package.json new file mode 100644 index 0000000..216a199 --- /dev/null +++ b/node_modules/youch/package.json @@ -0,0 +1,59 @@ +{ + "name": "youch", + "version": "3.3.2", + "description": "HTML Pretty error stack viewer", + "main": "src/Youch.js", + "files": [ + "src", + "index.d.ts" + ], + "types": "./index.d.ts", + "directories": { + "example": "examples" + }, + "scripts": { + "pretest": "npm run lint", + "prepublishOnly": "npm run build", + "build": "node bin/compile.js", + "test": "npm run build && node test/youch.spec.js && node test/youch.spec.mjs", + "lint": "standard" + }, + "author": "amanvirk", + "license": "MIT", + "devDependencies": { + "concat": "^1.0.3", + "cz-conventional-changelog": "^3.3.0", + "japa": "^4.0.0", + "standard": "^17.0.0", + "supertest": "^6.3.3", + "uglify-js": "^3.17.4" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "dependencies": { + "cookie": "^0.5.0", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/poppinss/youch.git" + }, + "keywords": [ + "errors", + "error-reporting", + "whoops" + ], + "bugs": { + "url": "https://github.com/poppinss/youch/issues" + }, + "standard": { + "ignore": [ + "static" + ] + }, + "homepage": "https://github.com/poppinss/youch#readme" +} diff --git a/node_modules/youch/src/Youch.js b/node_modules/youch/src/Youch.js new file mode 100644 index 0000000..bcd3899 --- /dev/null +++ b/node_modules/youch/src/Youch.js @@ -0,0 +1,381 @@ +'use strict' + +/* + * youch + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const fs = require('fs') +const path = require('path') +const cookie = require('cookie') +const Mustache = require('mustache') +const { fileURLToPath } = require('url') +const StackTracey = require('stacktracey') + +const VIEW_PATH = './error.compiled.mustache' +const viewTemplate = fs.readFileSync(path.join(__dirname, VIEW_PATH), 'utf-8') + +class Youch { + constructor (error, request, options = {}) { + this.options = options + this.options.postLines = options.postLines || 5 + this.options.preLines = options.preLines || 5 + + this._filterHeaders = ['cookie', 'connection'] + this.error = error + this.request = request + this.links = [] + this.showAllFrames = false + } + + /** + * Returns the source code for a given file. It unable to + * read file it resolves the promise with a null. + * + * @param {Object} frame + * @return {Promise} + */ + _getFrameSource (frame) { + let path = frame + .file + .replace(/dist\/webpack:\//g, '') // unix + .replace(/dist\\webpack:\\/g, '') // windows + + /** + * We ignore the error when "fileURLToPath" is unable to parse + * the path, since returning the frame source is an optional + * thing + */ + try { + path = path.startsWith('file:') ? fileURLToPath(path) : path + } catch { + } + + return new Promise((resolve) => { + fs.readFile(path, 'utf-8', (error, contents) => { + if (error) { + resolve(null) + return + } + + const lines = contents.split(/\r?\n/) + const lineNumber = frame.line + + resolve({ + pre: lines.slice( + Math.max(0, lineNumber - (this.options.preLines + 1)), + lineNumber - 1 + ), + line: lines[lineNumber - 1], + post: lines.slice(lineNumber, lineNumber + this.options.postLines) + }) + }) + }) + } + + /** + * Parses the error stack and returns serialized + * frames out of it. + * + * @return {Object} + */ + _parseError () { + return new Promise((resolve, reject) => { + const stack = new StackTracey(this.error) + Promise.all( + stack.items.map(async (frame) => { + if (this._isNode(frame)) { + return Promise.resolve(frame) + } + return this._getFrameSource(frame).then((context) => { + frame.context = context + return frame + }) + }) + ) + .then(resolve) + .catch(reject) + }) + } + + /** + * Returns the context with code for a given + * frame. + * + * @param {Object} + * @return {Object} + */ + _getContext (frame) { + if (!frame.context) { + return {} + } + + return { + start: frame.line - (frame.context.pre || []).length, + pre: frame.context.pre.join('\n'), + line: frame.context.line, + post: frame.context.post.join('\n') + } + } + + /** + * Returns classes to be used inside HTML when + * displaying the frames list. + * + * @param {Object} + * @param {Number} + * + * @return {String} + */ + _getDisplayClasses (frame) { + const classes = [] + if (!frame.isApp) { + classes.push('native-frame') + } + + return classes + } + + /** + * Compiles the view using HTML + * + * @param {String} + * @param {Object} + * + * @return {String} + */ + _compileView (view, data) { + return Mustache.render(view, data) + } + + /** + * Serializes frame to a usable error object. + * + * @param {Object} + * + * @return {Object} + */ + _serializeFrame (frame) { + return { + file: frame.fileRelative, + filePath: frame.file.startsWith('file:') + ? fileURLToPath(frame.file).replaceAll('\\', '/') + : frame.file, + line: frame.line, + callee: frame.callee, + calleeShort: frame.calleeShort, + column: frame.column, + context: this._getContext(frame), + isModule: frame.thirdParty, + isNative: frame.native, + isApp: this._isApp(frame) + } + } + + /** + * Returns whether frame belongs to nodejs + * or not. + * + * @return {Boolean} [description] + */ + _isNode (frame) { + if (frame.native) { + return true + } + + const filename = frame.file || '' + if (filename.startsWith('node:')) { + return true + } + return false + + // return !path.isAbsolute(filename) && filename[0] !== '.' + } + + /** + * Returns whether code belongs to the app + * or not. + * + * @return {Boolean} [description] + */ + _isApp (frame) { + return !this._isNode(frame) && !this._isNodeModule(frame) + } + + /** + * Returns whether frame belongs to a node_module or + * not + * + * @method _isNodeModule + * + * @param {Object} frame + * + * @return {Boolean} + * + * @private + */ + _isNodeModule (frame) { + return (frame.file || '').indexOf('node_modules/') > -1 + } + + /** + * Serializes stack to Mustache friendly object to + * be used within the view. Optionally can pass + * a callback to customize the frames output. + * + * @param {Object} + * @param {Function} [callback] + * + * @return {Object} + */ + _serializeData (stack, callback) { + callback = callback || this._serializeFrame.bind(this) + return { + message: this.error.message, + help: this.error.help, + cause: this.error.cause, + name: this.error.name, + status: this.error.status, + frames: + stack instanceof Array === true + ? stack.filter((frame) => frame.file).map(callback) + : [] + } + } + + /** + * Returns a serialized object with important + * information. + * + * @return {Object} + */ + _serializeRequest () { + const headers = [] + const cookies = [] + + if (this.request.headers) { + Object.keys(this.request.headers).forEach((key) => { + if (this._filterHeaders.indexOf(key) > -1) { + return + } + headers.push({ + key: key.toUpperCase(), + value: this.request.headers[key] + }) + }) + + if (this.request.headers.cookie) { + const parsedCookies = cookie.parse(this.request.headers.cookie || '') + Object.keys(parsedCookies).forEach((key) => { + cookies.push({ key, value: parsedCookies[key] }) + }) + } + } + + return { + url: this.request.url, + httpVersion: this.request.httpVersion, + method: this.request.method, + connection: this.request.headers ? this.request.headers.connection : null, + headers, + cookies + } + } + + /** + * Stores the link `callback` which + * will be processed when rendering + * the HTML view. + * + * @param {Function} callback + * + * @returns {Object} + */ + addLink (callback) { + if (typeof callback === 'function') { + this.links.push(callback) + return this + } + + throw new Error('Pass a callback function to "addLink"') + } + + /** + * Toggle the state of showing all frames by default + */ + toggleShowAllFrames () { + this.showAllFrames = !this.showAllFrames + return this + } + + /** + * Returns error stack as JSON. + * + * @return {Promise} + */ + toJSON () { + return new Promise((resolve, reject) => { + this._parseError() + .then((stack) => { + resolve({ + error: this._serializeData(stack) + }) + }) + .catch(reject) + }) + } + + /** + * Returns HTML representation of the error stack + * by parsing the stack into frames and getting + * important info out of it. + * + * @return {Promise} + */ + toHTML (templateState) { + return new Promise((resolve, reject) => { + this._parseError() + .then((stack) => { + let foundActiveFrame = false + + const data = this._serializeData(stack, (frame, index) => { + const serializedFrame = this._serializeFrame(frame) + const classes = this._getDisplayClasses(serializedFrame) + + /** + * Adding active class to first app framework + */ + if (!foundActiveFrame && (serializedFrame.isApp || index + 1 === stack.length)) { + classes.push('active') + foundActiveFrame = true + } + + serializedFrame.classes = classes.join(' ') + + return serializedFrame + }) + + if (templateState) { + Object.assign(data, templateState) + } + + if (this.request) { + data.request = this._serializeRequest() + } + + data.links = this.links.map((renderLink) => renderLink(data)) + data.loadFA = !!data.links.find((link) => link.includes('fa-')) + data.showAllFrames = this.showAllFrames + + return resolve(this._compileView(viewTemplate, data)) + }) + .catch(reject) + }) + } +} + +module.exports = Youch diff --git a/node_modules/youch/src/error.compiled.mustache b/node_modules/youch/src/error.compiled.mustache new file mode 100644 index 0000000..952db8b --- /dev/null +++ b/node_modules/youch/src/error.compiled.mustache @@ -0,0 +1,766 @@ + + + + + + + {{#loadFA}} + + + {{/loadFA}} + + {{#cspNonce}} + + + + +
+
+

{{ status }}

+ +
+

{{ name }}

+

{{ message }}

+ +
+ +
+ {{!-- Filled using frontend code, based upon the active frame --}} + + +
+
+ + +
+ +
+ {{#frames}} + {{ index }} +
+
+ {{ file }}:{{ line }}:{{ column }} +
+
+ {{ callee }} +
+
{{ context.pre }} +{{ context.line }} +{{ context.post }} +
+
+ {{/frames}} +
+
+
+
+ + {{#request}} +
+

Request Details

+ + + + + + + + + + + + + + + + + + + + + +
URI {{ request.url }}
Request Method {{ request.method }}
HTTP Version {{ request.httpVersion }}
Connection {{ request.connection }}
+ +

Headers

+ + + {{#request.headers}} + + + + + {{/request.headers}} +
{{ key }} {{ value }}
+ +

Cookies

+ + {{#request.cookies}} + + + + + {{/request.cookies}} +
{{ key }} {{ value }}
+
+ {{/request}} +
+ {{#cspNonce}} + + + -- cgit v1.2.3