From dadceae70312ee4c1f821cb265d52f0c041cc5f1 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 2 Jan 2020 17:07:04 +0100 Subject: [PATCH 01/10] Support TypeScript sourcemap resolution. --- package.json | 1 + src/error-handler.ts | 10 ++--- src/interfaces/flub.options.interface.ts | 1 + src/parser/error-parser.ts | 20 ++++++--- src/parser/frame-parser.ts | 52 +++++++++++++++++++++--- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index aa3486b..9468ab3 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "dependencies": { "mustache": "^3.0.1", + "source-map": "^0.7.3", "stack-trace": "^0.0.10" }, "peerDependencies": { diff --git a/src/error-handler.ts b/src/error-handler.ts index 78b723c..90c7400 100644 --- a/src/error-handler.ts +++ b/src/error-handler.ts @@ -25,9 +25,9 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(stack => { + .then(async stack => { resolve({ - error: this.errorParser.serialize(stack), + error: await this.errorParser.serialize(stack), }); }) .catch(reject); @@ -45,9 +45,9 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(stack => { - const data = this.errorParser.serialize(stack, (frame, index) => { - const serializedFrame = FrameParser.serializeCodeFrame(frame); + .then(async stack => { + const data = await this.errorParser.serialize(stack, async (frame, index) => { + const serializedFrame = await FrameParser.serializeCodeFrame(frame); serializedFrame.classes = this.getDisplayClasses(frame, index); return serializedFrame; }); diff --git a/src/interfaces/flub.options.interface.ts b/src/interfaces/flub.options.interface.ts index 9eabd22..9415726 100644 --- a/src/interfaces/flub.options.interface.ts +++ b/src/interfaces/flub.options.interface.ts @@ -1,4 +1,5 @@ export interface FlubOptions { theme?: string; quote?: boolean; + sourcemap?: boolean; } diff --git a/src/parser/error-parser.ts b/src/parser/error-parser.ts index 3a7ef06..f9cba7b 100644 --- a/src/parser/error-parser.ts +++ b/src/parser/error-parser.ts @@ -5,11 +5,13 @@ import { FrameParser } from './frame-parser'; export class ErrorParser { public viewQuote: boolean = true; + public resolveSourceMap: boolean = false; private readonly error: Error; constructor(error: Error, options: FlubOptions) { this.error = error; this.viewQuote = options.quote; + this.resolveSourceMap = options.sourcemap; } /** @@ -22,11 +24,16 @@ export class ErrorParser { * * @return {Object} */ - public serialize(stack: object, callback?): object { + public async serialize(stack: object, callback?): Promise { callback = callback || FrameParser.serializeCodeFrame.bind(this); let frames = []; if (stack instanceof Array) { - frames = stack.filter(frame => frame.getFileName()).map(callback); + if (this.resolveSourceMap) { + const resolvedStack = await Promise.all(stack.map(async frame => await FrameParser.resolveSourceMap(frame))); + frames = await Promise.all(resolvedStack.filter(frame => frame.getFileName()).map(callback)); + } else { + frames = await Promise.all(stack.filter(frame => frame.getFileName()).map(callback)); + } } return { frames, @@ -47,13 +54,14 @@ export class ErrorParser { return new Promise((resolve, reject) => { const stack = stackTrace.parse(this.error); Promise.all( - stack.map(frame => { + stack.map(async frame => { if (FrameParser.isNode(frame)) { return Promise.resolve(frame); } - return FrameParser.readCodeFrame(frame).then(context => { - frame.context = context; - return frame; + const resolvedFrame = this.resolveSourceMap ? await FrameParser.resolveSourceMap(frame): frame; + return FrameParser.readCodeFrame(resolvedFrame).then(context => { + resolvedFrame.context = context; + return resolvedFrame; }); }), ) diff --git a/src/parser/frame-parser.ts b/src/parser/frame-parser.ts index 2491966..9790c2f 100644 --- a/src/parser/frame-parser.ts +++ b/src/parser/frame-parser.ts @@ -1,22 +1,61 @@ import * as fs from 'fs'; import * as path from 'path'; -import { StackTraceInterface, FrameInterface } from './../interfaces'; +import { StackTraceInterface, FrameInterface, Context } from './../interfaces'; import { Logger } from '@nestjs/common'; +import { SourceMapConsumer } from 'source-map'; export class FrameParser { public static codeContext: number = 7; + /** + * Returns the `StackTrace` + * + */ + public static resolveSourceMap( + frame: StackTraceInterface + ): Promise { + return new Promise((resolve, reject) => { + fs.readFile(`${frame.getFileName()}.map`, 'utf-8', async (error, contents) => { + if (error) { + return resolve(frame); + } + const consumer = await new SourceMapConsumer(contents); + const originalSourceData = consumer.originalPositionFor({ + line: frame.getLineNumber(), + column: frame.getColumnNumber(), + }); + const stackTrace = new class implements StackTraceInterface { + context: Context; + get(belowFn?: any) { return frame.get(belowFn) } + parse(err) { return frame.parse(err) } + getTypeName() { return frame.getTypeName() } + getFunctionName() { return frame.getFunctionName() } + getMethodName() { return frame.getMethodName() } + getFileName() { return originalSourceData.source.substring(1) } + getLineNumber() { return originalSourceData.line } + getColumnNumber() { return originalSourceData.column } + isNative() { return frame.isNative() } + }; + + stackTrace.context = await this.readCodeFrame(stackTrace); + + return resolve(stackTrace); + }); + }); + } + /** * Returns the source code for a given file. If unable to * read file it log a warn and resolves the promise with a null. * * @param {Object} frame + * @param {boolean} resolveSourceMap * @return {Promise} null || Object */ public static async readCodeFrame( - frame: StackTraceInterface, - ): Promise { - return new Promise((resolve, reject) => { + frame: StackTraceInterface + ): Promise<{ pre: any, post: any, line: any }> { + return new Promise(async (resolve, reject) => { fs.readFile(frame.getFileName(), 'utf-8', (error, contents) => { if (error) { Logger.warn( @@ -47,7 +86,9 @@ export class FrameParser { * * @return {Object} */ - public static serializeCodeFrame(frame: StackTraceInterface): FrameInterface { + public static async serializeCodeFrame( + frame: StackTraceInterface + ): Promise { let relativeFileName = frame.getFileName().indexOf(process.cwd()); if (relativeFileName > -1) { relativeFileName = frame @@ -67,6 +108,7 @@ export class FrameParser { method: frame.getFunctionName(), }; } + /** * Serializes frame to a usable as an error object. * From bbed0892a0493bf5a5f131404abbbecddce760cf Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 2 Jan 2020 18:48:44 +0100 Subject: [PATCH 02/10] Refactoring to adhere to the TSLint rules --- src/error-handler.ts | 10 +++++----- src/flub-error-handler.ts | 8 ++++---- src/parser/error-parser.ts | 16 ++++++++-------- src/parser/frame-parser.ts | 27 ++++++++------------------- src/parser/synthetic-stack-trace.ts | 22 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 src/parser/synthetic-stack-trace.ts diff --git a/src/error-handler.ts b/src/error-handler.ts index 90c7400..206a73d 100644 --- a/src/error-handler.ts +++ b/src/error-handler.ts @@ -1,9 +1,9 @@ -import { ErrorParser, FrameParser } from './parser'; -import { DefaultFlubOptions } from './default-flub-options'; -import { FlubOptions } from './interfaces'; import * as fs from 'fs'; import * as Mustache from 'mustache'; import * as path from 'path'; +import { DefaultFlubOptions } from './default-flub-options'; +import { FlubOptions } from './interfaces'; +import { ErrorParser, FrameParser } from './parser'; export class ErrorHandler { private error: Error; @@ -25,7 +25,7 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(async stack => { + .then(async (stack) => { resolve({ error: await this.errorParser.serialize(stack), }); @@ -45,7 +45,7 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(async stack => { + .then(async (stack) => { const data = await this.errorParser.serialize(stack, async (frame, index) => { const serializedFrame = await FrameParser.serializeCodeFrame(frame); serializedFrame.classes = this.getDisplayClasses(frame, index); diff --git a/src/flub-error-handler.ts b/src/flub-error-handler.ts index 7244299..556aa5e 100644 --- a/src/flub-error-handler.ts +++ b/src/flub-error-handler.ts @@ -1,7 +1,7 @@ -import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common'; +import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { ErrorHandler } from './error-handler'; import { FlubOptions } from './interfaces'; -import { Logger } from '@nestjs/common'; @Catch(Error) export class FlubErrorHandler implements ExceptionFilter { @@ -14,13 +14,13 @@ export class FlubErrorHandler implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { new ErrorHandler(exception, this.options) .toHTML() - .then(data => { + .then((data) => { const ctx = host.switchToHttp(); const response = ctx.getResponse(); response.status(500).send(data); }) - .catch(e => { + .catch((e) => { Logger.error(e.message, e.context); }); } diff --git a/src/parser/error-parser.ts b/src/parser/error-parser.ts index f9cba7b..0bbca1c 100644 --- a/src/parser/error-parser.ts +++ b/src/parser/error-parser.ts @@ -1,5 +1,5 @@ -import { FlubOptions } from '../interfaces'; import * as stackTrace from 'stack-trace'; +import { FlubOptions } from '../interfaces'; import quotes from './../quotes'; import { FrameParser } from './frame-parser'; @@ -29,10 +29,10 @@ export class ErrorParser { let frames = []; if (stack instanceof Array) { if (this.resolveSourceMap) { - const resolvedStack = await Promise.all(stack.map(async frame => await FrameParser.resolveSourceMap(frame))); - frames = await Promise.all(resolvedStack.filter(frame => frame.getFileName()).map(callback)); + const resolvedStack = await Promise.all(stack.map(async (frame) => await FrameParser.resolveSourceMap(frame))); + frames = await Promise.all(resolvedStack.filter((frame) => frame.getFileName()).map(callback)); } else { - frames = await Promise.all(stack.filter(frame => frame.getFileName()).map(callback)); + frames = await Promise.all(stack.filter((frame) => frame.getFileName()).map(callback)); } } return { @@ -40,7 +40,7 @@ export class ErrorParser { message: this.error.message, name: this.error.name, quote: this.viewQuote ? this.randomQuote() : undefined, - //status: this.error.status, //TODO what's status for? + // status: this.error.status, //TODO what's status for? }; } @@ -54,12 +54,12 @@ export class ErrorParser { return new Promise((resolve, reject) => { const stack = stackTrace.parse(this.error); Promise.all( - stack.map(async frame => { + stack.map(async (frame) => { if (FrameParser.isNode(frame)) { return Promise.resolve(frame); } - const resolvedFrame = this.resolveSourceMap ? await FrameParser.resolveSourceMap(frame): frame; - return FrameParser.readCodeFrame(resolvedFrame).then(context => { + const resolvedFrame = this.resolveSourceMap ? await FrameParser.resolveSourceMap(frame) : frame; + return FrameParser.readCodeFrame(resolvedFrame).then((context) => { resolvedFrame.context = context; return resolvedFrame; }); diff --git a/src/parser/frame-parser.ts b/src/parser/frame-parser.ts index 9790c2f..44f35ff 100644 --- a/src/parser/frame-parser.ts +++ b/src/parser/frame-parser.ts @@ -1,8 +1,9 @@ +import { Logger } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; -import { StackTraceInterface, FrameInterface, Context } from './../interfaces'; -import { Logger } from '@nestjs/common'; import { SourceMapConsumer } from 'source-map'; +import { FrameInterface, StackTraceInterface} from './../interfaces'; +import { SyntheticStackTrace } from './synthetic-stack-trace'; export class FrameParser { public static codeContext: number = 7; @@ -12,7 +13,7 @@ export class FrameParser { * */ public static resolveSourceMap( - frame: StackTraceInterface + frame: StackTraceInterface, ): Promise { return new Promise((resolve, reject) => { fs.readFile(`${frame.getFileName()}.map`, 'utf-8', async (error, contents) => { @@ -21,22 +22,10 @@ export class FrameParser { } const consumer = await new SourceMapConsumer(contents); const originalSourceData = consumer.originalPositionFor({ - line: frame.getLineNumber(), column: frame.getColumnNumber(), + line: frame.getLineNumber(), }); - const stackTrace = new class implements StackTraceInterface { - context: Context; - get(belowFn?: any) { return frame.get(belowFn) } - parse(err) { return frame.parse(err) } - getTypeName() { return frame.getTypeName() } - getFunctionName() { return frame.getFunctionName() } - getMethodName() { return frame.getMethodName() } - getFileName() { return originalSourceData.source.substring(1) } - getLineNumber() { return originalSourceData.line } - getColumnNumber() { return originalSourceData.column } - isNative() { return frame.isNative() } - }; - + const stackTrace = new SyntheticStackTrace(frame, originalSourceData); stackTrace.context = await this.readCodeFrame(stackTrace); return resolve(stackTrace); @@ -53,7 +42,7 @@ export class FrameParser { * @return {Promise} null || Object */ public static async readCodeFrame( - frame: StackTraceInterface + frame: StackTraceInterface, ): Promise<{ pre: any, post: any, line: any }> { return new Promise(async (resolve, reject) => { fs.readFile(frame.getFileName(), 'utf-8', (error, contents) => { @@ -87,7 +76,7 @@ export class FrameParser { * @return {Object} */ public static async serializeCodeFrame( - frame: StackTraceInterface + frame: StackTraceInterface, ): Promise { let relativeFileName = frame.getFileName().indexOf(process.cwd()); if (relativeFileName > -1) { diff --git a/src/parser/synthetic-stack-trace.ts b/src/parser/synthetic-stack-trace.ts new file mode 100644 index 0000000..6442bfe --- /dev/null +++ b/src/parser/synthetic-stack-trace.ts @@ -0,0 +1,22 @@ +import { Context, StackTraceInterface } from './../interfaces'; + +export class SyntheticStackTrace implements StackTraceInterface { + context: Context; + frame: StackTraceInterface; + originalSourceData: any; + + constructor(frame, originalSourceData) { + this.frame = frame; + this.originalSourceData = originalSourceData; + } + + get(belowFn?: any) { return this.frame.get(belowFn); } + parse(err) { return this.frame.parse(err); } + getTypeName() { return this.frame.getTypeName(); } + getFunctionName() { return this.frame.getFunctionName(); } + getMethodName() { return this.frame.getMethodName(); } + getFileName() { return this.originalSourceData.source.substring(1); } + getLineNumber() { return this.originalSourceData.line; } + getColumnNumber() { return this.originalSourceData.column; } + isNative() { return this.frame.isNative(); } +} From bb1f1573d8c02b0dfdff8b4cfdb0dfb856ff77f5 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 2 Jan 2020 18:55:59 +0100 Subject: [PATCH 03/10] Fixing up documentation for sourcemap support --- README.md | 1 + src/parser/frame-parser.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cdf6f58..02c93da 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ export class CatsController { ```typescript theme: string; // for themes ['dark', 'light', 'default'] quote: boolean; // for displaying very good quotes +souremap: boolean; // for resolving sourcemap positions ``` example diff --git a/src/parser/frame-parser.ts b/src/parser/frame-parser.ts index 44f35ff..4e1da6b 100644 --- a/src/parser/frame-parser.ts +++ b/src/parser/frame-parser.ts @@ -38,7 +38,6 @@ export class FrameParser { * read file it log a warn and resolves the promise with a null. * * @param {Object} frame - * @param {boolean} resolveSourceMap * @return {Promise} null || Object */ public static async readCodeFrame( From aaec033f42bc6294e0c194ee68abd7c1ef95a58e Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 2 Jan 2020 19:09:13 +0100 Subject: [PATCH 04/10] Apparently prettier has different style than tslint so committing the prettier version --- src/error-handler.ts | 19 +++++++++------ src/flub-error-handler.ts | 4 +-- src/parser/error-parser.ts | 20 ++++++++++----- src/parser/frame-parser.ts | 34 ++++++++++++++------------ src/parser/synthetic-stack-trace.ts | 38 +++++++++++++++++++++-------- 5 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/error-handler.ts b/src/error-handler.ts index 206a73d..097ab06 100644 --- a/src/error-handler.ts +++ b/src/error-handler.ts @@ -25,7 +25,7 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(async (stack) => { + .then(async stack => { resolve({ error: await this.errorParser.serialize(stack), }); @@ -45,12 +45,17 @@ export class ErrorHandler { return new Promise((resolve, reject) => { this.errorParser .parse() - .then(async (stack) => { - const data = await this.errorParser.serialize(stack, async (frame, index) => { - const serializedFrame = await FrameParser.serializeCodeFrame(frame); - serializedFrame.classes = this.getDisplayClasses(frame, index); - return serializedFrame; - }); + .then(async stack => { + const data = await this.errorParser.serialize( + stack, + async (frame, index) => { + const serializedFrame = await FrameParser.serializeCodeFrame( + frame, + ); + serializedFrame.classes = this.getDisplayClasses(frame, index); + return serializedFrame; + }, + ); const viewTemplate = fs.readFileSync( path.join( __dirname, diff --git a/src/flub-error-handler.ts b/src/flub-error-handler.ts index 556aa5e..b3c167a 100644 --- a/src/flub-error-handler.ts +++ b/src/flub-error-handler.ts @@ -14,13 +14,13 @@ export class FlubErrorHandler implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { new ErrorHandler(exception, this.options) .toHTML() - .then((data) => { + .then(data => { const ctx = host.switchToHttp(); const response = ctx.getResponse(); response.status(500).send(data); }) - .catch((e) => { + .catch(e => { Logger.error(e.message, e.context); }); } diff --git a/src/parser/error-parser.ts b/src/parser/error-parser.ts index 0bbca1c..83b6a13 100644 --- a/src/parser/error-parser.ts +++ b/src/parser/error-parser.ts @@ -29,10 +29,16 @@ export class ErrorParser { let frames = []; if (stack instanceof Array) { if (this.resolveSourceMap) { - const resolvedStack = await Promise.all(stack.map(async (frame) => await FrameParser.resolveSourceMap(frame))); - frames = await Promise.all(resolvedStack.filter((frame) => frame.getFileName()).map(callback)); + const resolvedStack = await Promise.all( + stack.map(async frame => await FrameParser.resolveSourceMap(frame)), + ); + frames = await Promise.all( + resolvedStack.filter(frame => frame.getFileName()).map(callback), + ); } else { - frames = await Promise.all(stack.filter((frame) => frame.getFileName()).map(callback)); + frames = await Promise.all( + stack.filter(frame => frame.getFileName()).map(callback), + ); } } return { @@ -54,12 +60,14 @@ export class ErrorParser { return new Promise((resolve, reject) => { const stack = stackTrace.parse(this.error); Promise.all( - stack.map(async (frame) => { + stack.map(async frame => { if (FrameParser.isNode(frame)) { return Promise.resolve(frame); } - const resolvedFrame = this.resolveSourceMap ? await FrameParser.resolveSourceMap(frame) : frame; - return FrameParser.readCodeFrame(resolvedFrame).then((context) => { + const resolvedFrame = this.resolveSourceMap + ? await FrameParser.resolveSourceMap(frame) + : frame; + return FrameParser.readCodeFrame(resolvedFrame).then(context => { resolvedFrame.context = context; return resolvedFrame; }); diff --git a/src/parser/frame-parser.ts b/src/parser/frame-parser.ts index 4e1da6b..d1b9b65 100644 --- a/src/parser/frame-parser.ts +++ b/src/parser/frame-parser.ts @@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; import { SourceMapConsumer } from 'source-map'; -import { FrameInterface, StackTraceInterface} from './../interfaces'; +import { FrameInterface, StackTraceInterface } from './../interfaces'; import { SyntheticStackTrace } from './synthetic-stack-trace'; export class FrameParser { @@ -16,20 +16,24 @@ export class FrameParser { frame: StackTraceInterface, ): Promise { return new Promise((resolve, reject) => { - fs.readFile(`${frame.getFileName()}.map`, 'utf-8', async (error, contents) => { - if (error) { - return resolve(frame); - } - const consumer = await new SourceMapConsumer(contents); - const originalSourceData = consumer.originalPositionFor({ - column: frame.getColumnNumber(), - line: frame.getLineNumber(), - }); - const stackTrace = new SyntheticStackTrace(frame, originalSourceData); - stackTrace.context = await this.readCodeFrame(stackTrace); + fs.readFile( + `${frame.getFileName()}.map`, + 'utf-8', + async (error, contents) => { + if (error) { + return resolve(frame); + } + const consumer = await new SourceMapConsumer(contents); + const originalSourceData = consumer.originalPositionFor({ + column: frame.getColumnNumber(), + line: frame.getLineNumber(), + }); + const stackTrace = new SyntheticStackTrace(frame, originalSourceData); + stackTrace.context = await this.readCodeFrame(stackTrace); - return resolve(stackTrace); - }); + return resolve(stackTrace); + }, + ); }); } @@ -42,7 +46,7 @@ export class FrameParser { */ public static async readCodeFrame( frame: StackTraceInterface, - ): Promise<{ pre: any, post: any, line: any }> { + ): Promise<{ pre: any; post: any; line: any }> { return new Promise(async (resolve, reject) => { fs.readFile(frame.getFileName(), 'utf-8', (error, contents) => { if (error) { diff --git a/src/parser/synthetic-stack-trace.ts b/src/parser/synthetic-stack-trace.ts index 6442bfe..68bb3c1 100644 --- a/src/parser/synthetic-stack-trace.ts +++ b/src/parser/synthetic-stack-trace.ts @@ -4,19 +4,37 @@ export class SyntheticStackTrace implements StackTraceInterface { context: Context; frame: StackTraceInterface; originalSourceData: any; - + constructor(frame, originalSourceData) { this.frame = frame; this.originalSourceData = originalSourceData; } - get(belowFn?: any) { return this.frame.get(belowFn); } - parse(err) { return this.frame.parse(err); } - getTypeName() { return this.frame.getTypeName(); } - getFunctionName() { return this.frame.getFunctionName(); } - getMethodName() { return this.frame.getMethodName(); } - getFileName() { return this.originalSourceData.source.substring(1); } - getLineNumber() { return this.originalSourceData.line; } - getColumnNumber() { return this.originalSourceData.column; } - isNative() { return this.frame.isNative(); } + get(belowFn?: any) { + return this.frame.get(belowFn); + } + parse(err) { + return this.frame.parse(err); + } + getTypeName() { + return this.frame.getTypeName(); + } + getFunctionName() { + return this.frame.getFunctionName(); + } + getMethodName() { + return this.frame.getMethodName(); + } + getFileName() { + return this.originalSourceData.source.substring(1); + } + getLineNumber() { + return this.originalSourceData.line; + } + getColumnNumber() { + return this.originalSourceData.column; + } + isNative() { + return this.frame.isNative(); + } } From 7ba35ae84e5b2207142f502e69c12bae89ef10bd Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 2 Jan 2020 19:09:38 +0100 Subject: [PATCH 05/10] Added the sourcemap option to the test cases --- src/__tests__/error-handler.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/__tests__/error-handler.spec.ts b/src/__tests__/error-handler.spec.ts index 6f74fec..23a54ef 100644 --- a/src/__tests__/error-handler.spec.ts +++ b/src/__tests__/error-handler.spec.ts @@ -5,6 +5,7 @@ describe('ErrorHandler', () => { const errorHandler = new ErrorHandler(new Error('hello I am an error'), { theme: 'dark', quote: false, + sourcemap: true, }); expect(errorHandler).toBeInstanceOf(ErrorHandler); @@ -20,6 +21,7 @@ describe('ErrorHandler', () => { const errorHandler = new ErrorHandler(new Error('hello, another error'), { theme: 'dark', quote: false, + sourcemap: true, }); const result: any = await errorHandler.toJSON(); From f5c9e254e882d0bdb7cfce341d75f52f69baff92 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 3 Jan 2020 22:22:51 +0100 Subject: [PATCH 06/10] Refactoring and adding tests to satisfy the coverage requirements --- src/__tests__/error-handler.spec.ts | 3 ++- src/__tests__/flub-error-handler-e2e-spec.ts | 28 ++++++++++++++++++-- src/parser/synthetic-stack-trace.ts | 3 ++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/__tests__/error-handler.spec.ts b/src/__tests__/error-handler.spec.ts index 23a54ef..55622ba 100644 --- a/src/__tests__/error-handler.spec.ts +++ b/src/__tests__/error-handler.spec.ts @@ -21,7 +21,7 @@ describe('ErrorHandler', () => { const errorHandler = new ErrorHandler(new Error('hello, another error'), { theme: 'dark', quote: false, - sourcemap: true, + sourcemap: false, }); const result: any = await errorHandler.toJSON(); @@ -34,6 +34,7 @@ describe('ErrorHandler', () => { const errorHandler = new ErrorHandler(new Error('hello, error here'), { theme: 'dark', quote: false, + sourcemap: true, }); const html = await errorHandler.toHTML(); diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts b/src/__tests__/flub-error-handler-e2e-spec.ts index 9c74d6c..539a77d 100644 --- a/src/__tests__/flub-error-handler-e2e-spec.ts +++ b/src/__tests__/flub-error-handler-e2e-spec.ts @@ -2,16 +2,16 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Controller, Get, UseFilters, INestApplication } from '@nestjs/common'; import { FlubErrorHandler } from './../flub-error-handler'; import * as request from 'supertest'; +const fs = require('fs'); let flubModule: TestingModule; let app: INestApplication; @Controller('test') -@UseFilters(new FlubErrorHandler()) +@UseFilters(new FlubErrorHandler({ sourcemap: true })) class TestController { @Get('') testMe() { - return 'test'; throw new Error('standard error'); } @@ -22,6 +22,15 @@ class TestController { } beforeAll(async () => { + fs.writeFileSync( + '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', + '{"version":3,"file":"flub-error-handler-e2e-spec.js","sourceRoot":"","sources":["../../src/__tests__/flub-error-handler-e2e-spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAA+E;AAC/E,gEAA2D;AAC3D,qCAAqC;AAErC,IAAI,UAAyB,CAAC;AAC9B,IAAI,GAAqB,CAAC;AAI1B,IAAM,cAAc,GAApB,MAAM,cAAc;IAElB,MAAM;QACJ,OAAO,MAAM,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAGD,OAAO;QACL,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AATC;IADC,YAAG,CAAC,EAAE,CAAC;;;;4CAIP;AAGD;IADC,YAAG,CAAC,IAAI,CAAC;;;;6CAGT;AAVG,cAAc;IAFnB,mBAAU,CAAC,MAAM,CAAC;IAClB,mBAAU,CAAC,IAAI,qCAAgB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;GAChD,cAAc,CAWnB;AAED,SAAS,CAAC,GAAS,EAAE;IACnB,UAAU,GAAG,MAAM,cAAI,CAAC,mBAAmB,CAAC;QAC1C,WAAW,EAAE,CAAC,cAAc,CAAC;KAC9B,CAAC,CAAC,OAAO,EAAE,CAAC;IAEb,GAAG,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;IACzC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;AACnB,CAAC,CAAA,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,UAAU,EAAE,GAAS,EAAE;QACxB,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;aACtC,GAAG,CAAC,UAAU,CAAC;aACf,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC;aACjC,MAAM,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aAC9B,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAS,EAAE;IAClB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC,CAAA,CAAC,CAAC"}', + ); + fs.writeFileSync( + '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', + '{"version":3,"file":"error-handler.spec.js","sourceRoot":"","sources":["../../src/__tests__/error-handler.spec.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,sDAAkD;AAElD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,EAAE;YACtE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,MAAM,CAAC,YAAY,CAAC,CAAC,cAAc,CAAC,4BAAY,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QAExE,MAAM,CAAC,YAAY,CAAC,CAAC,cAAc,CAAC,4BAAY,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAS,EAAE;QAC/C,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE;YACvE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAQ,MAAM,YAAY,CAAC,MAAM,EAAE,CAAC;QAEhD,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC5D,CAAC,CAAA,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAS,EAAE;QAC/C,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,EAAE;YACpE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,CAAC;QAEzC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}', + ); + flubModule = await Test.createTestingModule({ controllers: [TestController], }).compile(); @@ -38,8 +47,23 @@ describe('FlubErrorHandler', () => { .expect(200, { success: true }) .expect('Content-Type', /json/); }); + + it('Errors out', async () => { + return await request(app.getHttpServer()) + .get('/test') + .set('Accept', 'application/json') + .expect(500) + .expect('Content-Type', /text\/html/); + }); }); afterAll(async () => { + fs.unlinkSync( + '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', + ); + fs.unlinkSync( + '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', + ); + await app.close(); }); diff --git a/src/parser/synthetic-stack-trace.ts b/src/parser/synthetic-stack-trace.ts index 68bb3c1..2d0c7e5 100644 --- a/src/parser/synthetic-stack-trace.ts +++ b/src/parser/synthetic-stack-trace.ts @@ -26,7 +26,8 @@ export class SyntheticStackTrace implements StackTraceInterface { return this.frame.getMethodName(); } getFileName() { - return this.originalSourceData.source.substring(1); + const source = this.originalSourceData.source; + return source ? source.substring(1) : ''; } getLineNumber() { return this.originalSourceData.line; From ff1bdecc56a04a134e15aa8a4de46e3496f38386 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 3 Jan 2020 22:29:59 +0100 Subject: [PATCH 07/10] Ignoring .map test unlink error to not falsly fail on CI --- src/__tests__/flub-error-handler-e2e-spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts b/src/__tests__/flub-error-handler-e2e-spec.ts index 539a77d..a5dc95c 100644 --- a/src/__tests__/flub-error-handler-e2e-spec.ts +++ b/src/__tests__/flub-error-handler-e2e-spec.ts @@ -58,11 +58,13 @@ describe('FlubErrorHandler', () => { }); afterAll(async () => { - fs.unlinkSync( + fs.unlink( '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', + (err) => { /* ignore error */} ); - fs.unlinkSync( + fs.unlink( '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', + (err) => { /* ignore error */} ); await app.close(); From 4f8d2dde078bde860d8027bd713f8acf6218c6e2 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 3 Jan 2020 22:40:07 +0100 Subject: [PATCH 08/10] Attempting to fix CI run issue which does not show up in local runs --- src/__tests__/flub-error-handler-e2e-spec.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts b/src/__tests__/flub-error-handler-e2e-spec.ts index a5dc95c..0681a25 100644 --- a/src/__tests__/flub-error-handler-e2e-spec.ts +++ b/src/__tests__/flub-error-handler-e2e-spec.ts @@ -58,14 +58,17 @@ describe('FlubErrorHandler', () => { }); afterAll(async () => { + await app.close(); fs.unlink( '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', - (err) => { /* ignore error */} + err => { + /* ignore error */ + }, ); fs.unlink( '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', - (err) => { /* ignore error */} + err => { + /* ignore error */ + }, ); - - await app.close(); }); From acaf95fb9ff0ca3686946218022b38c9d87468b9 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 3 Jan 2020 22:46:33 +0100 Subject: [PATCH 09/10] Still fixing CI-only issue in E2E test where app variable is not initialized --- src/__tests__/flub-error-handler-e2e-spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts b/src/__tests__/flub-error-handler-e2e-spec.ts index 0681a25..d74f389 100644 --- a/src/__tests__/flub-error-handler-e2e-spec.ts +++ b/src/__tests__/flub-error-handler-e2e-spec.ts @@ -58,7 +58,9 @@ describe('FlubErrorHandler', () => { }); afterAll(async () => { - await app.close(); + if (app) { + await app.close(); + } fs.unlink( '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', err => { From ff1a6fcbc78d7c9b35cd34bd9c62176c93dd80a4 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Sat, 4 Jan 2020 10:03:22 +0100 Subject: [PATCH 10/10] Adding the .map file to the __tests__ directory permanently so CI tests can hopefully execute --- src/__tests__/flub-error-handler-e2e-spec.ts | 25 +------------------ .../flub-error-handler-e2e-spec.ts.map | 1 + 2 files changed, 2 insertions(+), 24 deletions(-) create mode 100644 src/__tests__/flub-error-handler-e2e-spec.ts.map diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts b/src/__tests__/flub-error-handler-e2e-spec.ts index d74f389..28ba07e 100644 --- a/src/__tests__/flub-error-handler-e2e-spec.ts +++ b/src/__tests__/flub-error-handler-e2e-spec.ts @@ -22,15 +22,6 @@ class TestController { } beforeAll(async () => { - fs.writeFileSync( - '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', - '{"version":3,"file":"flub-error-handler-e2e-spec.js","sourceRoot":"","sources":["../../src/__tests__/flub-error-handler-e2e-spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAA+E;AAC/E,gEAA2D;AAC3D,qCAAqC;AAErC,IAAI,UAAyB,CAAC;AAC9B,IAAI,GAAqB,CAAC;AAI1B,IAAM,cAAc,GAApB,MAAM,cAAc;IAElB,MAAM;QACJ,OAAO,MAAM,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAGD,OAAO;QACL,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AATC;IADC,YAAG,CAAC,EAAE,CAAC;;;;4CAIP;AAGD;IADC,YAAG,CAAC,IAAI,CAAC;;;;6CAGT;AAVG,cAAc;IAFnB,mBAAU,CAAC,MAAM,CAAC;IAClB,mBAAU,CAAC,IAAI,qCAAgB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;GAChD,cAAc,CAWnB;AAED,SAAS,CAAC,GAAS,EAAE;IACnB,UAAU,GAAG,MAAM,cAAI,CAAC,mBAAmB,CAAC;QAC1C,WAAW,EAAE,CAAC,cAAc,CAAC;KAC9B,CAAC,CAAC,OAAO,EAAE,CAAC;IAEb,GAAG,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;IACzC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;AACnB,CAAC,CAAA,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,UAAU,EAAE,GAAS,EAAE;QACxB,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;aACtC,GAAG,CAAC,UAAU,CAAC;aACf,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC;aACjC,MAAM,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aAC9B,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAS,EAAE;IAClB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC,CAAA,CAAC,CAAC"}', - ); - fs.writeFileSync( - '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', - '{"version":3,"file":"error-handler.spec.js","sourceRoot":"","sources":["../../src/__tests__/error-handler.spec.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,sDAAkD;AAElD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,EAAE;YACtE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,MAAM,CAAC,YAAY,CAAC,CAAC,cAAc,CAAC,4BAAY,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QAExE,MAAM,CAAC,YAAY,CAAC,CAAC,cAAc,CAAC,4BAAY,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAS,EAAE;QAC/C,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE;YACvE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAQ,MAAM,YAAY,CAAC,MAAM,EAAE,CAAC;QAEhD,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC5D,CAAC,CAAA,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAS,EAAE;QAC/C,MAAM,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,EAAE;YACpE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,CAAC;QAEzC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}', - ); - flubModule = await Test.createTestingModule({ controllers: [TestController], }).compile(); @@ -58,19 +49,5 @@ describe('FlubErrorHandler', () => { }); afterAll(async () => { - if (app) { - await app.close(); - } - fs.unlink( - '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/flub-error-handler-e2e-spec.ts.map', - err => { - /* ignore error */ - }, - ); - fs.unlink( - '/Users/mtolmacs/Projects/nestjs-flub/src/__tests__/error-handler.spec.ts.map', - err => { - /* ignore error */ - }, - ); + await app.close(); }); diff --git a/src/__tests__/flub-error-handler-e2e-spec.ts.map b/src/__tests__/flub-error-handler-e2e-spec.ts.map new file mode 100644 index 0000000..86b048b --- /dev/null +++ b/src/__tests__/flub-error-handler-e2e-spec.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"flub-error-handler-e2e-spec.js","sourceRoot":"","sources":["../../src/__tests__/flub-error-handler-e2e-spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,6CAAsD;AACtD,2CAA+E;AAC/E,gEAA2D;AAC3D,qCAAqC;AAErC,IAAI,UAAyB,CAAC;AAC9B,IAAI,GAAqB,CAAC;AAI1B,IAAM,cAAc,GAApB,MAAM,cAAc;IAElB,MAAM;QACJ,OAAO,MAAM,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAGD,OAAO;QACL,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AATC;IADC,YAAG,CAAC,EAAE,CAAC;;;;4CAIP;AAGD;IADC,YAAG,CAAC,IAAI,CAAC;;;;6CAGT;AAVG,cAAc;IAFnB,mBAAU,CAAC,MAAM,CAAC;IAClB,mBAAU,CAAC,IAAI,qCAAgB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;GAChD,cAAc,CAWnB;AAED,SAAS,CAAC,GAAS,EAAE;IACnB,UAAU,GAAG,MAAM,cAAI,CAAC,mBAAmB,CAAC;QAC1C,WAAW,EAAE,CAAC,cAAc,CAAC;KAC9B,CAAC,CAAC,OAAO,EAAE,CAAC;IAEb,GAAG,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;IACzC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;AACnB,CAAC,CAAA,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,UAAU,EAAE,GAAS,EAAE;QACxB,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;aACtC,GAAG,CAAC,UAAU,CAAC;aACf,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC;aACjC,MAAM,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aAC9B,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAS,EAAE;IAClB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC,CAAA,CAAC,CAAC"} \ No newline at end of file