├── .npmignore ├── .prettierrc ├── src ├── index.ts ├── parser │ ├── index.ts │ ├── error-parser.ts │ └── frame-parser.ts ├── interfaces │ ├── flub.options.interface.ts │ ├── index.ts │ ├── frame.interface.ts │ └── stack.trace.interface.ts ├── default-flub-options.ts ├── quotes.ts ├── flub-error-handler.ts ├── __tests__ │ ├── flub-error-handler-e2e-spec.ts │ └── error-handler.spec.ts ├── error-handler.ts └── themes │ ├── error.default.mustache │ ├── error.light.mustache │ └── error.dark.mustache ├── .travis.yml ├── tsconfig.spec.json ├── tslint.json ├── tsconfig.json ├── CHANGELOG.md ├── LICENSE ├── .gitignore ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-handler'; 2 | export * from './flub-error-handler'; 3 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './frame-parser'; 2 | export * from './error-parser'; 3 | -------------------------------------------------------------------------------- /src/interfaces/flub.options.interface.ts: -------------------------------------------------------------------------------- 1 | export interface FlubOptions { 2 | theme?: string; 3 | quote?: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flub.options.interface'; 2 | export * from './frame.interface'; 3 | export * from './stack.trace.interface'; 4 | -------------------------------------------------------------------------------- /src/default-flub-options.ts: -------------------------------------------------------------------------------- 1 | import { FlubOptions } from './interfaces'; 2 | 3 | export class DefaultFlubOptions implements FlubOptions { 4 | theme = 'default'; 5 | quote = true; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/frame.interface.ts: -------------------------------------------------------------------------------- 1 | export interface FrameInterface { 2 | classes: string; 3 | column: any; 4 | context: any; 5 | file: any; 6 | line: any; 7 | method: any; 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | - "node" 7 | git: 8 | depth: 5 9 | addons: 10 | firefox: "latest" 11 | before_install: 12 | - npm i -g npm@latest 13 | - npm i -g yarn 14 | install: 15 | - yarn 16 | script: 17 | - yarn run test 18 | after_success: yarn run coveralls -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es6", 7 | "baseUrl": "./", 8 | "types": ["jest", "node"], 9 | "allowJs": true 10 | }, 11 | "include": ["./src/**/*.spec.ts", "./src/**/*.d.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/stack.trace.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Context { 2 | pre: any; 3 | post: any; 4 | line: any; 5 | } 6 | 7 | export interface StackTraceInterface { 8 | context: Context; 9 | get(belowFn?: any); 10 | parse(err); 11 | getTypeName(); 12 | getFunctionName(); 13 | getMethodName(); 14 | getFileName(); 15 | getLineNumber(); 16 | getColumnNumber(); 17 | isNative(); 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "rules": { 7 | "quotemark": [ 8 | true, 9 | "single" 10 | ], 11 | "no-unused-expression": false, 12 | "interface-name": false, 13 | "no-console": false, 14 | "no-bitwise": false, 15 | "member-access": false 16 | }, 17 | "rulesDirectory": [] 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "noUnusedLocals": false, 8 | "removeComments": false, 9 | "noLib": false, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": false, 14 | "allowJs": false, 15 | "rootDir": "./src", 16 | "baseUrl": "./", 17 | "outDir": "./dist", 18 | "lib": ["es2017"] 19 | }, 20 | "exclude": ["**/node_modules", "**/*-spec.ts", "**/*.spec.ts", "examples"] 21 | } 22 | -------------------------------------------------------------------------------- /src/quotes.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | `Always remember that your present situation is not your final destination. 3 | The best is yet to come.`, 4 | `Life does not have to be perfect to be wonderful.`, 5 | `You’re always one decision away from a totally different life.`, 6 | `No matter how you feel – get up, show up and never give up.`, 7 | `Overthinking will destroy your mood. Breathe and let go.`, 8 | `It's hard to beat a person who never gives up.`, 9 | `Success isn't permanent and failure isn't fatal; it's the courage to continue that counts.`, 10 | `You make mistakes. Mistakes don't make you.`, 11 | `It's just a bad day. Not a bad life.`, 12 | ]; 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 0.1.2 (2024-01-11) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **build:** resolved export & build errors ([7ef47ad](https://github.com/nestjsx/nestjs-flub/commit/7ef47adb6c54c81ed769a2a53633de1e5ec8a2f1)) 11 | * **build:** update 'main' path in package.json ([72add19](https://github.com/nestjsx/nestjs-flub/commit/72add19387807c428347c7ae69813d7545793bf0)) 12 | * **options:** fix defaults, honor theme in handler ([f8f6576](https://github.com/nestjsx/nestjs-flub/commit/f8f6576281f5552e5120a1523a0dc3209d7c6120)) 13 | -------------------------------------------------------------------------------- /src/flub-error-handler.ts: -------------------------------------------------------------------------------- 1 | import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common'; 2 | import { ErrorHandler } from './error-handler'; 3 | import { FlubOptions } from './interfaces'; 4 | import { Logger } from '@nestjs/common'; 5 | 6 | @Catch(Error) 7 | export class FlubErrorHandler implements ExceptionFilter { 8 | private options: FlubOptions; 9 | 10 | constructor(options: FlubOptions = { theme: 'dark', quote: false }) { 11 | this.options = options; 12 | } 13 | 14 | catch(exception: Error, host: ArgumentsHost) { 15 | new ErrorHandler(exception, this.options) 16 | .toHTML() 17 | .then(data => { 18 | const ctx = host.switchToHttp(); 19 | const response = ctx.getResponse(); 20 | 21 | response.header('Content-Type', 'text/html; charset=UTF-8'); 22 | response.status(500).send(data); 23 | }) 24 | .catch(e => { 25 | Logger.error(e.message, e.context); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Shady Khalifa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | #IDE 9 | .vscode 10 | .idea 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | dist/ -------------------------------------------------------------------------------- /src/__tests__/flub-error-handler-e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { Controller, Get, UseFilters, INestApplication } from '@nestjs/common'; 3 | import { FlubErrorHandler } from './../flub-error-handler'; 4 | import * as request from 'supertest'; 5 | 6 | let flubModule: TestingModule; 7 | let app: INestApplication; 8 | 9 | @Controller('test') 10 | @UseFilters(new FlubErrorHandler()) 11 | class TestController { 12 | @Get('') 13 | testMe() { 14 | return 'test'; 15 | throw new Error('standard error'); 16 | } 17 | 18 | @Get('no') 19 | noError() { 20 | return { success: true }; 21 | } 22 | } 23 | 24 | beforeAll(async () => { 25 | flubModule = await Test.createTestingModule({ 26 | controllers: [TestController], 27 | }).compile(); 28 | 29 | app = flubModule.createNestApplication(); 30 | await app.init(); 31 | }); 32 | 33 | describe('FlubErrorHandler', () => { 34 | it('No Error', async () => { 35 | return await request(app.getHttpServer()) 36 | .get('/test/no') 37 | .set('Accept', 'application/json') 38 | .expect(200, { success: true }) 39 | .expect('Content-Type', /json/); 40 | }); 41 | }); 42 | 43 | afterAll(async () => { 44 | await app.close(); 45 | }); 46 | -------------------------------------------------------------------------------- /src/__tests__/error-handler.spec.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from './../error-handler'; 2 | 3 | describe('ErrorHandler', () => { 4 | it('Should instance', () => { 5 | const errorHandler = new ErrorHandler(new Error('hello I am an error'), { 6 | theme: 'dark', 7 | quote: false, 8 | }); 9 | 10 | expect(errorHandler).toBeInstanceOf(ErrorHandler); 11 | }); 12 | 13 | it('Should instance without options', () => { 14 | const errorHandler = new ErrorHandler(new Error('hello I am an error')); 15 | 16 | expect(errorHandler).toBeInstanceOf(ErrorHandler); 17 | }); 18 | 19 | it('toJson should return valid json', async () => { 20 | const errorHandler = new ErrorHandler(new Error('hello, another error'), { 21 | theme: 'dark', 22 | quote: false, 23 | }); 24 | 25 | const result: any = await errorHandler.toJSON(); 26 | 27 | expect(typeof result).toBe('object'); 28 | expect(result.error.message).toBe('hello, another error'); 29 | }); 30 | 31 | it('toHtml should return valid html', async () => { 32 | const errorHandler = new ErrorHandler(new Error('hello, error here'), { 33 | theme: 'dark', 34 | quote: false, 35 | }); 36 | 37 | const html = await errorHandler.toHTML(); 38 | 39 | expect(typeof html).toBe('string'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/parser/error-parser.ts: -------------------------------------------------------------------------------- 1 | import { FlubOptions } from '../interfaces'; 2 | import * as stackTrace from 'stack-trace'; 3 | import quotes from './../quotes'; 4 | import { FrameParser } from './frame-parser'; 5 | 6 | export class ErrorParser { 7 | public viewQuote: boolean = true; 8 | private readonly error: Error; 9 | 10 | constructor(error: Error, options: FlubOptions) { 11 | this.error = error; 12 | this.viewQuote = options.quote; 13 | } 14 | 15 | /** 16 | * Serializes stack to Mustache friendly object to 17 | * be used within the view. Optionally can pass 18 | * a callback to customize the frames output. 19 | * 20 | * @param {Object} stack 21 | * @param {Function} [callback] 22 | * 23 | * @return {Object} 24 | */ 25 | public serialize(stack: object, callback?): object { 26 | callback = callback || FrameParser.serializeCodeFrame.bind(this); 27 | let frames = []; 28 | if (stack instanceof Array) { 29 | frames = stack.filter(frame => frame.getFileName()).map(callback); 30 | } 31 | return { 32 | frames, 33 | message: this.error.message, 34 | name: this.error.name, 35 | quote: this.viewQuote ? this.randomQuote() : undefined, 36 | //status: this.error.status, //TODO what's status for? 37 | }; 38 | } 39 | 40 | /** 41 | * Parses the error stack and returns serialized 42 | * frames out of it. 43 | * 44 | * @return {Promise} 45 | */ 46 | public parse(): Promise { 47 | return new Promise((resolve, reject) => { 48 | const stack = stackTrace.parse(this.error); 49 | Promise.all( 50 | stack.map(frame => { 51 | if (FrameParser.isNode(frame)) { 52 | return Promise.resolve(frame); 53 | } 54 | return FrameParser.readCodeFrame(frame).then(context => { 55 | frame.context = context; 56 | return frame; 57 | }); 58 | }), 59 | ) 60 | .then(resolve) 61 | .catch(reject); 62 | }); 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | private randomQuote(): string { 69 | return quotes[Math.floor(Math.random() * quotes.length)]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-flub", 3 | "version": "0.1.2", 4 | "description": "Pretty Error Stack Viewer for NestJS Framework", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "release": "standard-version", 8 | "clean": "rm -rf dist", 9 | "build": "yarn run clean && tsc -p . && mkdir dist/themes && cp src/themes/* dist/themes/", 10 | "test": "jest", 11 | "coverage": "jest --coverage", 12 | "coveralls": "yarn run coverage --coverageReporters=text-lcov | coveralls", 13 | "format": "prettier --write \"src/**/*.ts\"", 14 | "lint": "tslint -p tsconfig.json -c tslint.json" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/nestjsx/nestjs-flub.git" 19 | }, 20 | "keywords": [ 21 | "errorhandler", 22 | "error-reporting", 23 | "whoops", 24 | "nestjs", 25 | "middlewares" 26 | ], 27 | "author": "Shady Khalifa ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/nestjsx/nestjs-flub/issues" 31 | }, 32 | "homepage": "https://github.com/nestjsx/nestjs-flub#readme", 33 | "devDependencies": { 34 | "@nestjs/common": "^6.5.3", 35 | "@nestjs/core": "^6.5.3", 36 | "@nestjs/platform-express": "^6.5.3", 37 | "@nestjs/testing": "^6.5.3", 38 | "@types/jest": "^23.3.9", 39 | "@types/node": "^12.6.8", 40 | "@types/superagent": "^4.1.3", 41 | "@types/supertest": "^2.0.8", 42 | "coveralls": "^3.0.5", 43 | "jest": "^23.6.0", 44 | "prettier": "^1.18.2", 45 | "reflect-metadata": "^0.1.13", 46 | "rxjs": "^6.5.2", 47 | "supertest": "^4.0.2", 48 | "ts-jest": "^23.10.4", 49 | "ts-node": "^8.3.0", 50 | "tslint": "^5.18.0", 51 | "typescript": "^3.5.3" 52 | }, 53 | "dependencies": { 54 | "mustache": "^3.0.1", 55 | "stack-trace": "^0.0.10" 56 | }, 57 | "peerDependencies": { 58 | "@nestjs/common": "^6.5.3", 59 | "@nestjs/core": "^6.5.3" 60 | }, 61 | "jest": { 62 | "globals": { 63 | "ts-jest": { 64 | "tsConfig": "tsconfig.spec.json" 65 | } 66 | }, 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": "src", 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "coverageDirectory": "./coverage" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | 5 | 6 | Coverage Status 7 | 8 |

9 |

Nestjs Flub

10 | 11 | Pretty ErrorHandler 😫, Stack Viewer for [Nestjs Framework](https://nestjs.com/) 🛠️ 12 | > it's just a simple `Filter` for Catching the Errors 13 | 14 | ## Features 15 | 16 | 1. HTML reporter 17 | 18 | 2. JSON reporter, if request accepts a json instead of text/html. 19 | 20 | 3. Sorted frames of error stack. 21 | 22 | 4. Themes 23 | 24 | 25 | ### Dark Theme 26 | ![dark](https://files.gitter.im/nestjs/nestjs/qkqB/error-dark.png) 27 | 28 | ### Light Theme 29 | ![light](https://files.gitter.im/nestjs/nestjs/z6X6/error-light.png) 30 | 31 | ## Install 32 | 33 | ```bash 34 | npm install nestjs-flub --save 35 | ``` 36 | 37 | ## Simple Usage 38 | Just add this filter as you would any filter: 39 | ```typescript 40 | import { FlubErrorHandler } from 'nestjs-flub'; 41 | @Controller('cats') 42 | @UseFilters(new FlubErrorHandler()) 43 | export class CatsController { 44 | 45 | @Get('/error') 46 | throwError() { 47 | throw new Error('Very Bad Error'); 48 | } 49 | } 50 | 51 | ``` 52 | 53 | ## Configuration 54 | **FlubErrorHandler** accepts an optional object to configure the Error Handler. For now, it only has two Options: 55 | ```typescript 56 | theme: string; // for themes ['dark', 'light', 'default'] 57 | quote: boolean; // for displaying very good quotes 58 | ``` 59 | example 60 | 61 | ```typescript 62 | @UseFilters(new FlubErrorHandler({ theme: 'dark', quote:true })) 63 | ``` 64 | #### Theming 65 | 66 | copy `/src/themes/error.default.mustache` and play 67 | 68 | ## TODO 69 | - [ ] Write Tests 70 | - [ ] list syntax required 71 | 72 | ## Contributing 73 | 74 | You are welcome to contribute to this project. If you want to add new themes, make a new PR containing the theme and a simple image to represent it 75 | 76 | ## Global filters 77 | 78 | Use flub error handler for all controllers 79 | 80 | ```typescript 81 | async function bootstrap() { 82 | const app = await NestFactory.create(ApplicationModule); 83 | app.useGlobalFilters(new FlubErrorHandler()); 84 | await app.listen(3000); 85 | } 86 | bootstrap(); 87 | -------------------------------------------------------------------------------- /src/error-handler.ts: -------------------------------------------------------------------------------- 1 | import { ErrorParser, FrameParser } from './parser'; 2 | import { DefaultFlubOptions } from './default-flub-options'; 3 | import { FlubOptions } from './interfaces'; 4 | import * as fs from 'fs'; 5 | import * as Mustache from 'mustache'; 6 | import * as path from 'path'; 7 | 8 | export class ErrorHandler { 9 | private error: Error; 10 | private options: FlubOptions; 11 | private readonly errorParser: ErrorParser; 12 | 13 | constructor(error: Error, options: FlubOptions = new DefaultFlubOptions()) { 14 | this.error = error; 15 | this.options = options; 16 | this.errorParser = new ErrorParser(error, options); 17 | } 18 | 19 | /** 20 | * Returns error stack as JSON. 21 | * 22 | * @return {Promise} 23 | */ 24 | public async toJSON(): Promise { 25 | return new Promise((resolve, reject) => { 26 | this.errorParser 27 | .parse() 28 | .then(stack => { 29 | resolve({ 30 | error: this.errorParser.serialize(stack), 31 | }); 32 | }) 33 | .catch(reject); 34 | }); 35 | } 36 | 37 | /** 38 | * Returns HTML representation of the error stack 39 | * by parsing the stack into frames and getting 40 | * important info out of it. 41 | * 42 | * @return {Promise} 43 | */ 44 | public async toHTML(): Promise { 45 | return new Promise((resolve, reject) => { 46 | this.errorParser 47 | .parse() 48 | .then(stack => { 49 | const data = this.errorParser.serialize(stack, (frame, index) => { 50 | const serializedFrame = FrameParser.serializeCodeFrame(frame); 51 | serializedFrame.classes = this.getDisplayClasses(frame, index); 52 | return serializedFrame; 53 | }); 54 | const viewTemplate = fs.readFileSync( 55 | path.join( 56 | __dirname, 57 | `./themes/error.${this.options.theme || 'default'}.mustache`, 58 | ), 59 | 'utf-8', 60 | ); 61 | resolve(this.complieView(viewTemplate, data)); 62 | }) 63 | .catch(reject); 64 | }); 65 | } 66 | 67 | /** 68 | * Returns classes to be used inside HTML when 69 | * displaying the frames list. 70 | * 71 | * @param {Object} 72 | * @param {Number} 73 | * 74 | * @return {String} 75 | */ 76 | private getDisplayClasses(frame, index): string { 77 | const classes = []; 78 | if (index === 0) { 79 | classes.push('active'); 80 | } 81 | 82 | if (!FrameParser.isApp(frame)) { 83 | classes.push('native-frame'); 84 | } 85 | return classes.join(' '); 86 | } 87 | 88 | /** 89 | * Compiles the view using HTML 90 | * 91 | * @param {String} 92 | * @param {Object} 93 | * 94 | * @return {String} 95 | */ 96 | private complieView(view, data): string { 97 | return Mustache.render(view, data); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/parser/frame-parser.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { StackTraceInterface, FrameInterface } from './../interfaces'; 4 | import { Logger } from '@nestjs/common'; 5 | 6 | export class FrameParser { 7 | public static codeContext: number = 7; 8 | 9 | /** 10 | * Returns the source code for a given file. If unable to 11 | * read file it log a warn and resolves the promise with a null. 12 | * 13 | * @param {Object} frame 14 | * @return {Promise} null || Object 15 | */ 16 | public static async readCodeFrame( 17 | frame: StackTraceInterface, 18 | ): Promise { 19 | return new Promise((resolve, reject) => { 20 | fs.readFile(frame.getFileName(), 'utf-8', (error, contents) => { 21 | if (error) { 22 | Logger.warn( 23 | `Nest Flub`, 24 | `Cannot Read File: ${frame.getFileName()} , I'm sorry`, 25 | ); 26 | resolve(null); 27 | return; 28 | } 29 | const lines = contents.split(/\r?\n/); 30 | const lineNumber = frame.getLineNumber(); 31 | resolve({ 32 | line: lines[lineNumber - 1], 33 | post: lines.slice(lineNumber, lineNumber + FrameParser.codeContext), 34 | pre: lines.slice( 35 | Math.max(0, lineNumber - (FrameParser.codeContext + 1)), 36 | lineNumber - 1, 37 | ), 38 | }); 39 | }); 40 | }); 41 | } 42 | 43 | /** 44 | * Serializes frame to a usable as an error object. 45 | * 46 | * @param {Object} frame 47 | * 48 | * @return {Object} 49 | */ 50 | public static serializeCodeFrame(frame: StackTraceInterface): FrameInterface { 51 | let relativeFileName = frame.getFileName().indexOf(process.cwd()); 52 | if (relativeFileName > -1) { 53 | relativeFileName = frame 54 | .getFileName() 55 | .replace(process.cwd(), '') 56 | .replace(/\\|\//, ''); 57 | } else { 58 | relativeFileName = frame.getFileName(); 59 | } 60 | 61 | return { 62 | classes: '', 63 | column: frame.getColumnNumber(), 64 | context: FrameParser.frameContext(frame), 65 | file: relativeFileName, 66 | line: frame.getLineNumber(), 67 | method: frame.getFunctionName(), 68 | }; 69 | } 70 | /** 71 | * Serializes frame to a usable as an error object. 72 | * 73 | * @param {Object} frame 74 | * 75 | * @return {Object} 76 | */ 77 | public static frameContext(frame: StackTraceInterface): object { 78 | if (!frame.context) { 79 | return {}; 80 | } 81 | return { 82 | line: frame.context.line, 83 | post: frame.context.post.join('\n'), 84 | pre: frame.context.pre.join('\n'), 85 | start: frame.getLineNumber() - (frame.context.pre || []).length, 86 | }; 87 | } 88 | 89 | /** 90 | * Returns whether frame belongs to nodejs 91 | * or not. 92 | * 93 | * @return {Boolean} [description] 94 | */ 95 | public static isNode(frame): boolean { 96 | if (frame.isNative()) { 97 | return true; 98 | } 99 | 100 | const filename = frame.getFileName() || ''; 101 | return !path.isAbsolute(filename) && filename[0] !== '.'; 102 | } 103 | 104 | /** 105 | * Returns whether code belongs to the app 106 | * or not. 107 | * 108 | * @return {Boolean} [description] 109 | */ 110 | public static isApp(frame): boolean { 111 | if (FrameParser.isNode(frame)) { 112 | return false; 113 | } 114 | return !~(frame.getFileName() || '').indexOf('node_modules' + path.sep); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/themes/error.default.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Oops!, Something Broke | {{ name }} 6 | 7 | 250 | 251 | 349 | 350 | 567 | 568 | 569 |
570 | 571 |
572 | 573 |

{{ status }}

574 | 575 |
576 | {{#quote}} 577 |
{{ quote }}
578 | {{/quote}} 579 |
580 |

Here's What we get

581 |

{{ name }}

582 |

{{ message }}

583 |
584 | 585 |
586 | 591 | 592 |
593 |
594 | 595 | 596 |
597 | 598 |
599 | {{#frames}} 600 | {{index}} 601 |
602 |
603 | {{ file }}:{{ line }}:{{ column }} 604 |
605 |
606 | {{ method }} 607 |
608 |
{{ context.pre }} {{ context.line }} {{ context.post }}
614 |
615 | {{/frames}} 616 |
617 |
618 |
619 |
620 | 631 | 632 | 679 |
680 | 681 | -------------------------------------------------------------------------------- /src/themes/error.light.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Oops!, Something Broke | {{ name }} 6 | 7 | 250 | 251 | 349 | 350 | 567 | 568 | 569 |
570 | 571 |
572 | 573 |

{{ status }}

574 | 575 |
576 | {{#quote}} 577 |
{{ quote }}
578 | {{/quote}} 579 |
580 |

Here's What we get

581 |

{{ name }}

582 |

{{ message }}

583 |
584 | 585 |
586 | 591 | 592 |
593 |
594 | 595 | 596 |
597 | 598 |
599 | {{#frames}} 600 | {{index}} 601 |
602 |
603 | {{ file }}:{{ line }}:{{ column }} 604 |
605 |
606 | {{ method }} 607 |
608 |
{{ context.pre }} {{ context.line }} {{ context.post }}
614 |
615 | {{/frames}} 616 |
617 |
618 |
619 |
620 | 631 | 632 | 679 |
680 | 681 | -------------------------------------------------------------------------------- /src/themes/error.dark.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Oops!, Something Broke | {{ name }} 6 | 7 | 325 | 326 | 425 | 426 | 644 | 645 | 646 |
647 | 648 |
649 | 650 |

{{ status }}

651 | 652 |
653 | {{#quote}} 654 |
{{ quote }}
655 | {{/quote}} 656 |
657 |

Here's What we get

658 |

{{ name }}

659 |

{{ message }}

660 |
661 | 662 |
663 | 668 | 669 |
670 |
671 | 672 | 673 |
674 | 675 |
676 | {{#frames}} 677 | {{index}} 678 |
679 |
680 | {{ file }}:{{ line }}:{{ column }} 681 |
682 |
683 | {{ method }} 684 |
685 |
{{ context.pre }} {{ context.line }} {{ context.post }}
691 |
692 | {{/frames}} 693 |
694 |
695 |
696 |
697 | 708 | 709 | 756 |
757 | 758 | --------------------------------------------------------------------------------