UNPKG

3.99 kBJavaScriptView Raw
1const Mustache = require('mustache');
2const path = require('path');
3const stackTrace = require('stack-trace');
4const fs = require('fs-extra');
5const cookie = require('cookie');
6const startingSlashRegex = /\\|\//;
7
8const T_error = fs.readFileSync(path.join(__dirname, 'resources/error.mustache'), 'utf-8');
9
10class HTMLifiedError {
11 constructor(error, request) {
12 this.codeRange = 5;
13 this.skipHeaders = ['cookie', 'connection', 'dnt'];
14 this.error = error;
15 this.request = request;
16 }
17
18 async provideSnippetForFrame(frame) {
19 const path = frame.getFileName().replace(/dist\/webpack:\//g, '');
20
21 const content = await fs.readFile(path, 'utf-8');
22 const lines = content.split(/\r?\n/);
23 const lineNumber = frame.getLineNumber();
24
25 return {
26 pre: lines.slice(Math.max(0, lineNumber - (this.codeRange + 1)), lineNumber - 1),
27 line: lines[lineNumber - 1],
28 post: lines.slice(lineNumber, lineNumber + this.codeRange)
29 };
30 }
31
32 async parse() {
33 const stack = stackTrace.parse(this.error);
34
35 return await Promise.all(stack.map(async frame => {
36 if (this.isNode(frame)) {
37 return frame;
38 }
39
40 const context = await this.provideSnippetForFrame(frame);
41 frame.context = context;
42
43 return frame;
44 }))
45 }
46
47 getContext(frame) {
48 if (!frame.context) return {}
49
50 const { pre, line, post } = frame.context;
51 return {
52 start: frame.getLineNumber() - (pre || []).length,
53 pre: pre.join('\n'),
54 line,
55 post: post.join('\n')
56 }
57 }
58
59 constructCSSClasses(frame, index) {
60 const classes = [];
61
62 if (index === 0) classes.push('active');
63 if (!frame.isApp) classes.push('native-frame');
64
65 return classes.join(' ');
66 }
67
68 convertFrameToObject(frame) {
69 const relativeFileName = frame.getFileName().indexOf(process.cwd()) > -1
70 ? frame.getFileName().replace(process.cwd(), '').replace(startingSlashRegex, '')
71 : frame.getFileName()
72
73 return {
74 file: relativeFileName,
75 filePath: frame.getFileName(),
76 method: frame.getFunctionName(),
77 line: frame.getLineNumber(),
78 column: frame.getColumnNumber(),
79 context: this.getContext(frame),
80 isModule: this.isNodeModule(frame),
81 isNative: this.isNode(frame),
82 isApp: this.isApp(frame)
83 }
84 }
85
86 isNode(frame) {
87 if (frame.isNative()) return true
88
89 const filename = frame.getFileName() || ''
90 return !path.isAbsolute(filename) && filename[0] !== '.'
91 }
92
93 isApp(frame) {
94 return !this.isNode(frame) && !this.isNodeModule(frame)
95 }
96
97 isNodeModule(frame) {
98 return (frame.getFileName() || '').indexOf('node_modules' + path.sep) > -1
99 }
100
101 convertFramesToObjects(stack, callback) {
102 callback = callback || this._serializeFrame.bind(this)
103
104 const { message, name, status } = this.error;
105 return {
106 message,
107 name,
108 status,
109 frames: stack instanceof Array === true ? stack.filter((frame) => frame.getFileName()).map(callback) : []
110 }
111 }
112
113 convertRequestToObject() {
114 const headers = [];
115
116 for (let header in this.request.headers) {
117 if (this.skipHeaders.includes(header)) continue;
118
119 headers.push({
120 key: header.toUpperCase(),
121 value: this.request.headers[header]
122 });
123 }
124
125 const parsedCookies = cookie.parse(this.request.headers.cookie || '');
126 const cookies = Object.keys(parsedCookies).map(key => ({ key, value: parsedCookies[key] }));
127 const { url, method } = this.request;
128
129 return { url, method, headers, cookies };
130 }
131
132 async generate() {
133 const stack = await this.parse();
134
135 const data = this.convertFramesToObjects(stack, (frame, index) => {
136 const serializedFrame = this.convertFrameToObject(frame);
137 serializedFrame.classes = this.constructCSSClasses(serializedFrame, index);
138 return serializedFrame;
139 });
140
141 data.request = this.convertRequestToObject();
142
143 return Mustache.render(T_error, data);
144 }
145}
146
147module.exports = HTMLifiedError;