diff --git a/.eslintrc.json b/.eslintrc.json index 88c84b7..f1a2ea0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "eslint:recommended" + "eslint" ], "env": { "es6": true @@ -10,7 +10,7 @@ "SharedArrayBuffer": "readonly" }, "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 2019, "sourceType": "script" }, "ignorePatterns": [ @@ -18,38 +18,12 @@ "dist/" ], "rules": { - "indent": [ - "error", - 4 - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "no-irregular-whitespace": [ - "error", - { - "skipStrings": true, - "skipTemplates": true - } - ], - "comma-dangle": [ - "error", - { - "arrays": "always-multiline", - "objects": "always-multiline", - "imports": "always-multiline", - "exports": "always-multiline", - "functions": "never" - } - ] + "strict": ["error", "never"], + "no-console": ["error", {"allow": ["info", "warn", "error"]}], + "no-multi-str": "off", + "func-style": ["error", "expression"], + "max-len": ["error", {"code": 80}], + "lines-around-comment": ["error", {"allowBlockStart": true}], + "jsdoc/require-returns": "off" } } diff --git a/package-lock.json b/package-lock.json index df1ebc9..12ba53a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1380,21 +1380,6 @@ "physical-cpu-count": "^2.0.0" } }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@turf/along": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.0.1.tgz", @@ -1814,6 +1799,16 @@ "requires": { "@turf/helpers": "^5.1.5" } + }, + "@turf/projection": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-5.1.5.tgz", + "integrity": "sha1-JFF+7rLzaBa6n3EueubWo2jt91c=", + "requires": { + "@turf/clone": "^5.1.5", + "@turf/helpers": "^5.1.5", + "@turf/meta": "^5.1.5" + } } } }, @@ -3545,26 +3540,21 @@ } }, "@turf/projection": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-5.1.5.tgz", - "integrity": "sha1-JFF+7rLzaBa6n3EueubWo2jt91c=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.0.1.tgz", + "integrity": "sha512-Y3RvGT6I53MjYKLG69e9sMk45wJXcLbrEO1t6P3WQQQGqA2gYhhMJyV41vE2Z2llrJpvs2dDx/tIeQzGd0HHMQ==", "requires": { - "@turf/clone": "^5.1.5", - "@turf/helpers": "^5.1.5", - "@turf/meta": "^5.1.5" + "@turf/clone": "6.x", + "@turf/helpers": "6.x", + "@turf/meta": "6.x" }, "dependencies": { - "@turf/helpers": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz", - "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8=" - }, - "@turf/meta": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz", - "integrity": "sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0=", + "@turf/clone": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.0.2.tgz", + "integrity": "sha512-UVpYPnW3wRj3bPncR6Z2PRbowBk+nEdVWgGewPxrKKLfvswtVtG9n/OIyvbU3E3ZOadBVxTH2uAMEMOz4800FA==", "requires": { - "@turf/helpers": "^5.1.5" + "@turf/helpers": "6.x" } } } @@ -4325,6 +4315,16 @@ "requires": { "@turf/helpers": "^5.1.5" } + }, + "@turf/projection": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-5.1.5.tgz", + "integrity": "sha1-JFF+7rLzaBa6n3EueubWo2jt91c=", + "requires": { + "@turf/clone": "^5.1.5", + "@turf/helpers": "^5.1.5", + "@turf/meta": "^5.1.5" + } } } }, @@ -4427,12 +4427,6 @@ "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", "dev": true }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -4496,40 +4490,6 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -4571,16 +4531,6 @@ "entities": "^1.1.2" } }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4885,12 +4835,6 @@ "chainsaw": "~0.1.0" } }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true - }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -4939,74 +4883,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5017,15 +4893,6 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "brfs": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", @@ -5238,38 +5105,6 @@ "unset-value": "^1.0.0" } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -5308,12 +5143,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5363,28 +5192,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "chokidar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", - "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -5418,12 +5225,6 @@ } } }, - "cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5451,15 +5252,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", @@ -5532,6 +5324,12 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "comment-parser": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.5.tgz", + "integrity": "sha512-iH9YA35ccw94nx5244GVkpyC9eVTsL71jZz6iz5w6RIf79JLF2AsXHXq9p6Oaohyl3sx5qSMnGsWUDFIAfWL4w==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5567,20 +5365,6 @@ "tinyqueue": "^2.0.3" } }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -5776,12 +5560,6 @@ "randomfill": "^1.0.3" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -6124,15 +5902,6 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -6146,12 +5915,6 @@ "regexp.prototype.flags": "^1.2.0" } }, - "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", @@ -6175,12 +5938,6 @@ } } }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -6383,12 +6140,6 @@ "readable-stream": "^2.0.2" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "earcut": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", @@ -6454,15 +6205,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -6526,12 +6268,6 @@ "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6626,6 +6362,108 @@ } } }, + "eslint-config-eslint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-eslint/-/eslint-config-eslint-6.0.0.tgz", + "integrity": "sha512-dZ+eNtuU8V6+SbXgXjUf11/7Bv4EXzIqn4DxA3Ou1YklwFz31veKYTjzF9uYejUQQjOMpLHTEKWMGSmHAoh4wg==", + "dev": true + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + } + } + }, + "eslint-plugin-jsdoc": { + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.0.3.tgz", + "integrity": "sha512-EviSu0Hgc9Bhz00WhA6829KYC9BaP6JWoycOTA1xFxjQ/2XguRlB3r6nGNA/jkmMDQp5dTQ22s1kAJIaC+dE8Q==", + "dev": true, + "requires": { + "comment-parser": "^0.7.5", + "debug": "^4.1.1", + "jsdoctypeparser": "^8.0.0", + "lodash": "^4.17.15", + "regextras": "^0.7.1", + "semver": "^7.3.2", + "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "eslint-scope": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", @@ -7038,15 +6876,6 @@ "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -7150,13 +6979,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -7203,15 +7025,6 @@ "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -7256,15 +7069,6 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, - "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "dev": true, - "requires": { - "ini": "^1.3.5" - } - }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -7274,25 +7078,6 @@ "type-fest": "^0.8.1" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -7413,12 +7198,6 @@ } } }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -7581,12 +7360,6 @@ } } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -7641,12 +7414,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -7657,12 +7424,6 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -7690,12 +7451,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, "inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -7828,15 +7583,6 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -7848,15 +7594,6 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-color-stop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", @@ -7957,40 +7694,12 @@ "html-tags": "^1.0.0" } }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -8055,12 +7764,6 @@ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8107,6 +7810,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, + "jsdoctypeparser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-8.0.0.tgz", + "integrity": "sha512-eLCs6s4JqN8TjFJfgdiLHRvogLhOAJz+5RIA2FtoMe6ZDyuvghvppnlIToqAEnVbxRqLMrfnNXpW8FpmR6IMBw==", + "dev": true + }, "jsdom": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz", @@ -8177,12 +7886,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -8234,30 +7937,12 @@ "verror": "1.10.0" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, "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 }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8336,12 +8021,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "magic-string": { "version": "0.22.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", @@ -8351,15 +8030,6 @@ "vlq": "^0.2.2" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -8577,12 +8247,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -8742,56 +8406,6 @@ "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, - "nodemon": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", - "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", - "dev": true, - "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, "normalize-html-whitespace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz", @@ -8804,12 +8418,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true - }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -9074,24 +8682,6 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -9292,12 +8882,6 @@ "integrity": "sha1-GN4vl+S/epVRrXURlCtUlverpmA=", "dev": true }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, "pixelworks": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pixelworks/-/pixelworks-1.1.0.tgz", @@ -9824,12 +9408,6 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9868,12 +9446,6 @@ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -9896,31 +9468,12 @@ } } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "purgecss": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.3.0.tgz", @@ -10023,26 +9576,6 @@ "quickselect": "^2.0.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -10058,15 +9591,6 @@ "util-deprecate": "~1.0.1" } }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "regenerate": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", @@ -10136,23 +9660,11 @@ "unicode-match-property-value-ecmascript": "^1.2.0" } }, - "registry-auth-token": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", - "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } + "regextras": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", + "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "dev": true }, "regjsgen": { "version": "0.5.2", @@ -10280,15 +9792,6 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -10396,15 +9899,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -10724,6 +10218,28 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -11040,12 +10556,6 @@ } } }, - "term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true - }, "terser": { "version": "3.17.0", "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", @@ -11146,12 +10656,6 @@ } } }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -11164,15 +10668,6 @@ "safe-regex": "^1.1.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -11194,15 +10689,6 @@ "commander": "2" } }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -11289,15 +10775,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "uncss": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/uncss/-/uncss-0.17.3.tgz", @@ -11323,26 +10800,6 @@ } } }, - "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -11405,15 +10862,6 @@ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", "dev": true }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -11480,79 +10928,6 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, - "update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", - "dev": true, - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -11586,15 +10961,6 @@ } } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -11749,15 +11115,6 @@ "isexe": "^2.0.0" } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -11779,18 +11136,6 @@ "mkdirp": "^0.5.1" } }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "ws": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", @@ -11800,12 +11145,6 @@ "async-limiter": "~1.0.0" } }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index d716934..ad88ba0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tamview", - "version": "1.0.0", + "version": "0.1.0", "description": "", "main": "index.js", "scripts": { @@ -9,13 +9,17 @@ "front:prod": "npx parcel build src/front/index.html --no-source-maps --no-autoinstall", "lint": "eslint ." }, + "engines": { + "node": ">=10.0.0" + }, "keywords": [], - "author": "", - "license": "ISC", + "author": "Mattéo Delabre", + "license": "MIT", "dependencies": { "@turf/along": "^6.0.1", "@turf/helpers": "^6.1.4", "@turf/length": "^6.0.2", + "@turf/projection": "^6.0.1", "@turf/turf": "^5.1.6", "axios": "^0.19.2", "color": "^3.1.2", @@ -26,7 +30,9 @@ }, "devDependencies": { "eslint": "^6.8.0", - "nodemon": "^2.0.2", + "eslint-config-eslint": "^6.0.0", + "eslint-plugin-jsdoc": "^30.0.3", + "eslint-plugin-node": "^11.1.0", "parcel-bundler": "^1.12.4" } } diff --git a/src/back/index.js b/src/back/index.js index 0dbc908..8b808f4 100644 --- a/src/back/index.js +++ b/src/back/index.js @@ -1,16 +1,12 @@ -const express = require('express'); - -const util = require('../util'); -const realtime = require('../tam/realtime'); +const express = require("express"); +const realtime = require("../tam/realtime"); const app = express(); const port = 4321; -app.get('/courses', async (req, res) => -{ - res.header('Access-Control-Allow-Origin', '*'); - const courses = await realtime.getCourses(); - return res.json(courses); +app.get("/courses", async(req, res) => { + res.header("Access-Control-Allow-Origin", "*"); + return res.json(await realtime.fetch()); }); -app.listen(port, () => console.log(`App listening on port ${port}`)); +app.listen(port, () => console.info(`App listening on port ${port}`)); diff --git a/src/front/.eslintrc.json b/src/front/.eslintrc.json index a542dca..b99c111 100644 --- a/src/front/.eslintrc.json +++ b/src/front/.eslintrc.json @@ -1,6 +1,5 @@ { "env": { - "browser": true, - "commonjs": true + "browser": true } } diff --git a/src/front/index.js b/src/front/index.js index 4691e3e..588797f 100644 --- a/src/front/index.js +++ b/src/front/index.js @@ -1,4 +1,6 @@ -require('regenerator-runtime/runtime'); +// eslint-disable-next-line node/no-extraneous-require +require("regenerator-runtime/runtime"); -const {createMap} = require('./map'); -createMap(/* map = */ 'map'); +const { createMap } = require("./map"); + +createMap(/* map = */ "map"); diff --git a/src/front/map.js b/src/front/map.js index b80f9a0..8d384af 100644 --- a/src/front/map.js +++ b/src/front/map.js @@ -1,73 +1,64 @@ -require('ol/ol.css'); +require("ol/ol.css"); -const axios = require('axios'); -const {Map, View} = require('ol'); +const { Map, View } = require("ol"); -const GeoJSON = require('ol/format/GeoJSON').default; -const reader = new GeoJSON({featureProjection: 'EPSG:3857'}); +const GeoJSON = require("ol/format/GeoJSON").default; +const reader = new GeoJSON({ featureProjection: "EPSG:3857" }); -const TileLayer = require('ol/layer/Tile').default; -const XYZSource = require('ol/source/XYZ').default; +const TileLayer = require("ol/layer/Tile").default; +const XYZSource = require("ol/source/XYZ").default; -const VectorLayer = require('ol/layer/Vector').default; -const VectorSource = require('ol/source/Vector').default; -const {getVectorContext} = require('ol/render'); +const VectorLayer = require("ol/layer/Vector").default; +const VectorSource = require("ol/source/Vector").default; +const { getVectorContext } = require("ol/render"); -const Feature = require('ol/Feature').default; -const Point = require('ol/geom/Point').default; -const LineString = require('ol/geom/LineString').default; +const Point = require("ol/geom/Point").default; -const proj = require('ol/proj'); +const proj = require("ol/proj"); -const {Style, Fill, Stroke, Circle, Icon} = require('ol/style'); -const color = require('color'); +const { Style, Fill, Stroke, Circle, Icon } = require("ol/style"); +const colorModule = require("color"); -const mapboxToken = `pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ -h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw`; +const mapboxToken = "pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ +h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw"; -const simulation = require('../tam/simulation'); -const network = require('../tam/network.json'); +const simulation = require("../tam/simulation"); +const network = require("../tam/network.json"); -const lineFeaturesOrder = (feature1, feature2) => -{ - const lines1 = feature1.get('lines'); +const lineFeaturesOrder = (feature1, feature2) => { + const lines1 = feature1.get("lines"); - if (lines1.length === 0) - { + if (lines1.length === 0) { return -1; } - const lines2 = feature2.get('lines'); + const lines2 = feature2.get("lines"); - if (lines2.length === 0) - { + if (lines2.length === 0) { return 1; } return Math.min(...lines1) - Math.min(...lines2); }; -const makeDataSources = () => -{ +const makeDataSources = () => { const segmentsSource = new VectorSource(); const stopsSource = new VectorSource(); - const readFeatures = hash => Object.values(hash).map(json => - { + const readFeatures = hash => Object.values(hash).map(json => { json.properties.lines = json.properties.routes.filter( + // Only consider normal routes (excluding alternate routes) ([lineRef, routeRef]) => - network.lines[lineRef].routes[routeRef].state === 'normal' + network.lines[lineRef].routes[routeRef].state === "normal" ).map(([lineRef]) => lineRef); - if (json.properties.lines.length >= 1) - { + if (json.properties.lines.length >= 1) { json.properties.colors = json.properties.lines.map( - lineRef => network.lines[lineRef].color); - } - else - { - json.properties.colors = ['#FFFFFF']; + lineRef => network.lines[lineRef].color + ); + } else { + json.properties.colors = ["#FFFFFF"]; } return reader.readFeature(json); @@ -75,19 +66,19 @@ const makeDataSources = () => segmentsSource.addFeatures(readFeatures(network.segments)); stopsSource.addFeatures(readFeatures(network.stops)); - return {segmentsSource, stopsSource}; + return { segmentsSource, stopsSource }; }; -const makeBorderColor = mainColor => -{ - const hsl = color(mainColor).hsl(); +const makeBorderColor = mainColor => { + const hsl = colorModule(mainColor).hsl(); + hsl.color = Math.max(0, hsl.color[2] -= 20); return hsl.hex(); }; -const makeCourseColor = mainColor => -{ - const hsl = color(mainColor).hsl(); +const makeCourseColor = mainColor => { + const hsl = colorModule(mainColor).hsl(); + hsl.color = Math.max(0, hsl.color[2] += 10); return hsl.hex(); }; @@ -100,43 +91,41 @@ const sizes = { courseSize: 15, courseOuterBorder: 13, courseBorder: 10, - courseInnerBorder: 7, + courseInnerBorder: 7 }; const segmentBorderStyle = feature => new Style({ stroke: new Stroke({ - color: makeBorderColor(feature.get('colors')[0]), - width: sizes.segmentOuter, - }), + color: makeBorderColor(feature.get("colors")[0]), + width: sizes.segmentOuter + }) }); const segmentInnerStyle = feature => new Style({ stroke: new Stroke({ - color: feature.get('colors')[0], - width: sizes.segmentInner, - }), + color: feature.get("colors")[0], + width: sizes.segmentInner + }) }); const stopStyle = feature => new Style({ image: new Circle({ fill: new Fill({ - color: feature.get('colors')[0], + color: feature.get("colors")[0] }), stroke: new Stroke({ - color: makeBorderColor(feature.get('colors')[0]), - width: sizes.stopBorder, + color: makeBorderColor(feature.get("colors")[0]), + width: sizes.stopBorder }), - radius: sizes.stopRadius, - }), + radius: sizes.stopRadius + }) }); const courseStyles = {}; -const getCourseStyle = color => -{ - if (!(color in courseStyles)) - { - const icon = document.createElement('canvas'); +const getCourseStyle = lineColor => { + if (!(lineColor in courseStyles)) { + const icon = window.document.createElement("canvas"); const shapeSize = sizes.courseSize; const iconSize = sizes.courseSize + sizes.courseOuterBorder; @@ -147,18 +136,17 @@ const getCourseStyle = color => const cx = icon.width / 2; const cy = icon.height / 2; - const iconCtx = icon.getContext('2d'); + const iconCtx = icon.getContext("2d"); - for (let [color, size] of [ - [makeBorderColor(color), sizes.courseOuterBorder], - [color, sizes.courseBorder], - [makeCourseColor(color), sizes.courseInnerBorder] - ]) - { + for (const [color, size] of [ + [makeBorderColor(lineColor), sizes.courseOuterBorder], + [lineColor, sizes.courseBorder], + [makeCourseColor(lineColor), sizes.courseInnerBorder] + ]) { iconCtx.fillStyle = color; iconCtx.strokeStyle = color; iconCtx.lineWidth = size; - iconCtx.lineJoin = 'round'; + iconCtx.lineJoin = "round"; iconCtx.miterLimit = 200000; iconCtx.beginPath(); @@ -170,34 +158,34 @@ const getCourseStyle = color => iconCtx.fill(); } - courseStyles[color] = new Style({ + courseStyles[lineColor] = new Style({ image: new Icon({ img: icon, - imgSize: [icon.width, icon.height], - }), + imgSize: [icon.width, icon.height] + }) }); } - return courseStyles[color]; + return courseStyles[lineColor]; }; -const createMap = target => -{ +const createMap = target => { + // Map background const backgroundSource = new XYZSource({ - url: 'https://api.mapbox.com/' + [ - 'styles', 'v1', 'mapbox', 'streets-v11', - 'tiles', '512', '{z}', '{x}', '{y}', - ].join('/') + `?access_token=${mapboxToken}`, - tileSize: [512, 512], + url: `https://api.mapbox.com/${[ + "styles", "v1", "mapbox", "streets-v11", + "tiles", "512", "{z}", "{x}", "{y}" + ].join("/")}?access_token=${mapboxToken}`, + tileSize: [512, 512] }); const backgroundLayer = new TileLayer({ - source: backgroundSource, + source: backgroundSource }); // Static data overlay - const {segmentsSource, stopsSource} = makeDataSources(); + const { segmentsSource, stopsSource } = makeDataSources(); const segmentsBorderLayer = new VectorLayer({ source: segmentsSource, @@ -205,7 +193,7 @@ const createMap = target => style: segmentBorderStyle, updateWhileInteracting: true, - updateWhileAnimating: true, + updateWhileAnimating: true }); const segmentsInnerLayer = new VectorLayer({ @@ -214,7 +202,7 @@ const createMap = target => style: segmentInnerStyle, updateWhileInteracting: true, - updateWhileAnimating: true, + updateWhileAnimating: true }); const stopsLayer = new VectorLayer({ @@ -224,7 +212,7 @@ const createMap = target => minZoom: 13, updateWhileInteracting: true, - updateWhileAnimating: true, + updateWhileAnimating: true }); // Setup map @@ -232,7 +220,7 @@ const createMap = target => center: proj.fromLonLat([3.88, 43.605]), zoom: 14, maxZoom: 22, - constrainResolution: true, + constrainResolution: true }); const map = new Map({ @@ -241,9 +229,9 @@ const createMap = target => backgroundLayer, segmentsBorderLayer, segmentsInnerLayer, - stopsLayer, + stopsLayer ], - view, + view }); // Run courses simulation @@ -252,26 +240,25 @@ const createMap = target => // Course on which the view is currently focused let focusedCourse = null; - const startFocus = courseId => - { - if (courseId in simulInstance.courses) - { + const startFocus = courseId => { + if (courseId in simulInstance.courses) { const course = simulInstance.courses[courseId]; + view.animate({ center: course.position, - duration: 500, - }, () => focusedCourse = courseId); + duration: 500 + }, () => { + focusedCourse = courseId; + }); } }; - const stopFocus = () => - { + const stopFocus = () => { focusedCourse = null; }; // Draw courses directly on the map - map.on('postcompose', ev => - { + map.on("postcompose", ev => { simulInstance.update(); // The normal way to access a layer’s vector context is through the @@ -281,17 +268,16 @@ const createMap = target => // if no stop is visible. This hack listens to the global `postcompose` // event, which is always triggered at every frame, and reconstructs // the stops layer’s vector context from internal variables - if (stopsLayer.renderer_) - { + /* eslint-disable no-underscore-dangle */ + if (stopsLayer.renderer_) { ev.context = stopsLayer.renderer_.context; - ev.inversePixelTransform - = stopsLayer.renderer_.inversePixelTransform; + ev.inversePixelTransform = + stopsLayer.renderer_.inversePixelTransform; + /* eslint-enable no-underscore-dangle */ const ctx = getVectorContext(ev); - let rotation = 0; - for (let course of Object.values(simulInstance.courses)) - { + for (const course of Object.values(simulInstance.courses)) { const color = network.lines[course.line].color; const style = getCourseStyle(color); @@ -299,10 +285,10 @@ const createMap = target => ctx.setStyle(style); const point = new Point(course.position); + ctx.drawGeometry(point); - if (course.id === focusedCourse) - { + if (course.id === focusedCourse) { view.setCenter(course.position); } } @@ -313,20 +299,17 @@ const createMap = target => map.render(); - map.on('singleclick', ev => - { + map.on("singleclick", ev => { const mousePixel = map.getPixelFromCoordinate(ev.coordinate); const maxDistance = sizes.courseSize + sizes.courseOuterBorder; - for (let course of Object.values(simulInstance.courses)) - { + for (const course of Object.values(simulInstance.courses)) { const coursePixel = map.getPixelFromCoordinate(course.position); const dx = mousePixel[0] - coursePixel[0]; const dy = mousePixel[1] - coursePixel[1]; const distance = dx * dx + dy * dy; - if (distance <= maxDistance * maxDistance) - { + if (distance <= maxDistance * maxDistance) { startFocus(course.id); return; } diff --git a/src/tam/.eslintrc.json b/src/tam/.eslintrc.json new file mode 100644 index 0000000..94195b1 --- /dev/null +++ b/src/tam/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "commonjs": true + } +} diff --git a/src/tam/network.js b/src/tam/network.js index 295f8db..73a65e7 100644 --- a/src/tam/network.js +++ b/src/tam/network.js @@ -1,5 +1,5 @@ /** - * @file + * @fileoverview * * Extract static information about the TaM network from OpenStreetMap (OSM): * tram and bus lines, stops and routes. @@ -12,25 +12,24 @@ * the `script/update-network` script. */ -const turfHelpers = require('@turf/helpers'); -const turfLength = require('@turf/length').default; -const util = require('../util'); -const osm = require('./sources/osm'); -const tam = require('./sources/tam'); +const turfHelpers = require("@turf/helpers"); +const turfLength = require("@turf/length").default; +const util = require("../util"); +const osm = require("./sources/osm"); /** * Fetch stops and lines of the network. - * - * @param lineRefs List of lines to fetch. - * @return Object with a set of stops, segments and lines. + * @param {string[]} lineRefs List of lines to fetch. + * @returns {{stops: Object, lines: Object, segments: Object}} Set of stops, + * segments and lines. */ -const fetch = async (lineRefs) => -{ +const fetch = async lineRefs => { + // Retrieve routes, ways and stops from OpenStreetMap const rawData = await osm.runQuery(`[out:json]; // Find the public transport line bearing the requested reference -relation[network="TaM"][type="route_master"][ref~"^(${lineRefs.join('|')})$"]; +relation[network="TaM"][type="route_master"][ref~"^(${lineRefs.join("|")})$"]; // Recursively fetch routes, ways and stops inside the line (._; >>;); @@ -45,8 +44,7 @@ out body qt; const routeMasters = elementsList.filter(osm.isTransportLine); // Retrieved objects indexed by ID - const elements = elementsList.reduce((prev, elt) => - { + const elements = elementsList.reduce((prev, elt) => { prev[elt.id] = elt; return prev; }, {}); @@ -60,46 +58,39 @@ out body qt; // All segments leading from one stop to another const segments = {}; - for (let routeMaster of routeMasters) - { + for (const routeMaster of routeMasters) { const lineRef = routeMaster.tags.ref; - const color = routeMaster.tags.colour || '#000000'; + const color = routeMaster.tags.colour || "#000000"; // Extract all routes for the given line const routes = []; - for (let [routeRef, {ref: routeId}] of routeMaster.members.entries()) - { + for (const [routeRef, data] of routeMaster.members.entries()) { + const routeId = data.ref; const route = elements[routeId]; - const {from, via, to, name} = route.tags; - const state = route.tags.state || 'normal'; + const { from, via, to, name } = route.tags; + const state = route.tags.state || "normal"; // Add missing stops to the global stops object - for (let {ref, role} of route.members) - { - if (role === 'stop') - { + for (const { ref, role } of route.members) { + if (role === "stop") { const stop = elements[ref]; - if (!('ref' in stop.tags)) - { + if (!("ref" in stop.tags)) { throw new Error(`Stop ${stop.id} (${osm.viewNode(stop.id)}) on line ${route.tags.name} is missing a “ref” tag`); } - if (!(stop.tags.ref in stops)) - { + if (!(stop.tags.ref in stops)) { stops[stop.tags.ref] = turfHelpers.point([ stop.lon, stop.lat ], { name: stop.tags.name, - routes: [[lineRef, routeRef]], + routes: [[lineRef, routeRef]] }); - } - else - { + } else { stops[stop.tags.ref].properties.routes.push([ lineRef, routeRef @@ -111,21 +102,19 @@ a “ref” tag`); // Check that the route consists of a block of stops and platforms // followed by a block of routes as dictated by PTv2 const relationPivot = route.members.findIndex( - ({role}) => role === '' + ({ role }) => role === "" ); if (!route.members.slice(0, relationPivot).every( - ({role}) => role === 'stop' || role === 'platform' - )) - { + ({ role }) => role === "stop" || role === "platform" + )) { throw new Error(`Members with invalid roles in between stops of ${name}`); } if (!route.members.slice(relationPivot).every( - ({role}) => role === '' - )) - { + ({ role }) => role === "" + )) { throw new Error(`Members with invalid roles inside the path of ${name}`); } @@ -134,22 +123,21 @@ of ${name}`); // order as per PTv2 and to be traversed in order by the sequence // of ways extracted below const lineStops = route.members.slice(0, relationPivot) - .filter(({role}) => role === 'stop') - .map(({ref}) => ref); + .filter(({ role }) => role === "stop") + .map(({ ref }) => ref); // List of ways making up the route’s path through its stops // with each way connected to the next through a single point const ways = route.members.slice(relationPivot) - .map(({ref}) => ref); + .map(({ ref }) => ref); // Merge all used ways in a single path let path = []; let currentNode = lineStops[0]; - for (let wayIndex = 0; wayIndex < ways.length; wayIndex += 1) - { + for (let wayIndex = 0; wayIndex < ways.length; wayIndex += 1) { const way = elements[ways[wayIndex]]; - const {nodes: wayNodes, tags: wayTags} = way; + const { nodes: wayNodes } = way; const wayNodesSet = new Set(wayNodes); const curNodeIndex = wayNodes.indexOf(currentNode); @@ -159,13 +147,11 @@ of ${name}`); let nextNode = null; let nextNodeIndex = null; - if (wayIndex + 1 < ways.length) - { + if (wayIndex + 1 < ways.length) { const nextNodeCandidates = elements[ways[wayIndex + 1]] .nodes.filter(node => wayNodesSet.has(node)); - if (nextNodeCandidates.length !== 1) - { + if (nextNodeCandidates.length !== 1) { throw new Error(`There should be exactly one point connecting way n°${wayIndex} and way n°${wayIndex + 1} in ${name}, but there are ${nextNodeCandidates.length}`); @@ -173,24 +159,20 @@ but there are ${nextNodeCandidates.length}`); nextNode = nextNodeCandidates[0]; nextNodeIndex = wayNodes.indexOf(nextNode); - } - else - { + } else { nextNodeIndex = wayNodes.length; } - if (curNodeIndex < nextNodeIndex) - { + if (curNodeIndex < nextNodeIndex) { + // Use the way in its normal direction path = path.concat( wayNodes.slice(curNodeIndex, nextNodeIndex) ); - } - else - { + } else { + // Use the way in the reverse direction - if (osm.isOneWay(way)) - { + if (osm.isOneWay(way)) { throw new Error(`Way n°${wayIndex} in ${name} is one-way and cannot be used in reverse.`); } @@ -205,8 +187,7 @@ ${name} is one-way and cannot be used in reverse.`); } // Split the path into segments between stops - for (let stopIdx = 0; stopIdx + 1 < lineStops.length; ++stopIdx) - { + for (let stopIdx = 0; stopIdx + 1 < lineStops.length; ++stopIdx) { const begin = elements[lineStops[stopIdx]].tags.ref; const beginIdx = path.indexOf(lineStops[stopIdx]); const end = elements[lineStops[stopIdx + 1]].tags.ref; @@ -218,29 +199,28 @@ ${name} is one-way and cannot be used in reverse.`); const id = `${begin}-${end}`; const nodesIds = path.slice(beginIdx, endIdx); - if (id in segments) - { + if (id in segments) { if (!util.arraysEqual( nodesIds, segments[id].properties.nodesIds - )) - { + )) { throw new Error(`Segment ${id} is defined as a different sequence of nodes in two or more lines.`); } segments[id].properties.routes.push([lineRef, routeRef]); - } - else - { - segments[id] = turfHelpers.lineString(nodesIds.map(id => [ - elements[id].lon, - elements[id].lat - ]), { + } else { + segments[id] = turfHelpers.lineString(nodesIds.map( + nodeId => [ + elements[nodeId].lon, + elements[nodeId].lat + ] + ), { + // Keep track of the original sequence of nodes to // compare with duplicates nodesIds, - routes: [[lineRef, routeRef]], + routes: [[lineRef, routeRef]] }); segments[id].properties.length = ( @@ -249,24 +229,26 @@ different sequence of nodes in two or more lines.`); } routes.push({ - from, via, to, - name, state, + from, + via, + to, + name, + state }); } lines[lineRef] = { color, - routes, + routes }; } // Remove OSM nodes from segments that were only used for checking validity - for (let segment of Object.values(segments)) - { + for (const segment of Object.values(segments)) { delete segment.properties.nodesIds; } - return {stops, lines, segments}; + return { stops, lines, segments }; }; exports.fetch = fetch; diff --git a/src/tam/realtime.js b/src/tam/realtime.js index 4d8d02b..3f22979 100644 --- a/src/tam/realtime.js +++ b/src/tam/realtime.js @@ -1,6 +1,5 @@ -const tam = require('./sources/tam'); -const util = require('../util'); -const network = require('./network.json'); +const tam = require("./sources/tam"); +const network = require("./network.json"); // Time at which the course data needs to be updated next let nextUpdate = null; @@ -8,99 +7,78 @@ let nextUpdate = null; // Current information about courses let currentCourses = null; +/** + * Information about the course of a vehicle. + * @typedef {Object} Course + * @property {string} id Unique identifier for this course. + * @property {string} line Transport line number. + * @property {string} finalStop Final stop to which the course is headed. + * @property {Object.} nextPassings Next stations to which + * the vehicle will stop, associated to the passing timestamp. + */ + /** * Fetch real-time information about active courses in the TaM network. * * New data will only be fetched from the TaM server once every minute, * otherwise pulling from the in-memory cache. - * - * The following information is provided for each active course: - * - * - `id`: Unique identifier for the course. - * - `line`: Line number. - * - `finalStop`: The final stop to which the course is headed. - * - `nextPassings`: Next passings of the vehicle, as a dictionary associating - * each next stop to the passing timestamp. - * - * @return Mapping from active course IDs to information about each course. + * @returns {Object.} Mapping from active course IDs to + * information about each course. */ -const getCourses = () => new Promise((res, rej) => -{ - if (nextUpdate !== null && Date.now() < nextUpdate) - { - res(currentCourses); - return; - } +const fetch = async() => { + if (nextUpdate === null || Date.now() >= nextUpdate) { + const courses = {}; + const passings = tam.fetchRealtime(); + const timing = (await passings.next()).value; - const courses = {}; - let lastUpdate = null; + nextUpdate = timing.nextUpdate; - tam.fetchRealtime((err, entry) => - { - if (err) - { - rej(err); - return; + // Aggregate passings relative to the same course + for await (const passing of passings) { + const { + course: id, + routeShortName: line, + stopId, + destArCode: finalStop + } = passing; + + const arrivalTime = ( + timing.lastUpdate + + parseInt(passing.delaySec, 10) * 1000 + ); + + if (!(id in courses)) { + courses[id] = { + id, + line, + finalStop, + nextPassings: { [stopId]: arrivalTime } + }; + } else { + courses[id].nextPassings[stopId] = arrivalTime; + } } - if (!util.isObject(entry)) - { - // Filter courses to only keep those referring to known data - for (let courseId of Object.keys(courses)) - { - const course = courses[courseId]; + // Filter courses to only keep those referring to known data + for (const courseId of Object.keys(courses)) { + const course = courses[courseId]; - if (!(course.line in network.lines)) - { - delete courses[courseId]; - } - else - { - for (let stopId of Object.keys(course.nextPassings)) - { - if (!(stopId in network.stops)) - { - delete courses[courseId]; - break; - } + if (!(course.line in network.lines)) { + delete courses[courseId]; + } else { + for (const stopId of Object.keys(course.nextPassings)) { + if (!(stopId in network.stops)) { + delete courses[courseId]; + break; } } } - - currentCourses = courses; - res(currentCourses); - return; } - if ('lastUpdate' in entry) - { - // Metadata header - lastUpdate = entry.lastUpdate; - nextUpdate = entry.nextUpdate; - return; - } + currentCourses = courses; + } - const { - course: id, - routeShortName: line, - stopId, - destArCode: finalStop, - } = entry; + return currentCourses; +}; - const arrivalTime = lastUpdate + parseInt(entry.delaySec, 10) * 1000; - - if (!(id in courses)) - { - courses[id] = { - id, line, finalStop, - nextPassings: {[stopId]: arrivalTime}, - }; - } - else - { - courses[id].nextPassings[stopId] = arrivalTime; - } - }); -}); - -exports.getCourses = getCourses; +exports.fetch = fetch; diff --git a/src/tam/simulation.js b/src/tam/simulation.js index 3eec8b8..4b03705 100644 --- a/src/tam/simulation.js +++ b/src/tam/simulation.js @@ -1,14 +1,12 @@ -const axios = require('axios'); -const turfAlong = require('@turf/along').default; -const turfProjection = require('@turf/projection'); -const network = require('./network.json'); +const axios = require("axios"); +const turfAlong = require("@turf/along").default; +const turfProjection = require("@turf/projection"); +const network = require("./network.json"); -const server = 'http://localhost:4321'; +const server = "http://localhost:4321"; -class Course -{ - constructor(data) - { +class Course { + constructor(data) { this.id = data.id; this.passings = {}; this.state = null; @@ -29,18 +27,15 @@ class Course this.history = []; } - get currentSegment() - { - if (this.state !== 'moving') - { - return undefined; + get currentSegment() { + if (this.state !== "moving") { + return null; } return network.segments[`${this.departureStop}-${this.arrivalStop}`]; } - updateData(data) - { + updateData(data) { this.line = data.line; this.finalStop = data.finalStop; Object.assign(this.passings, data.nextPassings); @@ -48,104 +43,84 @@ class Course const now = Date.now(); // Make sure we’re on the right `stopped`/`moving` state - if (this.state === null) - { + if (this.state === null) { let previousStop = null; let departureTime = 0; let nextStop = null; let arrivalTime = Infinity; - for (let [stopId, time] of Object.entries(this.passings)) - { - if (time > now && time < arrivalTime) - { + for (const [stopId, time] of Object.entries(this.passings)) { + if (time > now && time < arrivalTime) { nextStop = stopId; arrivalTime = time; } - if (time < now && time > departureTime) - { + if (time < now && time > departureTime) { previousStop = stopId; departureTime = time; } } - if (nextStop === null) - { + if (nextStop === null) { return false; } - if (previousStop === null) - { + if (previousStop === null) { // Teleport to the first known stop this.arriveToStop(nextStop); - } - else - { + } else { + // Teleport to the first known segment this.arriveToStop(previousStop); this.moveToStop(nextStop, arrivalTime); } - } - else if (this.state === 'moving') - { - // Should already be at the next stop - if (this.passings[this.arrivalStop] <= now) - { + } else if (this.state === "moving") { + if (this.passings[this.arrivalStop] <= now) { + // Should already be at the next stop this.arriveToStop(this.arrivalStop); - } - // On the right track, update the arrival time - else - { + } else { + // On the right track, update the arrival time this.arrivalTime = this.passings[this.arrivalStop]; } - } - else // this.state === 'stopped' - { + } else { + // (this.state === 'stopped') // Try moving to the next stop let nextStop = null; let arrivalTime = Infinity; - for (let [stopId, time] of Object.entries(this.passings)) - { - if (time > now && time < arrivalTime) - { + for (const [stopId, time] of Object.entries(this.passings)) { + if (time > now && time < arrivalTime) { nextStop = stopId; arrivalTime = time; } } - if (nextStop === null) - { + if (nextStop === null) { // This course is finished return false; } - if (nextStop !== this.currentStop) - { + if (nextStop !== this.currentStop) { this.moveToStop(nextStop, arrivalTime); } } - if (this.state === 'moving') - { + if (this.state === "moving") { this.speed = this.computeTheoreticalSpeed(); } return true; } - tick(time) - { - if (this.state === 'moving') - { + tick(time) { + if (this.state === "moving") { + // Integrate current speed in travelled distance this.traveledDistance += this.speed * time; const segment = this.currentSegment; - if (this.traveledDistance >= segment.properties.length) - { + if (this.traveledDistance >= segment.properties.length) { this.arriveToStop(this.arrivalStop); return; } @@ -164,7 +139,7 @@ class Course this.angle = Math.atan2( positions[0][1] - positions[2][1], - positions[2][0] - positions[0][0], + positions[2][0] - positions[0][0] ); this.position = positions[1]; @@ -173,42 +148,40 @@ class Course /** * Transition this course to a state where it has arrived to a stop. - * - * @param stop Identifier for the stop to which the course arrives. + * @param {string} stop Identifier for the stop to which + * the course arrives. + * @returns {undefined} */ - arriveToStop(stop) - { - this.state = 'stopped'; + arriveToStop(stop) { + this.state = "stopped"; this.currentStop = stop; this.position = ( turfProjection.toMercator(network.stops[stop]) .geometry.coordinates); - this.history.push(['arriveToStop', stop]); + this.history.push(["arriveToStop", stop]); } /** * Transition this course to a state where it is moving to a stop. - * - * @param stop Next stop for this course. - * @param arrivalTime Planned arrival time to that stop. + * @param {string} stop Next stop for this course. + * @param {number} arrivalTime Planned arrival time to that stop. + * @returns {undefined} */ - moveToStop(stop, arrivalTime) - { - if (!(`${this.currentStop}-${stop}` in network.segments)) - { + moveToStop(stop, arrivalTime) { + if (!(`${this.currentStop}-${stop}` in network.segments)) { console.warn(`Course ${this.id} cannot go from stop ${this.currentStop} to stop ${stop}. Teleporting to ${stop}`); this.arriveToStop(stop); return; } - this.state = 'moving'; + this.state = "moving"; this.departureStop = this.currentStop; this.arrivalStop = stop; this.arrivalTime = arrivalTime; this.traveledDistance = 0; this.speed = 0; - this.history.push(['moveToStop', stop, arrivalTime]); + this.history.push(["moveToStop", stop, arrivalTime]); console.info(`Course ${this.id} leaving stop ${this.currentStop} \ with initial speed ${this.computeTheoreticalSpeed() * 3600} km/h`); @@ -216,11 +189,10 @@ with initial speed ${this.computeTheoreticalSpeed() * 3600} km/h`); /** * Compute the speed that needs to be maintained to arrive on time. + * @returns {number} Speed in meters per millisecond. */ - computeTheoreticalSpeed() - { - if (this.state !== 'moving') - { + computeTheoreticalSpeed() { + if (this.state !== "moving") { return 0; } @@ -230,47 +202,34 @@ with initial speed ${this.computeTheoreticalSpeed() * 3600} km/h`); segment.properties.length - this.traveledDistance ); - if (remainingDistance <= 0) - { + if (remainingDistance <= 0) { return 0; } - else if (remainingTime <= 0) - { + if (remainingTime <= 0) { // We’re late, go to maximum speed return 50 / 3600; // 50 km/h } - else - { - return remainingDistance / remainingTime; - } + + return remainingDistance / remainingTime; } } -const updateData = async (courses) => -{ +const updateData = async courses => { const dataset = (await axios.get(`${server}/courses`)).data; // Update or create new courses - for (let [id, data] of Object.entries(dataset)) - { - if (id in courses) - { - if (!courses[id].updateData(data)) - { + for (const [id, data] of Object.entries(dataset)) { + if (id in courses) { + if (!courses[id].updateData(data)) { console.info(`Course ${id} is finished.`); delete courses[id]; } - } - else - { + } else { const newCourse = new Course(data); - if (!newCourse.updateData(data)) - { + if (!newCourse.updateData(data)) { console.info(`Ignoring course ${id} which is outdated.`); - } - else - { + } else { console.info(`Course ${id} starting.`); courses[id] = newCourse; } @@ -278,45 +237,39 @@ const updateData = async (courses) => } // Remove stale courses - for (let id of Object.keys(courses)) - { - if (!(id in dataset)) - { + for (const id of Object.keys(courses)) { + if (!(id in dataset)) { delete courses[id]; } } }; -const tick = (courses, time) => -{ - for (let course of Object.values(courses)) - { +const tick = (courses, time) => { + for (const course of Object.values(courses)) { course.tick(time); } }; -const start = () => -{ +const start = () => { const courses = {}; let lastFrame = null; let lastUpdate = null; - const update = () => - { + const update = () => { const now = Date.now(); - if (lastUpdate === null || lastUpdate + 5000 <= now) - { + if (lastUpdate === null || lastUpdate + 5000 <= now) { lastUpdate = now; updateData(courses); } const time = lastFrame === null ? 0 : now - lastFrame; + lastFrame = now; tick(courses, time); }; - return {courses, update}; + return { courses, update }; }; exports.start = start; diff --git a/src/tam/sources/osm.js b/src/tam/sources/osm.js index 9731a52..a015896 100644 --- a/src/tam/sources/osm.js +++ b/src/tam/sources/osm.js @@ -1,77 +1,55 @@ /** - * @file + * @fileoverview * * Interface with the OpenStreetMap collaborative mapping database. */ -const axios = require('axios'); -const {isObject} = require('../../util'); +const axios = require("axios"); +const { isObject } = require("../../util"); /** * Submit a query to an Overpass endpoint. * - * See for more + * See for more * information on the Overpass Query Language (Overpass QL). - * * @async - * @param query Query to send. - * @param [endpoint] Overpass endpoint to use. - * @return Results returned by the endpoint. If JSON output is requested in - * the query, the result will automatically be parsed into a JS object. + * @param {string} query Query to send. + * @param {string} [endpoint] Overpass endpoint to use. + * @returns {string|Object} Results returned by the endpoint. If JSON output + * is requested in the query, the result will automatically be parsed into + * a JS object. */ const runQuery = ( query, - endpoint = 'https://lz4.overpass-api.de/api/interpreter' + endpoint = "https://lz4.overpass-api.de/api/interpreter" ) => ( - axios.post(endpoint, 'data=' + query) - .then(res => res.data) + axios.post(endpoint, `data=${query}`) + .then(res => res.data) ); exports.runQuery = runQuery; -/** - * Create a link to add tags into JOSM. - * - * The JOSM remote control must be activated and JOSM must be running for this - * link to work. See . - * - * @param id Identifier for the object to add the tags to. - * @param tags List of tags to add, in the `key=value` format. - * @return Link for remotely adding the tags. - */ -const addTagsToNode = (id, tags) => ( - 'http://127.0.0.1:8111/load_object?' + [ - `objects=n${id}`, - 'new_layer=false', - 'addtags=' + tags.join('%7C'), - ].join('&') -); - -exports.addTagsToNode = addTagsToNode; - /** * Create a link to view a node. - * - * @param id Identifier for the node to view. - * @return Link to view this node on the OSM website. + * @param {string|number} id Identifier for the node to view. + * @returns {string} Link to view this node on the OSM website. */ -const viewNode = id => `https://www.openstreetmap.org/node/${id}`; +const viewNode = id => `https://www.osm.org/node/${id}`; exports.viewNode = viewNode; /** * Determine if an OSM way is one-way or not. * - * See for details. - * - * @param tags Set of tags of the way. - * @return True iff. the way is one-way. + * See for details. + * @param {Object} obj OSM way object. + * @returns {boolean} Whether the way is one-way. */ -const isOneWay = object => ( - object.type === 'way' - && isObject(object.tags) - && (object.tags.oneway === 'yes' || object.tags.junction === 'roundabout' - || object.tags.highway === 'motorway') +const isOneWay = obj => ( + obj.type === "way" && + isObject(obj.tags) && + (obj.tags.oneway === "yes" || obj.tags.junction === "roundabout" || + obj.tags.highway === "motorway") ); exports.isOneWay = isOneWay; @@ -79,16 +57,15 @@ exports.isOneWay = isOneWay; /** * Determine if an OSM object is a public transport line (route master). * - * See - * and . - * - * @param object OSM object. - * @return True iff. the relation is a public transport line. + * See + * and . + * @param {Object} obj OSM relation object. + * @returns {boolean} Whether the relation is a public transport line. */ -const isTransportLine = object => ( - object.type === 'relation' - && isObject(object.tags) - && object.tags.type === 'route_master' +const isTransportLine = obj => ( + obj.type === "relation" && + isObject(obj.tags) && + obj.tags.type === "route_master" ); exports.isTransportLine = isTransportLine; diff --git a/src/tam/sources/tam.js b/src/tam/sources/tam.js index c528454..6b93146 100644 --- a/src/tam/sources/tam.js +++ b/src/tam/sources/tam.js @@ -1,123 +1,104 @@ -const unzip = require('unzip-stream'); -const csv = require('csv-parse'); -const axios = require('axios'); +const csv = require("csv-parse"); +const axios = require("axios"); +const { snakeToCamelCase, unzipFile } = require("../../util"); /** - * Process a CSV stream to extract passings. + * Data available for each passing of a vehicle at a station. * - * @private - * @param csvStream Stream containing CSV data. - * @param callback See fetchRealtime for a description of the callback. + * See also . + * @typedef {Object} Passing + * @property {string} course Identifier of the overall trip of the same vehicle + * from one end of the route to another (unique for the day). + * @property {string} stopCode Unused internal stop identifier. + * @property {string} stopId Unique network identifier for the station at + * which the vehicle will pass (same id as in GTFS). + * @property {string} routeShortName Transport line number. + * @property {string} tripHeadsign Name of the final stop of this trip. + * @property {string} directionId Route identifier inside the line. + * @property {string} departureTime Theoretical time at which the + * vehicle will depart the stop (HH:MM:SS format). + * @property {string} isTheorical (sic) Whether the arrival time is only + * a theoretical information. + * @property {string} delaySec Number of seconds before the vehicle arrives + * at the station. + * @property {string} destArCode Unique network identifier for the final + * stop of this trip. */ -const processTamPassingStream = (csvStream, callback) => -{ - const parser = csv({ - delimiter: ';', - }); - const rowStream = csvStream.pipe(parser); - - rowStream.on('readable', () => - { - let row; - - while ((row = rowStream.read())) - { - if (row.length === 0 || row[0] === 'course') - { - // Ignore les lignes invalides et l’en-tête - continue; - } - - callback(null, { - course: row[0], - stopCode: row[1], - stopId: row[2], - stopName: row[3], - routeShortName: row[4], - tripHeadsign: row[5], - directionId: row[6], - departureTime: row[7], - isTheorical: row[8], - delaySec: row[9], - destArCode: row[10], - }); - } - }); - - rowStream.on('end', () => callback(null, null)); - rowStream.on('error', err => callback(err)); -}; - -const tamRealtimeEndpoint = 'http://data.montpellier3m.fr/node/10732/download'; +const realtimeEndpoint = "http://data.montpellier3m.fr/node/10732/download"; /** - * Fetch realtime passings across the network. - * - * The callback always receives two arguments. If an error occurs, the first - * argument will contain an object with information about the error. Otherwise, - * it will be null and the second argument will be the payload. - * - * The first call will provide metadata, specifically the time at which - * the data that follows was last updated (`lastUpdate`) and the time at - * which it will be updated next (`nextUpdate`). - * - * Following calls will provide each passing of the dataset individually, - * and will be closed with a call where both arguments are null. - * - * @param callback Called for each passing during parsing. + * Fetch real time passings of vehicles across the network. + * @yields {{{lastUpdate: number, nextUpdate: number}|Passing}} First value + * is an object containing the time of last update and the time of next + * update of this information. Next values are informations about each vehicle + * passing. */ -const fetchRealtime = callback => -{ - axios.get(tamRealtimeEndpoint, { - responseType: 'stream' - }).then(res => - { - const lastUpdate = new Date(res.headers['last-modified']).getTime(); +const fetchRealtime = async function *() { + const res = await axios.get(realtimeEndpoint, { + responseType: "stream" + }); - // Data is advertised as being updated every minute. Add a small - // margin to account for potential delays - const nextUpdate = lastUpdate + 65 * 1000; + const lastUpdate = new Date(res.headers["last-modified"]).getTime(); + const nextUpdate = lastUpdate + 65 * 1000; - callback(null, {lastUpdate, nextUpdate}); - processTamPassingStream(res.data, callback); - }).catch(err => callback(err)); + yield { lastUpdate, nextUpdate }; + + const parser = res.data.pipe(csv({ + delimiter: ";", + columns: header => header.map(snakeToCamelCase) + })); + + for await (const passing of parser) { + yield passing; + } }; exports.fetchRealtime = fetchRealtime; -const tamTheoreticalEndpoint = - 'http://data.montpellier3m.fr/node/10731/download'; -const tamTheoreticalFileName = 'offre_du_jour.csv'; +const theoreticalEndpoint = "http://data.montpellier3m.fr/node/10731/download"; /** * Fetch theoretical passings for the current day across the network. - * - * @param callback Called for each passing during parsing. First argument will - * be non-null only if an error occurred. Second argument will contain passings - * or be null if the end was reached. + * @yields {{{lastUpdate: number, nextUpdate: number}|Passing}} First value + * is an object containing the time of last update and the time of next + * update of this information. Next values are informations about each vehicle + * passing. */ -const fetchTheoretical = callback => -{ - axios.get(tamTheoreticalEndpoint, { - responseType: 'stream' - }).then(res => - { - const fileStream = res.data.pipe(unzip.Parse()); - - fileStream.on('entry', entry => - { - if (entry.type !== 'File' || entry.path !== tamTheoreticalFileName) - { - entry.autodrain(); - return; - } - - processTamPassingStream(entry, callback); - }); - - fileStream.on('error', err => callback(err)); +const fetchTheoretical = async function *() { + const res = await axios.get(theoreticalEndpoint, { + responseType: "stream" }); + + const lastUpdate = new Date(); + + if (lastUpdate.getHours() < 4) { + lastUpdate.setDate(lastUpdate.getDate() - 1); + } + + lastUpdate.setHours(4); + lastUpdate.setMinutes(0); + lastUpdate.setSeconds(0); + lastUpdate.setMilliseconds(0); + + const nextUpdate = new Date(lastUpdate); + + nextUpdate.setDate(nextUpdate.getDate() + 1); + + yield { + lastUpdate: lastUpdate.getTime(), + nextUpdate: nextUpdate.getTime() + }; + + const stream = await unzipFile(res.data, "offre_du_jour.csv"); + const parser = stream.pipe(csv({ + delimiter: ";", + columns: header => header.map(snakeToCamelCase) + })); + + for await (const passing of parser) { + yield passing; + } }; exports.fetchTheoretical = fetchTheoretical; diff --git a/src/util.js b/src/util.js index a03716f..aeceb8d 100644 --- a/src/util.js +++ b/src/util.js @@ -1,86 +1,65 @@ -/** - * Choose between singular or plural form based on the number of elements. - * - * @example - * > choosePlural(1, 'example', '.s') - * 'example' - * > choosePlural(4, 'example', '.s') - * 'examples' - * > choosePlural(0, 'radius', 'radii') - * 'radii' - * - * @param count Number of elements. - * @param singular Singular form. - * @param plural Plural form. An initial dot will be replaced by `singular`. - * @return Appropriate form. - */ -const choosePlural = (count, singular, plural) => -{ - if (count === 1) - { - return singular; - } - else - { - return plural.replace(/^\./, singular); - } -}; - -exports.choosePlural = choosePlural; +const unzip = require("unzip-stream"); /** - * Join elements with the given separator and a special separator for the last - * element. - * - * @example - * > joinSentence(['apple', 'orange', 'banana'], ', ', ' and ') - * 'apple, orange and banana' - * > joinSentence(['apple', 'banana'], ', ', ' and ') - * 'apple and banana' - * > joinSentence(['banana'], ', ', ' and ') - * 'banana' - * - * @param array Sequence of strings to join. - * @param separator Separator for all elements but the last one. - * @param lastSeparator Separator for the last element. - * @return Joined string. + * Convert a snake-cased string to a camel-cased one. + * @param {string} str Original string. + * @returns {string} Transformed string. */ -const joinSentence = (array, separator, lastSeparator) => -{ - if (array.length <= 2) - { - return array.join(lastSeparator); - } +const snakeToCamelCase = str => str.replace(/([-_][a-z])/gu, group => + group.toUpperCase().replace("-", "").replace("_", "")); - return ( - array.slice(0, -1).join(separator) - + lastSeparator - + array[array.length - 1] - ); -}; - -exports.joinSentence = joinSentence; +exports.snakeToCamelCase = snakeToCamelCase; /** * Check if a value is a JS object. - * - * @param value Value to check. - * @return True iff. `value` is a JS object. + * @param {*} value Value to check. + * @returns {boolean} Whether `value` is a JS object. */ -const isObject = value => value !== null && typeof value === 'object'; +const isObject = value => value !== null && typeof value === "object"; exports.isObject = isObject; /** * Check if two arrays are equal in a shallow manner. - * - * @param array1 First array. - * @param array2 Second array. - * @return True iff. the two arrays are equal. + * @param {Array} array1 First array. + * @param {Array} array2 Second array. + * @returns {boolean} Whether the two arrays are equal. */ const arraysEqual = (array1, array2) => ( - array1.length === array2.length - && array1.every((elt1, index) => elt1 === array2[index]) + array1.length === array2.length && + array1.every((elt1, index) => elt1 === array2[index]) ); exports.arraysEqual = arraysEqual; + +/** + * Find a file in a zipped stream and unzip it. + * @param {stream.Readable} data Input zipped stream. + * @param {string} fileName Name of the file to find. + * @returns {Promise.} Stream of the unzipped file. + */ +const unzipFile = (data, fileName) => new Promise((res, rej) => { + // eslint-disable-next-line new-cap + const stream = data.pipe(unzip.Parse()); + let found = false; + + stream.on("entry", entry => { + if (entry.type !== "File" || entry.path !== fileName) { + entry.autodrain(); + return; + } + + found = true; + res(entry); + }); + + stream.on("end", () => { + if (!found) { + rej(new Error(`File ${fileName} not found in archive`)); + } + }); + + stream.on("error", err => rej(err)); +}); + +exports.unzipFile = unzipFile;