diff --git a/CHANGELOG.md b/CHANGELOG.md index c88edd1..cfd4797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,5 @@ # ts-error-handler + +## [1.0.0] + +Initial release diff --git a/README.md b/README.md index be64b5d..4ce546f 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,78 @@ # ts-error-handler -## Development +This package enhances typescript error handling, meaning easier troubleshooting and debugging! -Run a dev test with `npm start` +- Source mapping +- Customizable stack traces +- Set stack trace limit +- Set stack trace to "Just my Code" +- Sets global uncaught global error handlers -## Running Tests +## Setup -To run unit tests, `npm run test` +**Install** -## Compiling +`npm i ts-error-handler` -### Debug Builds +**Usage** -To compile a debug build, run `npm run build:dev`. The build output will appear in the `./dist` folder. +```typescript +import { setupErrorHandling } from 'ts-error-handler'; -### Prod Builds +// Setup error handling options +setupErrorHandling({ + justMyCode: true, + handler: (e) => console.error('Error!', e) +}) -To compile a production build, run `npm run build:prod`. The build output will appear in the `./dist` folder. +// Example: +throw new Error('Help!'); +``` -### Clean Builds +**With ts-async-bootstrap** -To generate a clean build (removes old artifacts and reruns pre&post process scripts), append `:clean` to a build script: -- Debug: `npm run build:dev:clean` -- Release: `npm run build:prod:clean` +```typescript +import { bootstrap } from 'ts-async-bootstrap'; +import { setupErrorHandling } from 'ts-error-handler'; -## More +import { register, main } from './somewhere-else'; -### Generating Docs +function errorHandler(e: Error): void { + console.error('Error!', e); +} -`npm run doc` and browse docs/index.html! +// Setup error handling +setupErrorHandling({ + handler: errorHandler +}); + +// Pass handler from ts-error-handler to bootstrap() +bootstrap({ + register: register, + run: main, + errorHandler: errorHandler +}); +``` -### Environment Variables +## Options -Environment variables should be set in .env -To add additional environment variables, edit `src/environment.ts`: +**traceLimit** -```typescript -/** - * Environment Variables Schema - */ -export interface Environment { - APP_TITLE: string +Set how long the stack trace is, in lines. Default is 100 - // TODO: Add additional allowed variables -} +**justMyCode** -/** - * Default Values - */ -const defaults: Environment = { - APP_TITLE: 'ts-error-handler' +Set to true to show only local code in stack traces (no node-modules or node-internals) - // TODO: Set default variables here -}; -``` +**justMyCodeIncludeNodeModules** + +Set to true to show node-modules in stack traces when justMyCode is on + +**justMyCodeIncludeInternals** + +Set to true to show node-internals in stack traces when justMyCode is on + +**handler** + +Global error handler for uncaught exceptions/promise-rejections. Default is `console.error` diff --git a/package-lock.json b/package-lock.json index 8a4454b..f1b6e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ts-error-handler", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -431,9 +431,9 @@ "dev": true }, "@types/node": { - "version": "12.20.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.41.tgz", - "integrity": "sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q==", + "version": "12.20.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", + "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -1473,27 +1473,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1988,9 +1967,9 @@ "dev": true }, "marked": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.7.tgz", - "integrity": "sha512-ctKqbnLuNbsHbI26cfMyOlKgXGfl1orOv1AvWWDX7AkgfMOwCWvmuYc+mVLeWhQ9W6hdWVBynOs96VkcscKo0Q==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", + "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", "dev": true }, "merge2": { @@ -2051,12 +2030,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -2133,32 +2106,6 @@ "wrappy": "1" } }, - "onigasm": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz", - "integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==", - "dev": true, - "requires": { - "lru-cache": "^5.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -2466,13 +2413,13 @@ "dev": true }, "shiki": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.11.tgz", - "integrity": "sha512-tjruNTLFhU0hruCPoJP0y+B9LKOmcqUhTpxn7pcJB3fa+04gFChuEmxmrUfOJ7ZO6Jd+HwMnDHgY3lv3Tqonuw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.0.tgz", + "integrity": "sha512-iczxaIYeBFHTFrQPb9DVy2SKgYxC4Wo7Iucm7C17cCh2Ge/refnvHscUOxM85u57MfLoNOtjoEFUWt9gBexblA==", "dev": true, "requires": { "jsonc-parser": "^3.0.0", - "onigasm": "^2.2.5", + "vscode-oniguruma": "^1.6.1", "vscode-textmate": "5.2.0" } }, @@ -2806,19 +2753,16 @@ } }, "typedoc": { - "version": "0.21.9", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.21.9.tgz", - "integrity": "sha512-VRo7aII4bnYaBBM1lhw4bQFmUcDQV8m8tqgjtc7oXl87jc1Slbhfw2X5MccfcR2YnEClHDWgsiQGgNB8KJXocA==", + "version": "0.22.11", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz", + "integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==", "dev": true, "requires": { - "glob": "^7.1.7", - "handlebars": "^4.7.7", + "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^3.0.2", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shiki": "^0.9.8", - "typedoc-default-themes": "^0.12.10" + "marked": "^4.0.10", + "minimatch": "^3.0.4", + "shiki": "^0.10.0" }, "dependencies": { "glob": { @@ -2837,12 +2781,6 @@ } } }, - "typedoc-default-themes": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz", - "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", - "dev": true - }, "typescript": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", @@ -2855,13 +2793,6 @@ "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", "dev": true }, - "uglify-js": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", - "integrity": "sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==", - "dev": true, - "optional": true - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -2900,6 +2831,12 @@ "extsprintf": "^1.2.0" } }, + "vscode-oniguruma": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", + "dev": true + }, "vscode-textmate": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", @@ -2927,12 +2864,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 5e7d2fe..95a0d00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-error-handler", - "version": "0.0.0", + "version": "1.0.0", "ts-project-version": "1.2.4", "private": "true", "scripts": { @@ -28,7 +28,7 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/jasmine": "^3.10.3", - "@types/node": "^12.20.41", + "@types/node": "^12.20.42", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "coveralls": "^3.1.1", @@ -37,7 +37,7 @@ "nyc": "^15.1.0", "ts-node": "^10.4.0", "ts-packager": "^1.0.1", - "typedoc": "^0.21.9", + "typedoc": "^0.22.11", "typescript": "^4.5.4" } } diff --git a/src/handler-function.ts b/src/handler-function.ts new file mode 100644 index 0000000..6e49f17 --- /dev/null +++ b/src/handler-function.ts @@ -0,0 +1,4 @@ +/** + * A handler function takes an Error and returns void + */ +export type HandlerFunction = (e: Error) => void; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7d1ceed --- /dev/null +++ b/src/index.ts @@ -0,0 +1,87 @@ +/** + * Source map support + */ +if (!process[Symbol.for('ts-node.register.instance')]) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('source-map-support').install({ + environment: 'node' + }); +} + +import { HandlerFunction } from 'handler-function'; + +/** + * Options for setupErrorHandling + */ +export interface ErrorHandlerOptions { + // Stack trace line-limit + traceLimit: number, + + // Ignore node_modules and node internals? + justMyCode: boolean, + + // Ignore node_modules (justMyCode must be true) + justMyCodeIncludeNodeModules: boolean, + + // Ignore node internals? (justMyCode must be true) + justMyCodeIncludeInternals: boolean, + + // Error handler + handler?: HandlerFunction, +} + +/** + * Default options + */ +export const defaultOptions: ErrorHandlerOptions = { + traceLimit: 100, + justMyCode: true, + justMyCodeIncludeNodeModules: false, + justMyCodeIncludeInternals: false, + handler: console.error +}; + +/** + * Setup error handling + * + * @param options Options + */ +export function setupErrorHandling(options: Partial) { + options = { ...defaultOptions, ...options }; + + /** + * Register global handler for uncaught errors + */ + process.on('uncaughtException', options.handler); + process.on('unhandledRejection', options.handler); + + /** + * Stack traces + */ + // Set trace limit + Error.stackTraceLimit = options.traceLimit; + + // Set traces to "Just My Code" + if (options.justMyCode) { + const nativePrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (err, traces) => { + return nativePrepareStackTrace(err, traces) + .split('\n') + .filter(line => { + return ( + line && + ( + options.justMyCodeIncludeNodeModules || + !line.includes('node_modules') + ) && + ( + options.justMyCodeIncludeInternals || + !/internal\/[a-zA-Z]+/.test(line) + ) + ); + }) + .map(line => line.replace('Object. ', '')) + .join('\n'); + }; + } +} diff --git a/test/spec/index.spec.ts b/test/spec/index.spec.ts new file mode 100644 index 0000000..acc576b --- /dev/null +++ b/test/spec/index.spec.ts @@ -0,0 +1,18 @@ +import 'jasmine'; +import { setupErrorHandling } from '../../src'; + +import { getStackTrace } from '../util/get-stack-trace'; + +describe('index', () => { + it('sets stack trace to "Just My Code"', () => { + setupErrorHandling({ + justMyCode: true + }); + + const trace = getStackTrace(); + + expect(trace).toContain('get-stack-trace'); + expect(trace).not.toContain('node_modules'); + expect(trace).not.toContain('internal/'); + }); +}); diff --git a/test/util/get-stack-trace.ts b/test/util/get-stack-trace.ts new file mode 100644 index 0000000..2e723ed --- /dev/null +++ b/test/util/get-stack-trace.ts @@ -0,0 +1,13 @@ +/** + * Get a stack trace + * + * @returns Returns the trace + */ +export function getStackTrace(): string { + try { + throw new Error(); + } + catch (e) { + return e.stack; + } +}