From c46075aaab42054fdfb95f2dc6436ad62fd602d6 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 17 Apr 2020 09:00:28 +0100 Subject: [PATCH 01/13] Make constructor take only message and info --- libraries/o-error/http.js | 4 ++-- libraries/o-error/index.js | 2 +- libraries/o-error/test/http.test.js | 20 +++++++++++++++----- libraries/o-error/test/o-error.test.js | 24 ++++++++++++------------ 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/libraries/o-error/http.js b/libraries/o-error/http.js index e6c1bd5a0b..f5e03ec05f 100644 --- a/libraries/o-error/http.js +++ b/libraries/o-error/http.js @@ -1,8 +1,8 @@ const OError = require('./index') class HttpError extends OError { - constructor(options) { - super(options) + constructor({ message, info, ...options }) { + super(message, info) this.statusCode = options.statusCode || 500 } } diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index f7259b177e..7e4e48abda 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -21,7 +21,7 @@ class OError extends Error { * @param {string} message as for built-in Error * @param {?object} info extra data to attach to the error */ - constructor({ message, info }) { + constructor(message, info) { super(message) this.name = this.constructor.name if (info) { diff --git a/libraries/o-error/test/http.test.js b/libraries/o-error/test/http.test.js index 1d1bac0035..210ee535b3 100644 --- a/libraries/o-error/test/http.test.js +++ b/libraries/o-error/test/http.test.js @@ -1,12 +1,22 @@ -const OError = require('..') const HttpErrors = require('../http') +const { expectError } = require('./support') + describe('OError/http', function () { - it('is instance of OError', function () { - try { + it('is a valid OError', function () { + function foo() { throw new HttpErrors.ConflictError() - } catch (e) { - expect(e).to.be.instanceof(OError) + } + + try { + foo() + } catch (error) { + expectError(error, { + name: 'ConflictError', + klass: HttpErrors.ConflictError, + message: 'ConflictError: Conflict', + firstFrameRx: /foo/, + }) } }) diff --git a/libraries/o-error/test/o-error.test.js b/libraries/o-error/test/o-error.test.js index 897b8b0d62..86bb6703b7 100644 --- a/libraries/o-error/test/o-error.test.js +++ b/libraries/o-error/test/o-error.test.js @@ -2,14 +2,14 @@ const OError = require('..') const { expectError } = require('./support') class CustomError1 extends OError { - constructor(options) { - super({ message: 'failed to foo', ...options }) + constructor(info) { + super('failed to foo', info) } } class CustomError2 extends OError { - constructor(options) { - super({ message: 'failed to bar', ...options }) + constructor(customMessage, info) { + super(customMessage || 'failed to bar', info) } } @@ -23,7 +23,7 @@ describe('OError', function () { try { doSomethingBadInternally() } catch (err) { - throw new CustomError1({ info: { userId: 123 } }).withCause(err) + throw new CustomError1({ userId: 123 }).withCause(err) } } @@ -55,7 +55,7 @@ describe('OError', function () { try { doSomethingBadInternally() } catch (err) { - throw new CustomError2({ info: { database: 'a' } }).withCause(err) + throw new CustomError2('failed to bar!', { inner: 'a' }).withCause(err) } } @@ -63,7 +63,7 @@ describe('OError', function () { try { doBar() } catch (err) { - throw new CustomError1({ info: { userId: 123 } }).withCause(err) + throw new CustomError1({ userId: 123 }).withCause(err) } } @@ -74,19 +74,19 @@ describe('OError', function () { expectError(e, { name: 'CustomError1', klass: CustomError1, - message: 'CustomError1: failed to foo: failed to bar: internal error', + message: 'CustomError1: failed to foo: failed to bar!: internal error', firstFrameRx: /doFoo/, }) expect(OError.getFullInfo(e)).to.deep.equal({ userId: 123, - database: 'a', + inner: 'a', }) const fullStack = OError.getFullStack(e) expect(fullStack).to.match( - /^CustomError1: failed to foo: failed to bar: internal error$/m + /^CustomError1: failed to foo: failed to bar!: internal error$/m ) expect(fullStack).to.match( - /^caused by: CustomError2: failed to bar: internal error$/m + /^caused by: CustomError2: failed to bar!: internal error$/m ) expect(fullStack).to.match(/^caused by: Error: internal error$/m) } @@ -94,7 +94,7 @@ describe('OError', function () { it('handles a custom error without info', function () { try { - throw new CustomError1({}) + throw new CustomError1() } catch (e) { expect(OError.getFullInfo(e)).to.deep.equal({}) const infoKey = Object.keys(e).find((k) => k === 'info') From 1a38f4e4ffc149f5dcac33a429cad5d98f99abab Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Tue, 28 Apr 2020 20:38:34 +0100 Subject: [PATCH 02/13] Add typescript for type checking --- libraries/o-error/.eslintrc.json | 3 --- libraries/o-error/package-lock.json | 12 ++++++++++++ libraries/o-error/package.json | 7 +++++-- libraries/o-error/test/http.test.js | 2 ++ libraries/o-error/test/o-error-util.test.js | 1 + libraries/o-error/test/o-error.test.js | 2 ++ libraries/o-error/test/support/index.js | 6 ++---- 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/libraries/o-error/.eslintrc.json b/libraries/o-error/.eslintrc.json index 6d31095967..1469158283 100644 --- a/libraries/o-error/.eslintrc.json +++ b/libraries/o-error/.eslintrc.json @@ -17,9 +17,6 @@ "files": ["test/**/*.js"], "env": { "mocha": true - }, - "globals": { - "expect": "readonly" } } ] diff --git a/libraries/o-error/package-lock.json b/libraries/o-error/package-lock.json index 9f8ca73d3c..01f5b60431 100644 --- a/libraries/o-error/package-lock.json +++ b/libraries/o-error/package-lock.json @@ -36,6 +36,12 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/node": { + "version": "13.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", + "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==", + "dev": true + }, "acorn": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", @@ -2207,6 +2213,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/libraries/o-error/package.json b/libraries/o-error/package.json index 6256793d16..90aa4e170d 100644 --- a/libraries/o-error/package.json +++ b/libraries/o-error/package.json @@ -5,12 +5,14 @@ "main": "index.js", "scripts": { "lint": "eslint .", - "test": "mocha --require test/support" + "test": "mocha", + "typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js" }, "author": "Overleaf (https://www.overleaf.com)", "license": "MIT", "repository": "github:overleaf/o-error", "devDependencies": { + "@types/node": "^13.13.2", "chai": "^3.3.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.1", @@ -24,6 +26,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "mocha": "^7.1.1", - "prettier": "^2.0.2" + "prettier": "^2.0.2", + "typescript": "^3.8.3" } } diff --git a/libraries/o-error/test/http.test.js b/libraries/o-error/test/http.test.js index 210ee535b3..94886ee854 100644 --- a/libraries/o-error/test/http.test.js +++ b/libraries/o-error/test/http.test.js @@ -1,3 +1,5 @@ +const { expect } = require('chai') + const HttpErrors = require('../http') const { expectError } = require('./support') diff --git a/libraries/o-error/test/o-error-util.test.js b/libraries/o-error/test/o-error-util.test.js index f803a7feda..06fb22b04c 100644 --- a/libraries/o-error/test/o-error-util.test.js +++ b/libraries/o-error/test/o-error-util.test.js @@ -1,4 +1,5 @@ const { getFullInfo, getFullStack, hasCauseInstanceOf } = require('..') +const { expect } = require('chai') describe('OError.getFullInfo', function () { it('works on a normal error', function () { diff --git a/libraries/o-error/test/o-error.test.js b/libraries/o-error/test/o-error.test.js index 86bb6703b7..30318e416f 100644 --- a/libraries/o-error/test/o-error.test.js +++ b/libraries/o-error/test/o-error.test.js @@ -1,3 +1,5 @@ +const { expect } = require('chai') + const OError = require('..') const { expectError } = require('./support') diff --git a/libraries/o-error/test/support/index.js b/libraries/o-error/test/support/index.js index 009391d62d..52a81d9d7b 100644 --- a/libraries/o-error/test/support/index.js +++ b/libraries/o-error/test/support/index.js @@ -1,8 +1,6 @@ -'use strict' +const { expect } = require('chai') -var chai = require('chai') - -global.expect = chai.expect +const OError = require('../..') exports.expectError = function OErrorExpectError(e, expected) { // should set the name to the error's name From 09cd72d51c5afd9783d210081855618d9a9967fc Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Tue, 28 Apr 2020 20:40:20 +0100 Subject: [PATCH 03/13] Rework to favor tagging over wrapping --- libraries/o-error/index.js | 169 +++++++----- libraries/o-error/test/o-error-util.test.js | 272 ++++++++++++++++---- libraries/o-error/test/o-error.test.js | 95 +++---- libraries/o-error/test/support/index.js | 9 + 4 files changed, 391 insertions(+), 154 deletions(-) diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index 7e4e48abda..3aea8c3635 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -1,32 +1,32 @@ -'use strict' - /** - * Make custom error types that pass `instanceof` checks, have stack traces, - * support custom messages and properties, and support wrapping errors (causes). - * - * @module - */ - -/** - * A base class for custom errors that handles: - * - * 1. Wrapping an optional 'cause'. - * 2. Storing an 'info' object with additional data. - * 3. Setting the name to the subclass name. - * - * @extends Error + * Light-weight helpers for handling JavaScript Errors in node.js and the + * browser. {@see README.md} */ class OError extends Error { /** * @param {string} message as for built-in Error - * @param {?object} info extra data to attach to the error + * @param {Object} [info] extra data to attach to the error + * @param {Error} [cause] */ - constructor(message, info) { + constructor(message, info, cause) { super(message) this.name = this.constructor.name - if (info) { - this.info = info - } + if (info) this.info = info + if (cause) this.cause = cause + + /** @type {Array} */ + this._oErrorTags // eslint-disable-line + } + + /** + * Set the extra info object for this error. + * + * @param {Object | null | undefined} info + * @return {this} + */ + withInfo(info) { + this.info = info + return this } /** @@ -37,57 +37,98 @@ class OError extends Error { */ withCause(cause) { this.cause = cause - if (this.message && cause.message) { - this.message += ': ' + cause.message - } return this } + + /** + * Tag debugging information onto any error (whether an OError or not) and + * return it. + * + * @param {Error} error + * @param {string} [message] + * @param {Object} [info] + * @return {Error} the modified `error` argument + */ + static tag(error, message, info) { + const oError = /** @type{OError} */ (error) + + if (!oError._oErrorTags) oError._oErrorTags = [] + + const tag = new TaggedError(message, info) + + // Hide this function in the stack trace. + if (Error.captureStackTrace) Error.captureStackTrace(tag, OError.tag) + + oError._oErrorTags.push(tag) + + return error + } + + /** + * The merged info from any `tag`s on the given error. + * + * If an info property is repeated, the last one wins. + * + * @param {Error} error + * @return {Object} + */ + static getFullInfo(error) { + const info = {} + + if (!error) return info + + const oError = /** @type{OError} */ (error) + + if (typeof oError.info === 'object') Object.assign(info, oError.info) + + if (oError._oErrorTags) { + for (const tag of oError._oErrorTags) { + Object.assign(info, tag.info) + } + } + + return info + } + + /** + * Return the `stack` property from `error`, including the `stack`s for any + * tagged errors added with `OError.tag` and for any `cause`s. + * + * @param {Error | null | undefined} error + * @return {string} + */ + static getFullStack(error) { + if (!error) return '' + + const oError = /** @type{OError} */ (error) + + let stack = oError.stack + + if (oError._oErrorTags) { + for (const tag of oError._oErrorTags) { + stack += `\n${tag.stack}` + } + } + + const causeStack = oError.cause && OError.getFullStack(oError.cause) + if (causeStack) { + stack += '\ncaused by:\n' + indent(causeStack) + } + + return stack + } } /** - * Return the `info` property from `error` and recursively merge the `info` - * properties from the error's causes, if any. + * Used to record a stack trace every time we tag info onto an Error. * - * If a property is repeated, the first one in the cause chain wins. - * - * @param {?Error} error assumed not to have circular causes - * @return {Object} + * @private + * @extends OError */ -function getFullInfo(error) { - if (!error) return {} - const info = getFullInfo(error.cause) - if (typeof error.info === 'object') Object.assign(info, error.info) - return info -} +class TaggedError extends OError {} -/** - * Return the `stack` property from `error` and recursively append the `stack` - * properties from the error's causes, if any. - * - * @param {?Error} error assumed not to have circular causes - * @return {string} - */ -function getFullStack(error) { - if (!error) return '' - const causeStack = getFullStack(error.cause) - if (causeStack) return error.stack + '\ncaused by: ' + causeStack - return error.stack +function indent(string) { + return string.replace(/^/gm, ' ') } -/** - * Is `error` or one of its causes an instance of `klass`? - * - * @param {?Error} error assumed not to have circular causes - * @param {function} klass - * @return {Boolean} - */ -function hasCauseInstanceOf(error, klass) { - if (!error) return false - return error instanceof klass || hasCauseInstanceOf(error.cause, klass) -} - -OError.getFullInfo = getFullInfo -OError.getFullStack = getFullStack -OError.hasCauseInstanceOf = hasCauseInstanceOf - module.exports = OError diff --git a/libraries/o-error/test/o-error-util.test.js b/libraries/o-error/test/o-error-util.test.js index 06fb22b04c..dd7d28654d 100644 --- a/libraries/o-error/test/o-error-util.test.js +++ b/libraries/o-error/test/o-error-util.test.js @@ -1,53 +1,201 @@ -const { getFullInfo, getFullStack, hasCauseInstanceOf } = require('..') const { expect } = require('chai') +const { promisify } = require('util') + +const OError = require('..') + +const { + expectError, + expectFullStackWithoutStackFramesToEqual, +} = require('./support') + +describe('OError.tag', function () { + it('tags errors thrown from an async function', async function () { + const delay = promisify(setTimeout) + + async function foo() { + await delay(10) + throw new Error('foo error') + } + + async function bar() { + try { + await foo() + } catch (error) { + throw OError.tag(error, 'failed to bar', { bar: 'baz' }) + } + } + + async function baz() { + try { + await bar() + } catch (error) { + throw OError.tag(error, 'failed to baz', { baz: 'bat' }) + } + } + + try { + await baz() + expect.fail('should have thrown') + } catch (error) { + expectError(error, { + name: 'Error', + klass: Error, + message: 'Error: foo error', + firstFrameRx: /at foo/, + }) + expectFullStackWithoutStackFramesToEqual(error, [ + 'Error: foo error', + 'TaggedError: failed to bar', + 'TaggedError: failed to baz', + ]) + expect(OError.getFullInfo(error)).to.eql({ + bar: 'baz', + baz: 'bat', + }) + } + }) + + it('tags errors thrown from a promise rejection', async function () { + function foo() { + return new Promise((resolve, reject) => { + setTimeout(function () { + reject(new Error('foo error')) + }, 10) + }) + } + + async function bar() { + try { + await foo() + } catch (error) { + throw OError.tag(error, 'failed to bar', { bar: 'baz' }) + } + } + + async function baz() { + try { + await bar() + } catch (error) { + throw OError.tag(error, 'failed to baz', { baz: 'bat' }) + } + } + + try { + await baz() + expect.fail('should have thrown') + } catch (error) { + expectError(error, { + name: 'Error', + klass: Error, + message: 'Error: foo error', + firstFrameRx: /_onTimeout/, + }) + expectFullStackWithoutStackFramesToEqual(error, [ + 'Error: foo error', + 'TaggedError: failed to bar', + 'TaggedError: failed to baz', + ]) + expect(OError.getFullInfo(error)).to.eql({ + bar: 'baz', + baz: 'bat', + }) + } + }) + + it('tags errors yielded through callbacks', function (done) { + function foo(cb) { + setTimeout(function () { + cb(new Error('foo error')) + }, 10) + } + + function bar(cb) { + foo(function (err) { + if (err) { + return cb(OError.tag(err, 'failed to bar', { bar: 'baz' })) + } + cb() + }) + } + + function baz(cb) { + bar(function (err) { + if (err) { + return cb(OError.tag(err, 'failed to baz', { baz: 'bat' })) + } + cb() + }) + } + + baz(function (err) { + if (err) { + expectError(err, { + name: 'Error', + klass: Error, + message: 'Error: foo error', + firstFrameRx: /_onTimeout/, + }) + expectFullStackWithoutStackFramesToEqual(err, [ + 'Error: foo error', + 'TaggedError: failed to bar', + 'TaggedError: failed to baz', + ]) + expect(OError.getFullInfo(err)).to.eql({ + bar: 'baz', + baz: 'bat', + }) + return done() + } + expect.fail('should have yielded an error') + }) + }) +}) describe('OError.getFullInfo', function () { it('works on a normal error', function () { const err = new Error('foo') - expect(getFullInfo(err)).to.deep.equal({}) + expect(OError.getFullInfo(err)).to.deep.equal({}) }) - it('works on an error with .info', function () { - const err = new Error('foo') - err.info = { userId: 123 } - expect(getFullInfo(err)).to.deep.equal({ userId: 123 }) + it('works on an error with tags', function () { + const err = OError.tag(new Error('foo'), 'bar', { userId: 123 }) + expect(OError.getFullInfo(err)).to.deep.equal({ userId: 123 }) }) - it('merges info from a cause chain', function () { + it('merges info from an error and its tags', function () { + const err = new OError('foo').withInfo({ projectId: 456 }) + OError.tag(err, 'failed to foo', { userId: 123 }) + expect(OError.getFullInfo(err)).to.deep.equal({ + projectId: 456, + userId: 123, + }) + }) + + it('does not merge info from a cause', function () { const err1 = new Error('foo') const err2 = new Error('bar') err1.cause = err2 err2.info = { userId: 123 } - expect(getFullInfo(err1)).to.deep.equal({ userId: 123 }) + expect(OError.getFullInfo(err1)).to.deep.equal({}) }) - it('merges info from a cause chain with no info', function () { - const err1 = new Error('foo') - const err2 = new Error('bar') - err1.cause = err2 - expect(getFullInfo(err1)).to.deep.equal({}) - }) - - it('merges info from a cause chain with duplicate keys', function () { - const err1 = new Error('foo') - const err2 = new Error('bar') - err1.cause = err2 - err1.info = { userId: 123 } - err2.info = { userId: 456 } - expect(getFullInfo(err1)).to.deep.equal({ userId: 123 }) + it('merges info from tags with duplicate keys', function () { + const err1 = OError.tag(new Error('foo'), 'bar', { userId: 123 }) + const err2 = OError.tag(err1, 'bat', { userId: 456 }) + expect(OError.getFullInfo(err2)).to.deep.equal({ userId: 456 }) }) it('works on an error with .info set to a string', function () { const err = new Error('foo') err.info = 'test' - expect(getFullInfo(err)).to.deep.equal({}) + expect(OError.getFullInfo(err)).to.deep.equal({}) }) }) describe('OError.getFullStack', function () { it('works on a normal error', function () { const err = new Error('foo') - const fullStack = getFullStack(err) + const fullStack = OError.getFullStack(err) expect(fullStack).to.match(/^Error: foo$/m) expect(fullStack).to.match(/^\s+at /m) }) @@ -57,28 +205,62 @@ describe('OError.getFullStack', function () { const err2 = new Error('bar') err1.cause = err2 - const fullStack = getFullStack(err1) + const fullStack = OError.getFullStack(err1) expect(fullStack).to.match(/^Error: foo$/m) expect(fullStack).to.match(/^\s+at /m) - expect(fullStack).to.match(/^caused by: Error: bar$/m) - }) -}) - -describe('OError.hasCauseInstanceOf', function () { - it('works on a normal error', function () { - const err = new Error('foo') - expect(hasCauseInstanceOf(null, Error)).to.be.false - expect(hasCauseInstanceOf(err, Error)).to.be.true - expect(hasCauseInstanceOf(err, RangeError)).to.be.false - }) - - it('works on an error with a cause', function () { - const err1 = new Error('foo') - const err2 = new RangeError('bar') - err1.cause = err2 - - expect(hasCauseInstanceOf(err1, Error)).to.be.true - expect(hasCauseInstanceOf(err1, RangeError)).to.be.true - expect(hasCauseInstanceOf(err1, TypeError)).to.be.false + expect(fullStack).to.match(/^caused by:\n\s+Error: bar$/m) + }) + + it('works on both tags and causes', async function () { + // Here's the actual error. + function tryToFoo() { + try { + throw Error('foo') + } catch (error) { + throw OError.tag(error, 'failed to foo', { foo: 1 }) + } + } + + // Inside another function that wraps it. + function tryToBar() { + try { + tryToFoo() + } catch (error) { + throw new OError('failed to bar').withCause(error) + } + } + + // And it is in another try. + try { + try { + tryToBar() + expect.fail('should have thrown') + } catch (error) { + throw OError.tag(error, 'failed to bat', { bat: 1 }) + } + } catch (error) { + // We catch the wrapping error. + expectError(error, { + name: 'OError', + klass: OError, + message: 'OError: failed to bar', + firstFrameRx: /tryToBar/, + }) + + // But the stack contains all of the errors and tags. + expectFullStackWithoutStackFramesToEqual(error, [ + 'OError: failed to bar', + 'TaggedError: failed to bat', + 'caused by:', + ' Error: foo', + ' TaggedError: failed to foo', + ]) + + // The info from the wrapped cause should not leak out. + expect(OError.getFullInfo(error)).to.eql({ bat: 1 }) + + // But it should still be recorded. + expect(OError.getFullInfo(error.cause)).to.eql({ foo: 1 }) + } }) }) diff --git a/libraries/o-error/test/o-error.test.js b/libraries/o-error/test/o-error.test.js index 30318e416f..84244ec92e 100644 --- a/libraries/o-error/test/o-error.test.js +++ b/libraries/o-error/test/o-error.test.js @@ -1,21 +1,40 @@ const { expect } = require('chai') const OError = require('..') -const { expectError } = require('./support') +const { + expectError, + expectFullStackWithoutStackFramesToEqual, +} = require('./support') class CustomError1 extends OError { - constructor(info) { - super('failed to foo', info) + constructor() { + super('failed to foo') } } class CustomError2 extends OError { - constructor(customMessage, info) { - super(customMessage || 'failed to bar', info) + constructor(customMessage) { + super(customMessage || 'failed to bar') } } describe('OError', function () { + it('can have an info object', function () { + const err1 = new OError('foo', { foo: 1 }) + expect(err1.info).to.eql({ foo: 1 }) + + const err2 = new OError('foo').withInfo({ foo: 2 }) + expect(err2.info).to.eql({ foo: 2 }) + }) + + it('can have a cause', function () { + const err1 = new OError('foo', { foo: 1 }, new Error('cause 1')) + expect(err1.cause.message).to.equal('cause 1') + + const err2 = new OError('foo').withCause(new Error('cause 2')) + expect(err2.cause.message).to.equal('cause 2') + }) + it('handles a custom error type with a cause', function () { function doSomethingBadInternally() { throw new Error('internal error') @@ -24,27 +43,27 @@ describe('OError', function () { function doSomethingBad() { try { doSomethingBadInternally() - } catch (err) { - throw new CustomError1({ userId: 123 }).withCause(err) + } catch (error) { + throw new CustomError1().withCause(error) } } try { doSomethingBad() expect.fail('should have thrown') - } catch (e) { - expectError(e, { + } catch (error) { + expectError(error, { name: 'CustomError1', klass: CustomError1, - message: 'CustomError1: failed to foo: internal error', + message: 'CustomError1: failed to foo', firstFrameRx: /doSomethingBad/, }) - expect(OError.getFullInfo(e)).to.deep.equal({ userId: 123 }) - const fullStack = OError.getFullStack(e) - expect(fullStack).to.match( - /^CustomError1: failed to foo: internal error$/m - ) - expect(fullStack).to.match(/^caused by: Error: internal error$/m) + expect(OError.getFullInfo(error)).to.deep.equal({}) + expectFullStackWithoutStackFramesToEqual(error, [ + 'CustomError1: failed to foo', + 'caused by:', + ' Error: internal error', + ]) } }) @@ -56,51 +75,37 @@ describe('OError', function () { function doBar() { try { doSomethingBadInternally() - } catch (err) { - throw new CustomError2('failed to bar!', { inner: 'a' }).withCause(err) + } catch (error) { + throw new CustomError2('failed to bar!').withCause(error) } } function doFoo() { try { doBar() - } catch (err) { - throw new CustomError1({ userId: 123 }).withCause(err) + } catch (error) { + throw new CustomError1().withCause(error) } } try { doFoo() expect.fail('should have thrown') - } catch (e) { - expectError(e, { + } catch (error) { + expectError(error, { name: 'CustomError1', klass: CustomError1, - message: 'CustomError1: failed to foo: failed to bar!: internal error', + message: 'CustomError1: failed to foo', firstFrameRx: /doFoo/, }) - expect(OError.getFullInfo(e)).to.deep.equal({ - userId: 123, - inner: 'a', - }) - const fullStack = OError.getFullStack(e) - expect(fullStack).to.match( - /^CustomError1: failed to foo: failed to bar!: internal error$/m - ) - expect(fullStack).to.match( - /^caused by: CustomError2: failed to bar!: internal error$/m - ) - expect(fullStack).to.match(/^caused by: Error: internal error$/m) - } - }) - - it('handles a custom error without info', function () { - try { - throw new CustomError1() - } catch (e) { - expect(OError.getFullInfo(e)).to.deep.equal({}) - const infoKey = Object.keys(e).find((k) => k === 'info') - expect(infoKey).to.not.exist + expectFullStackWithoutStackFramesToEqual(error, [ + 'CustomError1: failed to foo', + 'caused by:', + ' CustomError2: failed to bar!', + ' caused by:', + ' Error: internal error', + ]) + expect(OError.getFullInfo(error)).to.deep.equal({}) } }) }) diff --git a/libraries/o-error/test/support/index.js b/libraries/o-error/test/support/index.js index 52a81d9d7b..afa90c003e 100644 --- a/libraries/o-error/test/support/index.js +++ b/libraries/o-error/test/support/index.js @@ -27,3 +27,12 @@ exports.expectError = function OErrorExpectError(e, expected) { // first stack frame should be the function where the error was thrown expect(e.stack.split('\n')[1]).to.match(expected.firstFrameRx) } + +exports.expectFullStackWithoutStackFramesToEqual = function (error, expected) { + // But the stack contains all of the errors and tags. + const fullStack = OError.getFullStack(error) + const fullStackWithoutFrames = fullStack + .split('\n') + .filter((line) => !/^\s+at\s/.test(line)) + expect(fullStackWithoutFrames).to.deep.equal(expected) +} From f6eb185f84acd4353cda508007be5b92d60c458c Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 29 Apr 2020 20:58:10 +0100 Subject: [PATCH 04/13] Update README for v3 --- libraries/o-error/README.md | 458 +++++++-- libraries/o-error/doc/demo.js | 141 +++ libraries/o-error/doc/update-readme.js | 49 + libraries/o-error/index.js | 20 +- libraries/o-error/package-lock.json | 1205 ++++++++++++++++++++++++ libraries/o-error/package.json | 3 + 6 files changed, 1801 insertions(+), 75 deletions(-) create mode 100644 libraries/o-error/doc/demo.js create mode 100755 libraries/o-error/doc/update-readme.js diff --git a/libraries/o-error/README.md b/libraries/o-error/README.md index 50a6600214..e236e0b4d1 100644 --- a/libraries/o-error/README.md +++ b/libraries/o-error/README.md @@ -1,96 +1,424 @@ # @overleaf/o-error +[![npm version](https://badge.fury.io/js/%40overleaf%2Fo-error.svg)](https://badge.fury.io/js/%40overleaf%2Fo-error) [![CircleCI](https://circleci.com/gh/overleaf/o-error.svg?style=svg)](https://circleci.com/gh/overleaf/o-error) -Make custom error classes that: +Light-weight helpers for handling JavaScript Errors in node.js and the browser. -- pass `instanceof` checks, -- have stack traces, -- support custom messages and properties (`info`), and -- can wrap internal errors (causes) like [VError](https://github.com/joyent/node-verror). +- Get long stack traces across async functions and callbacks with `OError.tag`. +- Easily make custom `Error` subclasses, optionally with HTTP status codes. +- Wrap internal errors, preserving the original errors for logging as `causes`. +- Play nice with error logging services by keeping data in attached `info` objects instead of the error message. -ES6 classes make it easy to define custom errors by subclassing `Error`. Subclassing `OError` adds a few extra helpers. +## Table of Contents -## Usage + -### Throw an error directly +- [Long Stack Traces with `OError.tag`](#long-stack-traces-with-oerrortag) + * [The Problem](#the-problem) + * [The Solution](#the-solution) + * [Adding More Info](#adding-more-info) + * [`async`/`await`](#asyncawait) + * [Better Async Stack Traces in Node 12+](#better-async-stack-traces-in-node-12) +- [Create Custom Error Classes](#create-custom-error-classes) + * [Attaching Extra Info](#attaching-extra-info) + * [Wrapping an Internal Error](#wrapping-an-internal-error) +- [OError API Reference](#oerror-api-reference) + * [new OError(message, [info], [cause])](#new-oerrormessage-info-cause) + * [oError.withInfo(info) ⇒ this](#oerrorwithinfoinfo--this) + * [oError.withCause(cause) ⇒ this](#oerrorwithcausecause--this) + * [OError.tag(error, [message], [info]) ⇒ Error](#oerrortagerror-message-info--error) + * [OError.getFullInfo(error) ⇒ Object](#oerrorgetfullinfoerror--object) + * [OError.getFullStack(error) ⇒ string](#oerrorgetfullstackerror--string) +- [References](#references) + + + +## Long Stack Traces with `OError.tag` + +### The Problem + +While JavaScript errors have stack traces, they only go back to the start of the latest tick, so they are often not very useful. For example: ```js -const OError = require('@overleaf/o-error') +const demoDatabase = { + findUser(id, callback) { + process.nextTick(() => { + // return result asynchronously + if (id === 42) { + callback(null, { name: 'Bob' }) + } else { + callback(new Error('not found')) + } + }) + }, +} -function doSomethingBad() { - throw new OError({ - message: 'did something bad', - info: { thing: 'foo' }, +function sayHi1(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(err) + callback(null, 'Hi ' + user.name) }) } -doSomethingBad() -// => -// { OError: did something bad -// at doSomethingBad (repl:2:9) <-- stack trace -// name: 'OError', <-- default name -// info: { thing: 'foo' } } <-- attached info -``` -### Custom error class - -```js -class FooError extends OError { - constructor(options) { - super({ message: 'failed to foo', ...options }) +sayHi1(43, (err, result) => { + if (err) { + console.error(err) + } else { + console.log(result) } -} - -function doFoo() { - throw new FooError({ info: { foo: 'bar' } }) -} -doFoo() -// => -// { FooError: failed to foo -// at doFoo (repl:2:9) <-- stack trace -// name: 'FooError', <-- correct name -// info: { foo: 'bar' } } <-- attached info +}) ``` -### Wrapping an inner error (cause) +The resulting error's stack trace doesn't make any mention of our `sayHi1` function; it starts at the `nextTick` built-in: + +``` +Error: not found + at process.nextTick (repl:8:18) + at process._tickCallback (internal/process/next_tick.js:61:11) +``` + +In practice, it's often even worse, like + +``` +DBError: socket connection refused + at someObscureLibraryFunction (...) + at ... +``` + +### The Solution + +Before passing the error to a callback, call the `OError.tag` function to capture a stack trace at the call site: ```js -function doFoo2() { +const OError = require('.') + +function sayHi2(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(OError.tag(err)) + callback(null, 'Hi ' + user.name) + }) +} + +sayHi2(43, (err, result) => { + if (err) { + console.error(OError.getFullStack(OError.tag(err))) + } else { + console.log(result) + } +}) +``` + +And use `OError.getFullStack` to reconstruct the full stack, including the tagged errors: + +``` +Error: not found + at process.nextTick (repl:8:18) + at process._tickCallback (internal/process/next_tick.js:61:11) +TaggedError + at demoDatabase.findUser (repl:3:37) + at process.nextTick (repl:8:9) + at process._tickCallback (internal/process/next_tick.js:61:11) +TaggedError + at sayHi2 (repl:3:46) + at demoDatabase.findUser (repl:3:21) + at process.nextTick (repl:8:9) + at process._tickCallback (internal/process/next_tick.js:61:11) +``` + +The full stack contains the original error's stack and also the `TaggedError` stacks. There's some redundancy, but it's better to have too much information than too little. + +### Adding More Info + +You can add more information at each `tag` call site: a message and an `info` object with custom properties. + +```js +function sayHi3(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(OError.tag(err, 'failed to find user', { userId })) + callback(null, 'Hi ' + user.name) + }) +} + +sayHi3(43, (err, result) => { + if (err) { + OError.tag(err, 'failed to say hi') + console.error(OError.getFullStack(err)) + console.error(OError.getFullInfo(err)) + } else { + console.log(result) + } +}) +``` + +The `OError.getFullInfo` property merges all of the `info`s from the tags together into one object. This logs a full stack trace with `failed to ...` annotations and an `info` object that contains the `userId` that it failed to find: + +``` +Error: not found + at process.nextTick (repl:8:18) + at process._tickCallback (internal/process/next_tick.js:61:11) +TaggedError: failed to find user + at demoDatabase.findUser (repl:3:37) + at process.nextTick (repl:8:9) + at process._tickCallback (internal/process/next_tick.js:61:11) +TaggedError: failed to say hi + at sayHi3 (repl:3:12) + at demoDatabase.findUser (repl:3:21) + at process.nextTick (repl:8:9) + at process._tickCallback (internal/process/next_tick.js:61:11) + +{ userId: 43 } +``` + +Logging this information (or reporting it to an error monitoring service) hopefully gives you a good start to figuring out what went wrong. + +### `async`/`await` + +The `OError.tag` approach works with both async/await and callback-oriented code. When using async/await, the pattern is to catch an error, tag it and rethrow: + +```js +const promisify = require('util').promisify +demoDatabase.findUserAsync = promisify(demoDatabase.findUser) + +async function sayHi4(userId) { try { - throw new Error('bad') - } catch (err) { - throw new FooError({ info: { foo: 'bar' } }).withCause(err) + const user = await demoDatabase.findUserAsync(userId) + return `Hi ${user.name}` + } catch (error) { + throw OError.tag(error, 'failed to find user', { userId }) } } -doFoo2() -// => -// { FooError: failed to foo: bad <-- combined message -// at doFoo2 (repl:5:11) <-- stack trace -// name: 'FooError', <-- correct name -// info: { foo: 'bar' }, <-- attached info -// cause: <-- the cause (inner error) -// Error: bad <-- inner error message -// at doFoo2 (repl:3:11) <-- inner error stack trace -// at repl:1:1 -// ... +async function main() { + try { + await sayHi4(43) + } catch (error) { + OError.tag(error, 'failed to say hi') + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) + } +} +main() +``` + +The resulting full stack trace points to `sayHi4` in `main`, as expected: + +``` +Error: not found + at process.nextTick (repl:8:18) + at process._tickCallback (internal/process/next_tick.js:61:11) +TaggedError: failed to find user + at sayHi4 (repl:6:18) + at process._tickCallback (internal/process/next_tick.js:68:7) +TaggedError: failed to say hi + at main (repl:5:12) + at process._tickCallback (internal/process/next_tick.js:68:7) + +{ userId: 43 } +``` + +### Better Async Stack Traces in Node 12+ + +The above output is from node 10. Node 12 has improved stack traces for async code that uses native promises. However, until your whole stack, including all libraries, is using async/await and native promises, you're still likely to get unhelpful stack traces. So, the tagging approach still adds value, even in node 12. (And the `info` from tagging can add value even to a good stack trace, because it can contain clues about the input the caused the error.) + +## Create Custom Error Classes + +Broadly speaking, there are two kinds of errors: those we try to recover from, and those for which we give up (i.e. a 5xx response in a web application). For the latter kind, we usually just want to log a message and stack trace useful for debugging, which `OError.tag` helps with. + +To recover from an error, we usually need to know what kind of error it was and perhaps to check some of its properties. Defining a custom Error subclass is a good way to do this. Callers can check the type of the error either with `instanceof` or using a custom property, such as `code`. + +With ES6 classes, creating a custom error subclass is mostly as simple as `extends Error`. One extra line is required to set the error's `name` appropriately, and inheriting from `OError` handles this implementation detail. Here's an example: + +```js +class UserNotFoundError extends OError { + constructor() { + super('user not found') + } +} try { - doFoo2() -} catch (err) { - console.log(OError.getFullStack(err)) + throw new UserNotFoundError() +} catch (error) { + console.error(`instanceof Error: ${error instanceof Error}`) + console.error( + `instanceof UserNotFoundError: ${error instanceof UserNotFoundError}` + ) + console.error(error.stack) } -// => -// FooError: failed to foo: bad -// at doFoo2 (repl:5:11) -// at repl:2:3 -// ... -// caused by: Error: bad -// at doFoo2 (repl:3:11) -// at repl:2:3 -// ... ``` +``` +instanceof Error: true +instanceof UserNotFoundError: true +UserNotFoundError: user not found + at repl:2:9 + ... +``` + +### Attaching Extra Info + +Whether for helping with error recovery or just for debugging, it is often helpful to include some of the state that caused the error in the error. One way to do this is to put it in the message, but this has a few problems: + +- Even if the error is later handled and recovered from, we spend time stringifying the state to add it to the error message. +- Error monitoring systems often look at the message when trying to group similar errors together, and they can get confused by the ever-changing messages. +- When using structured logging, you lose the ability to easily query or filter the logs based on the state; instead clever regexes may be required to get it out of the messages. + +Instead, `OError`s (and subclasses) support an `info` object that can contain arbitrary data. Using `info`, we might write the above example as: + +```js +class UserNotFoundError extends OError { + constructor(userId) { + super('user not found', { userId }) + } +} + +try { + throw new UserNotFoundError(123) +} catch (error) { + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) +} +``` + +``` +UserNotFoundError: user not found + at repl:2:9 + ... +{ userId: 123 } +``` + +The `OError.getFullInfo` helper merges the `info` on custom errors and any info added with `OError.tag` on its way up the stack. It is intended for use when logging errors. If trying to recover from an error that is known to be a `UserNotFoundError`, it is usually better to interrogate `error.info.userId` directly. + +### Wrapping an Internal Error + +Detecting a condition like 'user not found' in the example above often starts with an internal database error. It is possible to just let the internal database error propagate all the way up through the stack, but this makes the code more coupled to the internals of the database (or database driver). It is often cleaner to handle and wrap the internal error in one that is under your control. Tying up the examples above: + +```js +async function sayHi5(userId) { + try { + const user = await demoDatabase.findUserAsync(userId) + return `Hi ${user.name}` + } catch (error) { + if (error.message === 'not found') { + throw new UserNotFoundError(userId).withCause(error) + } + } +} + +async function main() { + try { + await sayHi5(43) + } catch (error) { + OError.tag(error, 'failed to say hi') + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) + } +} +main() +``` + +The output includes the wrapping error, the tag and the cause, together with the info: + +``` +UserNotFoundError: user not found + at sayHi5 (repl:7:13) + at process._tickCallback (internal/process/next_tick.js:68:7) +TaggedError: failed to say hi + at main (repl:5:12) + at process._tickCallback (internal/process/next_tick.js:68:7) +caused by: + Error: not found + at process.nextTick (repl:8:18) + at process._tickCallback (internal/process/next_tick.js:61:11) +{ userId: 43 } +``` + +## OError API Reference + + +* [OError](#OError) + * [new OError(message, [info], [cause])](#new_OError_new) + * _instance_ + * [.withInfo(info)](#OError+withInfo) ⇒ this + * [.withCause(cause)](#OError+withCause) ⇒ this + * _static_ + * [.tag(error, [message], [info])](#OError.tag) ⇒ Error + * [.getFullInfo(error)](#OError.getFullInfo) ⇒ Object + * [.getFullStack(error)](#OError.getFullStack) ⇒ string + + + +### new OError(message, [info], [cause]) + +| Param | Type | Description | +| --- | --- | --- | +| message | string | as for built-in Error | +| [info] | Object | extra data to attach to the error | +| [cause] | Error | the internal error that caused this error | + + + +### oError.withInfo(info) ⇒ this +Set the extra info object for this error. + +**Kind**: instance method of [OError](#OError) + +| Param | Type | Description | +| --- | --- | --- | +| info | Object \| null \| undefined | extra data to attach to the error | + + + +### oError.withCause(cause) ⇒ this +Wrap the given error, which caused this error. + +**Kind**: instance method of [OError](#OError) + +| Param | Type | Description | +| --- | --- | --- | +| cause | Error | the internal error that caused this error | + + + +### OError.tag(error, [message], [info]) ⇒ Error +Tag debugging information onto any error (whether an OError or not) and +return it. + +**Kind**: static method of [OError](#OError) +**Returns**: Error - the modified `error` argument + +| Param | Type | Description | +| --- | --- | --- | +| error | Error | the error to tag | +| [message] | string | message with which to tag `error` | +| [info] | Object | extra data with wich to tag `error` | + + + +### OError.getFullInfo(error) ⇒ Object +The merged info from any `tag`s on the given error. + +If an info property is repeated, the last one wins. + +**Kind**: static method of [OError](#OError) + +| Param | Type | Description | +| --- | --- | --- | +| error | Error \| null \| undefined | any errror (may or may not be an `OError`) | + + + +### OError.getFullStack(error) ⇒ string +Return the `stack` property from `error`, including the `stack`s for any +tagged errors added with `OError.tag` and for any `cause`s. + +**Kind**: static method of [OError](#OError) + +| Param | Type | Description | +| --- | --- | --- | +| error | Error \| null \| undefined | any error (may or may not be an `OError`) | + + ## References - [MDN: Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) diff --git a/libraries/o-error/doc/demo.js b/libraries/o-error/doc/demo.js new file mode 100644 index 0000000000..ce906df5a8 --- /dev/null +++ b/libraries/o-error/doc/demo.js @@ -0,0 +1,141 @@ +// This is the code from the README. + +const demoDatabase = { + findUser(id, callback) { + process.nextTick(() => { + // return result asynchronously + if (id === 42) { + callback(null, { name: 'Bob' }) + } else { + callback(new Error('not found')) + } + }) + }, +} + +function sayHi1(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(err) + callback(null, 'Hi ' + user.name) + }) +} + +sayHi1(42, (err, result) => { + if (err) { + console.error(err) + } else { + console.log(result) + } +}) + +sayHi1(43, (err, result) => { + if (err) { + console.error(err) + } else { + console.log(result) + } +}) + +const OError = require('.') + +function sayHi2(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(OError.tag(err)) + callback(null, 'Hi ' + user.name) + }) +} + +sayHi2(43, (err, result) => { + if (err) { + console.error(OError.getFullStack(OError.tag(err))) + } else { + console.log(result) + } +}) + +function sayHi3(userId, callback) { + demoDatabase.findUser(userId, (err, user) => { + if (err) return callback(OError.tag(err, 'failed to find user', { userId })) + callback(null, 'Hi ' + user.name) + }) +} + +sayHi3(43, (err, result) => { + if (err) { + OError.tag(err, 'failed to say hi') + console.error(OError.getFullStack(err)) + console.error(OError.getFullInfo(err)) + } else { + console.log(result) + } +}) + +const promisify = require('util').promisify +demoDatabase.findUserAsync = promisify(demoDatabase.findUser) + +async function sayHi4NoHandling(userId) { + const user = await demoDatabase.findUserAsync(userId) + return `Hi ${user.name}` +} + +async function sayHi4(userId) { + try { + const user = await demoDatabase.findUserAsync(userId) + return `Hi ${user.name}` + } catch (error) { + throw OError.tag(error, 'failed to find user', { userId }) + } +} + +async function main() { + try { + await sayHi4NoHandling(43) + } catch (error) { + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) + } + + try { + await sayHi4(43) + } catch (error) { + OError.tag(error, 'failed to say hi') + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) + } +} +main() + +class UserNotFoundError extends OError { + constructor(userId) { + super('user not found', { userId }) + } +} + +try { + throw new UserNotFoundError(123) +} catch (error) { + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) +} + +async function sayHi5(userId) { + try { + const user = await demoDatabase.findUserAsync(userId) + return `Hi ${user.name}` + } catch (error) { + if (error.message === 'not found') { + throw new UserNotFoundError(userId).withCause(error) + } + } +} + +async function main2() { + try { + await sayHi5(43) + } catch (error) { + OError.tag(error, 'failed to say hi') + console.error(OError.getFullStack(error)) + console.error(OError.getFullInfo(error)) + } +} +main2() diff --git a/libraries/o-error/doc/update-readme.js b/libraries/o-error/doc/update-readme.js new file mode 100755 index 0000000000..61a7d90f3c --- /dev/null +++ b/libraries/o-error/doc/update-readme.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const fs = require('fs') +const jsdoc2md = require('jsdoc-to-markdown') +const toc = require('markdown-toc') + +const README = 'README.md' +const HEADER = '## OError API Reference' +const FOOTER = '' + +async function main() { + const apiDocs = await jsdoc2md.render({ files: 'index.js' }) + const apiDocLines = apiDocs.trim().split(/\r?\n/g) + + // The first few lines don't make much sense when included in the README. + const apiDocStart = apiDocLines.indexOf('* [OError](#OError)') + if (apiDocStart === -1) { + console.error('API docs not in expected format for insertion.') + process.exit(1) + } + apiDocLines.splice(1, apiDocStart - 1) + apiDocLines.unshift(HEADER, '') + + const readme = await fs.promises.readFile(README, { encoding: 'utf8' }) + const readmeLines = readme.split(/\r?\n/g) + + const apiStart = readmeLines.indexOf(HEADER) + const apiEnd = readmeLines.indexOf(FOOTER) + + if (apiStart === -1 || apiEnd === -1) { + console.error('Could not find the API Reference section.') + process.exit(1) + } + + Array.prototype.splice.apply( + readmeLines, + [apiStart, apiEnd - apiStart].concat(apiDocLines) + ) + + const readmeWithApi = readmeLines.join('\n') + + let readmeWithApiAndToc = toc.insert(readmeWithApi) + + // Unfortunately, the ⇒ breaks the generated TOC links. + readmeWithApiAndToc = readmeWithApiAndToc.replace(/-%E2%87%92-/g, '--') + + await fs.promises.writeFile(README, readmeWithApiAndToc) +} +main() diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index 3aea8c3635..c7a036bd2f 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -1,12 +1,12 @@ /** * Light-weight helpers for handling JavaScript Errors in node.js and the - * browser. {@see README.md} + * browser. */ class OError extends Error { /** * @param {string} message as for built-in Error * @param {Object} [info] extra data to attach to the error - * @param {Error} [cause] + * @param {Error} [cause] the internal error that caused this error */ constructor(message, info, cause) { super(message) @@ -14,14 +14,14 @@ class OError extends Error { if (info) this.info = info if (cause) this.cause = cause - /** @type {Array} */ + /** @private @type {Array} */ this._oErrorTags // eslint-disable-line } /** * Set the extra info object for this error. * - * @param {Object | null | undefined} info + * @param {Object | null | undefined} info extra data to attach to the error * @return {this} */ withInfo(info) { @@ -32,7 +32,7 @@ class OError extends Error { /** * Wrap the given error, which caused this error. * - * @param {Error} cause + * @param {Error} cause the internal error that caused this error * @return {this} */ withCause(cause) { @@ -44,9 +44,9 @@ class OError extends Error { * Tag debugging information onto any error (whether an OError or not) and * return it. * - * @param {Error} error - * @param {string} [message] - * @param {Object} [info] + * @param {Error} error the error to tag + * @param {string} [message] message with which to tag `error` + * @param {Object} [info] extra data with wich to tag `error` * @return {Error} the modified `error` argument */ static tag(error, message, info) { @@ -69,7 +69,7 @@ class OError extends Error { * * If an info property is repeated, the last one wins. * - * @param {Error} error + * @param {Error | null | undefined} error any errror (may or may not be an `OError`) * @return {Object} */ static getFullInfo(error) { @@ -94,7 +94,7 @@ class OError extends Error { * Return the `stack` property from `error`, including the `stack`s for any * tagged errors added with `OError.tag` and for any `cause`s. * - * @param {Error | null | undefined} error + * @param {Error | null | undefined} error any error (may or may not be an `OError`) * @return {string} */ static getFullStack(error) { diff --git a/libraries/o-error/package-lock.json b/libraries/o-error/package-lock.json index 01f5b60431..0526fee811 100644 --- a/libraries/o-error/package-lock.json +++ b/libraries/o-error/package-lock.json @@ -30,6 +30,12 @@ "js-tokens": "^4.0.0" } }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -72,6 +78,23 @@ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, + "ansi-escape-sequences": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", + "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -89,6 +112,15 @@ } } }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -104,6 +136,12 @@ "color-convert": "^1.9.0" } }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -123,6 +161,12 @@ "sprintf-js": "~1.0.2" } }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true + }, "array-includes": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", @@ -150,6 +194,15 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "autolinker": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz", + "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=", + "dev": true, + "requires": { + "gulp-header": "^1.7.1" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -162,6 +215,12 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -187,6 +246,23 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-1.0.0.tgz", + "integrity": "sha512-ZqrZp9Hi5Uq7vfSGmNP2bUT/9DzZC2Y/GXjHB8rUJN1a+KLmbV05+vxHipNsg8+CSVgjcVVzLV8VZms6w8ZeRw==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "fs-then-native": "^2.0.0", + "mkdirp2": "^1.0.4" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -199,6 +275,15 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "chai": { "version": "3.4.1", "resolved": "http://registry.npmjs.org/chai/-/chai-3.4.1.tgz", @@ -339,6 +424,22 @@ } } }, + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "dev": true + }, + "collect-all": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.3.tgz", + "integrity": "sha512-0y0rBgoX8IzIjBAUnO73SEtSb4Mhk3IoceWJq5zZSxb9mWORhWH8xLYo4EDSOE1jRBk1LhmfjqWFFt10h/+MEA==", + "dev": true, + "requires": { + "stream-connect": "^1.0.2", + "stream-via": "^1.0.4" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -354,18 +455,148 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "requires": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + } + } + }, + "command-line-tool": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz", + "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==", + "dev": true, + "requires": { + "ansi-escape-sequences": "^4.0.0", + "array-back": "^2.0.0", + "command-line-args": "^5.0.0", + "command-line-usage": "^4.1.0", + "typical": "^2.6.1" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, + "command-line-usage": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz", + "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==", + "dev": true, + "requires": { + "ansi-escape-sequences": "^4.0.0", + "array-back": "^2.0.0", + "table-layout": "^0.4.2", + "typical": "^2.6.1" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "common-sequence": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.0.tgz", + "integrity": "sha512-f0QqPLpRTgMQn/pQIynf+SdE73Lw5Q1jn4hjirHLgH/NJ71TiHjXusV16BmOyuK5rRQ1W2f++II+TFZbQOh4hA==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "config-master": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", + "integrity": "sha1-ZnZjWQUFooO/JqSE1oSJ10xUhdo=", + "dev": true, + "requires": { + "walk-back": "^2.0.1" + }, + "dependencies": { + "walk-back": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-2.0.1.tgz", + "integrity": "sha1-VU4qnYdPrEeoywBr9EwvDEmYoKQ=", + "dev": true + } + } + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -394,6 +625,12 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -409,12 +646,46 @@ "object-keys": "^1.0.12" } }, + "diacritics-map": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", + "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=", + "dev": true + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dmd": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-4.0.6.tgz", + "integrity": "sha512-7ZYAnFQ6jGm4SICArwqNPylJ83PaOdPTAkds3Z/s1ueFqSc5ilJ2F0b7uP+35W1PUbemH++gn5/VlC3KwEgiHQ==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "cache-point": "^1.0.0", + "common-sequence": "^2.0.0", + "file-set": "^3.0.0", + "handlebars": "^4.5.3", + "marked": "^0.7.0", + "object-get": "^2.1.0", + "reduce-flatten": "^3.0.0", + "reduce-unique": "^2.0.1", + "reduce-without": "^1.0.1", + "test-value": "^3.0.0", + "walk-back": "^4.0.0" + }, + "dependencies": { + "reduce-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.0.tgz", + "integrity": "sha512-eczl8wAYBxJ6Egl6I1ECIF+8z6sHu+KE7BzaEDZTpPXKXfy9SUDQlVYwkRcNTjJLC3Iakxbhss50KuT/R6SYfg==", + "dev": true + } + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -430,6 +701,12 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -872,6 +1149,48 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -925,6 +1244,32 @@ "flat-cache": "^2.0.1" } }, + "file-set": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/file-set/-/file-set-3.0.0.tgz", + "integrity": "sha512-B/SdeSIeRv7VlOgIjtH3dkxMI+tEy5m+OeCXfAUsirBoVoY+bGtsmvmmTFPm/G23TBY4RiTtjpcgePCfwXRjqA==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "glob": "^7.1.5" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -934,6 +1279,23 @@ "to-regex-range": "^5.0.1" } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -969,6 +1331,18 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fs-then-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", + "integrity": "sha1-GaEk2U2QwiyOBF8ujdbr6jbUjGc=", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1044,12 +1418,49 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "gray-matter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", + "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=", + "dev": true, + "requires": { + "ansi-red": "^0.1.1", + "coffee-script": "^1.12.4", + "extend-shallow": "^2.0.1", + "js-yaml": "^3.8.1", + "toml": "^2.3.2" + } + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "gulp-header": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", + "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "dev": true, + "requires": { + "concat-with-sourcemaps": "*", + "lodash.template": "^4.4.0", + "through2": "^2.0.0" + } + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "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" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1274,6 +1685,12 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1301,6 +1718,23 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -1343,6 +1777,15 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1359,6 +1802,138 @@ "esprima": "^4.0.0" } }, + "js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsdoc": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.4.tgz", + "integrity": "sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + } + } + }, + "jsdoc-api": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-5.0.4.tgz", + "integrity": "sha512-1KMwLnfo0FyhF06TQKzqIm8BiY1yoMIGICxRdJHUjzskaHMzHMmpLlmNFgzoa4pAC8t1CDPK5jWuQTvv1pBsEQ==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "cache-point": "^1.0.0", + "collect-all": "^1.0.3", + "file-set": "^2.0.1", + "fs-then-native": "^2.0.0", + "jsdoc": "^3.6.3", + "object-to-spawn-args": "^1.1.1", + "temp-path": "^1.0.0", + "walk-back": "^3.0.1" + }, + "dependencies": { + "file-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/file-set/-/file-set-2.0.1.tgz", + "integrity": "sha512-XgOUUpgR6FbbfYcniLw0qm1Am7PnNYIAkd+eXxRt42LiYhjaso0WiuQ+VmrNdtwotyM+cLCfZ56AZrySP3QnKA==", + "dev": true, + "requires": { + "array-back": "^2.0.0", + "glob": "^7.1.3" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, + "walk-back": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.1.tgz", + "integrity": "sha512-umiNB2qLO731Sxbp6cfZ9pwURJzTnftxE4Gc7hq8n/ehkuXC//s9F65IEIJA2ZytQZ1ZOsm/Fju4IWx0bivkUQ==", + "dev": true + } + } + }, + "jsdoc-parse": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-4.0.1.tgz", + "integrity": "sha512-qIObw8yqYZjrP2qxWROB5eLQFLTUX2jRGLhW9hjo2CC2fQVlskidCIzjCoctwsDvauBp2a/lR31jkSleczSo8Q==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "reduce-extract": "^1.0.0", + "sort-array": "^2.0.0", + "test-value": "^3.0.0" + } + }, + "jsdoc-to-markdown": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-5.0.3.tgz", + "integrity": "sha512-tQv5tBV0fTYidRQtE60lJKxE98mmuLcYuITFDKQiDPE9hGccpeEGUNFcVkInq1vigyuPnZmt79bQ8wv2GKjY0Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "command-line-tool": "^0.8.0", + "config-master": "^3.1.0", + "dmd": "^4.0.5", + "jsdoc-api": "^5.0.4", + "jsdoc-parse": "^4.0.1", + "walk-back": "^4.0.0" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -1371,6 +1946,41 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "dev": true, + "requires": { + "set-getter": "^0.1.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1381,6 +1991,38 @@ "type-check": "~0.3.2" } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "list-item": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", + "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "extend-shallow": "^2.0.1", + "is-number": "^2.1.0", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -1409,6 +2051,55 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=", + "dev": true + }, + "lodash.padend": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=", + "dev": true + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -1418,6 +2109,69 @@ "chalk": "^2.4.2" } }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.2.7.tgz", + "integrity": "sha512-REFmIaSS6szaD1bye80DMbp7ePwsPNvLTR5HunsUcZ0SG0rWJQ+Pz24R4UlTKtjKBPhxo0v0tOBDYjZQQknW8Q==", + "dev": true + }, + "markdown-link": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", + "integrity": "sha1-MsXGUZmmRXMWMi0eQinRNAfIx88=", + "dev": true + }, + "markdown-toc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz", + "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==", + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "diacritics-map": "^0.1.0", + "gray-matter": "^2.1.0", + "lazy-cache": "^2.0.2", + "list-item": "^1.1.1", + "markdown-link": "^0.1.1", + "minimist": "^1.2.0", + "mixin-deep": "^1.1.3", + "object.pick": "^1.2.0", + "remarkable": "^1.7.1", + "repeat-string": "^1.6.1", + "strip-color": "^0.1.0" + } + }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1439,6 +2193,27 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -1448,6 +2223,12 @@ "minimist": "^1.2.5" } }, + "mkdirp2": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.4.tgz", + "integrity": "sha512-Q2PKB4ZR4UPtjLl76JfzlgSCUZhSV1AXQgAZa1qt5RiaALFjP/CDrGvFBrOz7Ck6McPcwMAxTsJvWOUjOU8XMw==", + "dev": true + }, "mocha": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", @@ -1509,6 +2290,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -1543,6 +2330,12 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "object-get": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz", + "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==", + "dev": true + }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -1555,6 +2348,12 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, + "object-to-spawn-args": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-1.1.1.tgz", + "integrity": "sha1-d9qIJ/Bz0BHJ4bFz+JV4FHAkZ4U=", + "dev": true + }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", @@ -1577,6 +2376,23 @@ "es-abstract": "^1.17.0-next.1" } }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -1789,6 +2605,12 @@ "fast-diff": "^1.1.2" } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -1807,6 +2629,31 @@ "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", "dev": true }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -1873,6 +2720,21 @@ } } }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "readdirp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", @@ -1882,12 +2744,106 @@ "picomatch": "^2.0.4" } }, + "reduce-extract": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", + "integrity": "sha1-Z/I4W+2mUGG19fQxJmLosIDKFSU=", + "dev": true, + "requires": { + "test-value": "^1.0.1" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + }, + "test-value": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", + "integrity": "sha1-oJE29y7AQ9J8iTcHwrFZv6196T8=", + "dev": true, + "requires": { + "array-back": "^1.0.2", + "typical": "^2.4.2" + } + } + } + }, + "reduce-flatten": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", + "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=", + "dev": true + }, + "reduce-unique": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz", + "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==", + "dev": true + }, + "reduce-without": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz", + "integrity": "sha1-aK0OrRGFXJo31OglbBW7+Hly/Iw=", + "dev": true, + "requires": { + "test-value": "^2.0.0" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + }, + "test-value": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", + "integrity": "sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=", + "dev": true, + "requires": { + "array-back": "^1.0.3", + "typical": "^2.6.0" + } + } + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "remarkable": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", + "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", + "dev": true, + "requires": { + "argparse": "^1.0.10", + "autolinker": "~0.28.0" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1900,6 +2856,15 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "resolve": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.0.tgz", @@ -1952,6 +2917,12 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1970,6 +2941,15 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "dev": true, + "requires": { + "to-object-path": "^0.3.0" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2002,6 +2982,34 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "sort-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-2.0.0.tgz", + "integrity": "sha1-OKnG2if9fRR7QuYFVPKBGHtN9HI=", + "dev": true, + "requires": { + "array-back": "^1.0.4", + "object-get": "^2.1.0", + "typical": "^2.6.0" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + } + } + }, + "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 + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -2040,6 +3048,32 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "stream-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", + "integrity": "sha1-GLyB8u2zW4tdmoAJIAqYUxRCipc=", + "dev": true, + "requires": { + "array-back": "^1.0.2" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + } + } + }, + "stream-via": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", + "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2092,6 +3126,15 @@ "es-abstract": "^1.17.5" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -2107,6 +3150,12 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, + "strip-color": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", + "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -2162,6 +3211,63 @@ } } }, + "table-layout": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", + "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", + "dev": true, + "requires": { + "array-back": "^2.0.0", + "deep-extend": "~0.6.0", + "lodash.padend": "^4.6.1", + "typical": "^2.6.1", + "wordwrapjs": "^3.0.0" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "temp-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", + "integrity": "sha1-JLFUOXOrRCiW2a02fdnL2/r+kYs=", + "dev": true + }, + "test-value": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", + "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", + "dev": true, + "requires": { + "array-back": "^2.0.0", + "typical": "^2.6.1" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2174,6 +3280,16 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -2183,6 +3299,15 @@ "os-tmpdir": "~1.0.2" } }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2192,6 +3317,12 @@ "is-number": "^7.0.0" } }, + "toml": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", + "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==", + "dev": true + }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", @@ -2213,12 +3344,46 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "typescript": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uglify-js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3" + } + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -2228,6 +3393,12 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", @@ -2244,6 +3415,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "walk-back": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-4.0.0.tgz", + "integrity": "sha512-kudCA8PXVQfrqv2mFTG72vDBRi8BKWxGgFLwPpzHcpZnSwZk93WMwUDVcLHWNsnm+Y0AC4Vb6MUNRgaHfyV2DQ==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -2274,6 +3451,22 @@ "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 + }, + "wordwrapjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", + "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", + "dev": true, + "requires": { + "reduce-flatten": "^1.0.1", + "typical": "^2.6.1" + } + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -2328,6 +3521,18 @@ "mkdirp": "^0.5.1" } }, + "xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/libraries/o-error/package.json b/libraries/o-error/package.json index 90aa4e170d..428e6e8731 100644 --- a/libraries/o-error/package.json +++ b/libraries/o-error/package.json @@ -5,6 +5,7 @@ "main": "index.js", "scripts": { "lint": "eslint .", + "update-readme": "doc/update-readme.js", "test": "mocha", "typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js" }, @@ -25,6 +26,8 @@ "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", + "jsdoc-to-markdown": "^5.0.3", + "markdown-toc": "^1.2.0", "mocha": "^7.1.1", "prettier": "^2.0.2", "typescript": "^3.8.3" From 6078aa963dff8e2cae25dd2d2be033a4788e9cc0 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 29 Apr 2020 20:58:46 +0100 Subject: [PATCH 05/13] Add typecheck to CI --- libraries/o-error/.circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/o-error/.circleci/config.yml b/libraries/o-error/.circleci/config.yml index 2e644c884e..1dac5ec3bf 100644 --- a/libraries/o-error/.circleci/config.yml +++ b/libraries/o-error/.circleci/config.yml @@ -11,6 +11,7 @@ jobs: steps: - run: npm install - run: npm run lint + - run: npm run typecheck - run: npm test workflows: build-and-test: From fc197630beb03ecca7aaef2072b9f3ddb20212fe Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Tue, 5 May 2020 09:02:11 +0100 Subject: [PATCH 06/13] Avoid capturing stack trace twice Co-authored-by: Eric Mc Sween --- libraries/o-error/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index c7a036bd2f..3eadfc83de 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -54,10 +54,14 @@ class OError extends Error { if (!oError._oErrorTags) oError._oErrorTags = [] - const tag = new TaggedError(message, info) - - // Hide this function in the stack trace. - if (Error.captureStackTrace) Error.captureStackTrace(tag, OError.tag) + let tag + if (Error.captureStackTrace) { + // Hide this function in the stack trace. + tag = { name: 'TaggedError', message, info } + Error.captureStackTrace(tag, OError.tag) + } else { + tag = new TaggedError(message, info) + } oError._oErrorTags.push(tag) From c44054a31b870f82fd84f7fa14d7501eabd29459 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Tue, 5 May 2020 09:06:15 +0100 Subject: [PATCH 07/13] Add cast for the fake TaggedError --- libraries/o-error/doc/benchmark.js | 44 ++++++++++++++++++++++++++++++ libraries/o-error/index.js | 4 +-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 libraries/o-error/doc/benchmark.js diff --git a/libraries/o-error/doc/benchmark.js b/libraries/o-error/doc/benchmark.js new file mode 100644 index 0000000000..4e1bf2695c --- /dev/null +++ b/libraries/o-error/doc/benchmark.js @@ -0,0 +1,44 @@ +// +// A quick microbenchmark for OError.tag. +// +const OError = require('..') + +function benchmark(fn, repeats = 100000) { + const startTime = process.hrtime() + for (let i = 0; i < repeats; ++i) { + fn() + } + const elapsed = process.hrtime(startTime) + return elapsed[0] * 1e3 + elapsed[1] * 1e-6 +} + +function throwError() { + throw new Error('here is a test error') +} + +console.log( + 'no tagging: ', + benchmark(() => { + try { + throwError() + return 1 + } catch (error) { + return 0 + } + }), + 'ms' +) + +console.log( + 'tagging: ', + benchmark(() => { + try { + throwError() + return 1 + } catch (error) { + OError.tag(error, 'here is a test tag') + return 0 + } + }), + 'ms' +) diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index 3eadfc83de..fce23de2e0 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -56,8 +56,8 @@ class OError extends Error { let tag if (Error.captureStackTrace) { - // Hide this function in the stack trace. - tag = { name: 'TaggedError', message, info } + // Hide this function in the stack trace, and avoid capturing it twice. + tag = /** @type TaggedError */ ({ name: 'TaggedError', message, info }) Error.captureStackTrace(tag, OError.tag) } else { tag = new TaggedError(message, info) From 8af22ae8780078c5a5de88d1e26a1655cc7b0616 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 10:32:18 +0100 Subject: [PATCH 08/13] Improve assertion messages in test utility --- libraries/o-error/test/support/index.js | 51 ++++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/libraries/o-error/test/support/index.js b/libraries/o-error/test/support/index.js index afa90c003e..46dd49967b 100644 --- a/libraries/o-error/test/support/index.js +++ b/libraries/o-error/test/support/index.js @@ -3,36 +3,51 @@ const { expect } = require('chai') const OError = require('../..') exports.expectError = function OErrorExpectError(e, expected) { - // should set the name to the error's name - expect(e.name).to.equal(expected.name) + expect( + e.name, + "error should set the name property to the error's name" + ).to.equal(expected.name) - // should be an instance of the error type - expect(e instanceof expected.klass).to.be.true + expect( + e instanceof expected.klass, + 'error should be an instance of the error type' + ).to.be.true - // should be an instance of the built-in Error type - expect(e instanceof Error).to.be.true + expect( + e instanceof Error, + 'error should be an instance of the built-in Error type' + ).to.be.true - // should be recognised by util.isError - expect(require('util').types.isNativeError(e)).to.be.true + expect( + require('util').types.isNativeError(e), + 'error should be recognised by util.types.isNativeError' + ).to.be.true - // should have a stack trace - expect(e.stack).to.be.truthy + expect(e.stack, 'error should have a stack trace').to.be.truthy - // toString should return the default error message formatting - expect(e.toString()).to.equal(expected.message) + expect( + e.toString(), + 'toString should return the default error message formatting' + ).to.equal(expected.message) - // stack should start with the default error message formatting - expect(e.stack.split('\n')[0]).to.match(new RegExp(`^${expected.name}:`)) + expect( + e.stack.split('\n')[0], + 'stack should start with the default error message formatting' + ).to.match(new RegExp(`^${expected.name}:`)) - // first stack frame should be the function where the error was thrown - expect(e.stack.split('\n')[1]).to.match(expected.firstFrameRx) + expect( + e.stack.split('\n')[1], + 'first stack frame should be the function where the error was thrown' + ).to.match(expected.firstFrameRx) } exports.expectFullStackWithoutStackFramesToEqual = function (error, expected) { - // But the stack contains all of the errors and tags. const fullStack = OError.getFullStack(error) const fullStackWithoutFrames = fullStack .split('\n') .filter((line) => !/^\s+at\s/.test(line)) - expect(fullStackWithoutFrames).to.deep.equal(expected) + expect( + fullStackWithoutFrames, + 'full stack without frames should equal' + ).to.deep.equal(expected) } From 90494fd75f20688cd4610ab0c66c8cda73333975 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 10:36:00 +0100 Subject: [PATCH 09/13] Fix demo require path --- libraries/o-error/doc/demo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/o-error/doc/demo.js b/libraries/o-error/doc/demo.js index ce906df5a8..845a2bf671 100644 --- a/libraries/o-error/doc/demo.js +++ b/libraries/o-error/doc/demo.js @@ -1,5 +1,7 @@ // This is the code from the README. +const OError = require('..') + const demoDatabase = { findUser(id, callback) { process.nextTick(() => { @@ -36,8 +38,6 @@ sayHi1(43, (err, result) => { } }) -const OError = require('.') - function sayHi2(userId, callback) { demoDatabase.findUser(userId, (err, user) => { if (err) return callback(OError.tag(err)) From 4b0060f0b1e85e358e0cc5202aa447ad6e2d407b Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 10:45:08 +0100 Subject: [PATCH 10/13] Remove HTTPErrors from v3 --- libraries/o-error/README.md | 2 +- libraries/o-error/http.js | 89 ----------------------------- libraries/o-error/test/http.test.js | 32 ----------- 3 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 libraries/o-error/http.js delete mode 100644 libraries/o-error/test/http.test.js diff --git a/libraries/o-error/README.md b/libraries/o-error/README.md index e236e0b4d1..2a298fbc2e 100644 --- a/libraries/o-error/README.md +++ b/libraries/o-error/README.md @@ -6,7 +6,7 @@ Light-weight helpers for handling JavaScript Errors in node.js and the browser. - Get long stack traces across async functions and callbacks with `OError.tag`. -- Easily make custom `Error` subclasses, optionally with HTTP status codes. +- Easily make custom `Error` subclasses. - Wrap internal errors, preserving the original errors for logging as `causes`. - Play nice with error logging services by keeping data in attached `info` objects instead of the error message. diff --git a/libraries/o-error/http.js b/libraries/o-error/http.js deleted file mode 100644 index f5e03ec05f..0000000000 --- a/libraries/o-error/http.js +++ /dev/null @@ -1,89 +0,0 @@ -const OError = require('./index') - -class HttpError extends OError { - constructor({ message, info, ...options }) { - super(message, info) - this.statusCode = options.statusCode || 500 - } -} - -class InternalServerError extends HttpError { - constructor(options) { - super({ message: 'Internal Server Error', statusCode: 500, ...options }) - } -} - -class ServiceUnavailableError extends HttpError { - constructor(options) { - super({ message: 'Service Unavailable', statusCode: 503, ...options }) - } -} - -class BadRequestError extends HttpError { - constructor(options) { - super({ message: 'Bad Request', statusCode: 400, ...options }) - } -} - -class UnauthorizedError extends HttpError { - constructor(options) { - super({ message: 'Unauthorized', statusCode: 401, ...options }) - } -} - -class ForbiddenError extends HttpError { - constructor(options) { - super({ message: 'Forbidden', statusCode: 403, ...options }) - } -} - -class NotFoundError extends HttpError { - constructor(options) { - super({ message: 'Not Found', statusCode: 404, ...options }) - } -} - -class MethodNotAllowedError extends HttpError { - constructor(options) { - super({ message: 'Method Not Allowed', statusCode: 405, ...options }) - } -} - -class NotAcceptableError extends HttpError { - constructor(options) { - super({ message: 'Not Acceptable', statusCode: 406, ...options }) - } -} - -class ConflictError extends HttpError { - constructor(options) { - super({ message: 'Conflict', statusCode: 409, ...options }) - } -} - -class UnprocessableEntityError extends HttpError { - constructor(options) { - super({ message: 'Unprocessable Entity', statusCode: 422, ...options }) - } -} - -class TooManyRequestsError extends HttpError { - constructor(options) { - super({ message: 'Too Many Requests', statusCode: 429, ...options }) - } -} - -module.exports = { - HttpError, - InternalServerError, - ServiceUnavailableError, - BadRequestError, - UnauthorizedError, - ForbiddenError, - NotFoundError, - MethodNotAllowedError, - NotAcceptableError, - ConflictError, - UnprocessableEntityError, - TooManyRequestsError, -} diff --git a/libraries/o-error/test/http.test.js b/libraries/o-error/test/http.test.js deleted file mode 100644 index 94886ee854..0000000000 --- a/libraries/o-error/test/http.test.js +++ /dev/null @@ -1,32 +0,0 @@ -const { expect } = require('chai') - -const HttpErrors = require('../http') - -const { expectError } = require('./support') - -describe('OError/http', function () { - it('is a valid OError', function () { - function foo() { - throw new HttpErrors.ConflictError() - } - - try { - foo() - } catch (error) { - expectError(error, { - name: 'ConflictError', - klass: HttpErrors.ConflictError, - message: 'ConflictError: Conflict', - firstFrameRx: /foo/, - }) - } - }) - - it('has status code', function () { - try { - throw new HttpErrors.ConflictError() - } catch (e) { - expect(e.statusCode).to.equal(409) - } - }) -}) From 497f2a793634b4d6a0344f4e4a1ab5a9101c15d3 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 11:07:08 +0100 Subject: [PATCH 11/13] Make tag check stricter and use a map --- libraries/o-error/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/o-error/index.js b/libraries/o-error/index.js index fce23de2e0..1184fcfe35 100644 --- a/libraries/o-error/index.js +++ b/libraries/o-error/index.js @@ -108,10 +108,8 @@ class OError extends Error { let stack = oError.stack - if (oError._oErrorTags) { - for (const tag of oError._oErrorTags) { - stack += `\n${tag.stack}` - } + if (Array.isArray(oError._oErrorTags) && oError._oErrorTags.length) { + stack += `\n${oError._oErrorTags.map((tag) => tag.stack).join('\n')}` } const causeStack = oError.cause && OError.getFullStack(oError.cause) From fd6ea2bb70784534d9a54a4fc9b1796c2b4eef1f Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 11:24:14 +0100 Subject: [PATCH 12/13] Add typescript types --- libraries/o-error/index.d.ts | 58 ++++++++++++++++++++++++++++++++++ libraries/o-error/package.json | 6 +++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 libraries/o-error/index.d.ts diff --git a/libraries/o-error/index.d.ts b/libraries/o-error/index.d.ts new file mode 100644 index 0000000000..000448e49f --- /dev/null +++ b/libraries/o-error/index.d.ts @@ -0,0 +1,58 @@ +export = OError; +/** + * Light-weight helpers for handling JavaScript Errors in node.js and the + * browser. + */ +declare class OError extends Error { + /** + * Tag debugging information onto any error (whether an OError or not) and + * return it. + * + * @param {Error} error the error to tag + * @param {string} [message] message with which to tag `error` + * @param {Object} [info] extra data with wich to tag `error` + * @return {Error} the modified `error` argument + */ + static tag(error: Error, message?: string, info?: any): Error; + /** + * The merged info from any `tag`s on the given error. + * + * If an info property is repeated, the last one wins. + * + * @param {Error | null | undefined} error any errror (may or may not be an `OError`) + * @return {Object} + */ + static getFullInfo(error: Error): any; + /** + * Return the `stack` property from `error`, including the `stack`s for any + * tagged errors added with `OError.tag` and for any `cause`s. + * + * @param {Error | null | undefined} error any error (may or may not be an `OError`) + * @return {string} + */ + static getFullStack(error: Error): string; + /** + * @param {string} message as for built-in Error + * @param {Object} [info] extra data to attach to the error + * @param {Error} [cause] the internal error that caused this error + */ + constructor(message: string, info?: any, cause?: Error); + info: any; + cause: Error; + /** @private @type {Array} */ + private _oErrorTags; + /** + * Set the extra info object for this error. + * + * @param {Object | null | undefined} info extra data to attach to the error + * @return {this} + */ + withInfo(info: any): OError; + /** + * Wrap the given error, which caused this error. + * + * @param {Error} cause the internal error that caused this error + * @return {this} + */ + withCause(cause: Error): OError; +} diff --git a/libraries/o-error/package.json b/libraries/o-error/package.json index 428e6e8731..fbf4351849 100644 --- a/libraries/o-error/package.json +++ b/libraries/o-error/package.json @@ -3,11 +3,15 @@ "version": "2.1.0", "description": "Make custom error types that pass `instanceof` checks, have stack traces, support custom messages and properties, and can wrap causes (like VError).", "main": "index.js", + "types": "index.d.ts", "scripts": { "lint": "eslint .", "update-readme": "doc/update-readme.js", "test": "mocha", - "typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js" + "typecheck": "tsc --allowJs --checkJs --noEmit --moduleResolution node --target ES6 *.js test/**/*.js", + "declaration:build": "rm -f index.d.ts && tsc --allowJs --declaration --emitDeclarationOnly --moduleResolution node --target ES6 index.js", + "declaration:check": "git diff --exit-code -- index.d.ts", + "prepublishOnly": "npm run --silent declaration:build && npm run --silent declaration:check" }, "author": "Overleaf (https://www.overleaf.com)", "license": "MIT", From d2f47e69a864fce0971305f3924f22500fafe92f Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 15 May 2020 11:25:12 +0100 Subject: [PATCH 13/13] Bump version to 3.0.0 --- libraries/o-error/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/o-error/package.json b/libraries/o-error/package.json index fbf4351849..a7438a6684 100644 --- a/libraries/o-error/package.json +++ b/libraries/o-error/package.json @@ -1,6 +1,6 @@ { "name": "@overleaf/o-error", - "version": "2.1.0", + "version": "3.0.0", "description": "Make custom error types that pass `instanceof` checks, have stack traces, support custom messages and properties, and can wrap causes (like VError).", "main": "index.js", "types": "index.d.ts",