diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 1173070..0000000 --- a/.eslintrc +++ /dev/null @@ -1,91 +0,0 @@ -# borrowed from https://github.com/nodejs/node/blob/master/.eslintrc -env: - node: true - mocha: true - es6: true - -rules: - # Possible Errors - # http://eslint.org/docs/rules/#possible-errors - comma-dangle: [2, "only-multiline"] - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: [2, "functions"] - no-extra-semi: 2 - no-func-assign: 2 - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-proto: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-typeof: 2 - - # Best Practices - # http://eslint.org/docs/rules/#best-practices - no-fallthrough: 2 - no-multi-spaces: 2 - no-octal: 2 - no-redeclare: 2 - no-self-assign: 2 - no-unused-labels: 2 - - # Strict Mode - # http://eslint.org/docs/rules/#strict-mode - strict: [2, "global"] - - # Variables - # http://eslint.org/docs/rules/#variables - no-delete-var: 2 - no-undef: 2 - no-unused-vars: [2, {"args": "none"}] - - # Node.js and CommonJS - # http://eslint.org/docs/rules/#nodejs-and-commonjs - no-mixed-requires: 2 - no-new-require: 2 - no-path-concat: 2 - no-restricted-modules: [2, "sys", "_linklist"] - - # Stylistic Issues - # http://eslint.org/docs/rules/#stylistic-issues - comma-spacing: 2 - eol-last: 2 - indent: [2, 2, {SwitchCase: 1}] - key-spacing: [2, {mode: "minimum"}] - keyword-spacing: 2 - linebreak-style: [2, "unix"] - max-len: [2, 120, 2] - new-parens: 2 - no-mixed-spaces-and-tabs: 2 - no-multiple-empty-lines: [2, {max: 2}] - no-trailing-spaces: 2 - quotes: [2, "single", "avoid-escape"] - semi: 2 - space-before-blocks: [2, "always"] - space-before-function-paren: [2, "never"] - space-in-parens: [2, "never"] - space-infix-ops: 2 - space-unary-ops: 2 - - # ECMAScript 6 - # http://eslint.org/docs/rules/#ecmascript-6 - arrow-parens: [2, "always"] - arrow-spacing: [2, {"before": true, "after": true}] - constructor-super: 2 - no-class-assign: 2 - no-confusing-arrow: 2 - no-const-assign: 2 - no-dupe-class-members: 2 - no-new-symbol: 2 - no-this-before-super: 2 - prefer-const: 2 - template-curly-spacing: 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79b2216..46a1710 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x, 22.x, 24.x] + node-version: [22.13.x, 24.x] steps: - uses: actions/checkout@v6 @@ -23,4 +23,5 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm i + - run: npm run lint - run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d45472a..b35e2b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: with: node-version: 24 - name: Install dependencies - run: npm i + run: npm install - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b5d5129..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: node_js -node_js: - - "6.9" - - "8.4" - - "10" -scripts: - - npm run lint - - npm test diff --git a/Dockerfile b/Dockerfile index adf1f37..e6afa88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM mhart/alpine-node:8 +FROM node:24-alpine -MAINTAINER Dmitry Shirokov +LABEL maintainer="Dmitry Shirokov " ADD package.json /tmp/package.json RUN cd /tmp && \ - npm install --production && \ + npm install --omit=dev && \ mkdir -p /opt/npm-proxy-cache && \ cp -a /tmp/node_modules /opt/npm-proxy-cache && \ mkdir -p /opt/npm-proxy-cache/cache diff --git a/README.md b/README.md index 309c12c..a5f0e53 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ npm --proxy http://npm-proxy-cache:8080 --https-proxy http://npm-proxy-cache:808 ## Limitations - - Works only with node `6` and above. + - Requires Node.js `22.13` or newer. ---- diff --git a/bin/npm-proxy-cache b/bin/npm-proxy-cache index d9a0632..7e2e092 100755 --- a/bin/npm-proxy-cache +++ b/bin/npm-proxy-cache @@ -1,27 +1,87 @@ #!/usr/bin/env node -var fs = require('fs'), - program = require('commander'); +const fs = require('fs'); +const { parseArgs } = require('node:util'); -var numeric = function (v) { - return parseInt(v, 10); +const packageInfo = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')); +const help = `Usage: npm-proxy-cache [options] + +Options: + -V, --version Output the version number + -h, --host Hostname (default: "localhost") + -p, --port Port (default: 8080) + -t, --ttl Cache lifetime in seconds (default: 1800) + -s, --storage Storage path + -x, --proxy
HTTP proxy to use + -e, --expired Use expired cache when the registry is unavailable + -f, --friendly-names Use actual file names instead of cache hashes + -v, --verbose Enable verbose logging + -n, --metadata-excluded Exclude metadata requests from caching + -l, --log-path Log path + -m, --internal-port Port for the internal HTTPS MITM server + --help Display help +`; + +let values; +try { + ({ values } = parseArgs({ + options: { + version: { type: 'boolean', short: 'V' }, + host: { type: 'string', short: 'h', default: 'localhost' }, + port: { type: 'string', short: 'p', default: '8080' }, + ttl: { type: 'string', short: 't', default: '1800' }, + storage: { type: 'string', short: 's', default: __dirname + '/../cache' }, + proxy: { type: 'string', short: 'x' }, + expired: { type: 'boolean', short: 'e' }, + 'friendly-names': { type: 'boolean', short: 'f' }, + verbose: { type: 'boolean', short: 'v' }, + 'metadata-excluded': { type: 'boolean', short: 'n' }, + 'log-path': { type: 'string', short: 'l' }, + 'internal-port': { type: 'string', short: 'm' }, + help: { type: 'boolean' } + }, + strict: true, + allowPositionals: false + })); +} catch (err) { + console.error(err.message); + console.error('Run npm-proxy-cache --help for usage.'); + process.exit(1); +} + +if (values.help) { + process.stdout.write(help); + process.exit(0); +} + +if (values.version) { + console.log(packageInfo.version); + process.exit(0); +} + +const opts = { + host: values.host, + port: integer(values.port, 'port'), + ttl: integer(values.ttl, 'ttl'), + storage: values.storage, + proxy: values.proxy, + expired: values.expired, + friendlyNames: values['friendly-names'], + verbose: values.verbose, + metadataExcluded: values['metadata-excluded'], + logPath: values['log-path'], + internalPort: values['internal-port'] === undefined + ? undefined + : integer(values['internal-port'], 'internal-port') }; -program - .version(JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version) - - .option('-h, --host [name]', 'Hostname [localhost]', 'localhost') - .option('-p, --port [number]', 'An integer argument [8080]', numeric, 8080) - .option('-t, --ttl [seconds]', 'Cache lifetime in seconds [1800]', numeric, 1800) - .option('-s, --storage [path]', 'Storage path', __dirname + '/../cache') - .option('-x, --proxy [address]', 'HTTP proxy to be used, e.g. http://user:pass@example.com:8888/') - .option('-e, --expired', 'Use expired cache when npm registry unavailable') - .option('-f, --friendly-names', 'Use actual file names instead of hashes in the cache') - .option('-v, --verbose', 'Verbose mode') - .option('-n, --metadata-excluded', 'Exclude metadata requests from caching') - .option('-l, --log-path [path]', 'Log path') - .option('-m, --internal-port [port]', - 'HTTPs port to use for internal proxying "MITM" server (mandatory on Windows systems)') - .parse(process.argv); - -require('../lib/proxy').powerup(program); +require('../lib/proxy').powerup(opts); + +function integer(value, name) { + const number = Number(value); + if (!/^\d+$/.test(value) || !Number.isSafeInteger(number)) { + console.error('Option --' + name + ' must be a non-negative integer.'); + process.exit(1); + } + return number; +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ae464a0 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,24 @@ +'use strict'; + +const js = require('@eslint/js'); +const globals = require('globals'); + +module.exports = [ + { + ignores: ['cache/**', 'node_modules/**'] + }, + js.configs.recommended, + { + files: ['**/*.js', 'bin/npm-proxy-cache'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'commonjs', + globals: Object.assign({}, globals.node, globals.mocha) + }, + rules: { + 'no-unused-vars': ['error', { args: 'none' }], + 'quotes': ['error', 'single', { avoidEscape: true }], + 'semi': ['error', 'always'] + } + } +]; diff --git a/lib/cache.js b/lib/cache.js index af7bbd7..1895865 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -3,14 +3,12 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const crypto = require('crypto'); -const mkdirp = require('mkdirp'); -const mv = require('mv'); +const { pipeline } = require('stream'); function Cache(opts) { this.opts = opts || {}; - this.opts.ttl = (opts.ttl || 1800) * 1000; - this.opts.friendlyNames = opts.friendlyNames; - this.opts.path = opts.path || path.join(__dirname, '/../cache'); + this.opts.ttl = (this.opts.ttl || 1800) * 1000; + this.opts.path = this.opts.path || path.join(__dirname, '/../cache'); this.locks = {}; const nop = function() {}; @@ -32,13 +30,9 @@ function Cache(opts) { this.meta = function(key, cb) { const self = this; const fullpath = this.getPath(key).full; - const stat = this.stat(fullpath); - - if (stat.status === Cache.NOT_FOUND || stat.status === Cache.EXPIRED) - return cb(null, stat); if (!this.locks[key]) - return cb(null, stat); + return cb(null, this.stat(fullpath)); // wait until lock releases // generally when file is locked means that process is writing to file right now @@ -54,13 +48,7 @@ function Cache(opts) { this.read = function(key) { const pathInfo = this.getPath(key); - const file = fs.createReadStream(pathInfo.full); - - file.on('finish', function() { - file.close(nop); - }); - - return file; + return fs.createReadStream(pathInfo.full); }; @@ -72,22 +60,24 @@ function Cache(opts) { // Create a lock locks[key] = true; - mkdirp.sync(pathInfo.dir, 511); // 511 is decimal equvivalent of 0777 + fs.mkdirSync(pathInfo.dir, { recursive: true, mode: 0o777 }); // On top of locking mechanism, doing write to a temp location, and // when it's finish moving the data file to its final destination. - const tmpPath = path.join(os.tmpdir(), pathInfo.file + '-' + Math.round(Math.random() * 1e9).toString(36)); + const tmpPath = path.join(os.tmpdir(), pathInfo.file + '-' + crypto.randomUUID()); const writeStream = fs.createWriteStream(tmpPath); - readStream.pipe(writeStream); - - writeStream.on('finish', function() { - writeStream.close(nop); - - // Release the lock, move data file to final destination. - delete (locks[key]); - mv(tmpPath, pathInfo.full, function(err) { - if (err) { return cb(err); } + pipeline(readStream, writeStream, function(err) { + if (err) { + delete (locks[key]); + fs.rm(tmpPath, { force: true }, nop); + return cb(err); + } + + move(tmpPath, pathInfo.full, function(err) { + delete (locks[key]); + if (err) + return cb(err); self.meta(key, cb); }); }); @@ -135,6 +125,21 @@ function Cache(opts) { } +function move(source, destination, cb) { + fs.rename(source, destination, function(err) { + if (!err) + return cb(); + if (err.code !== 'EXDEV') + return cb(err); + + fs.copyFile(source, destination, function(err) { + if (err) + return cb(err); + fs.unlink(source, cb); + }); + }); +} + Cache.NOT_FOUND = 0; Cache.EXPIRED = 2; Cache.FRESH = 4; diff --git a/lib/proxy.js b/lib/proxy.js index ab11786..7fb64d8 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -4,16 +4,12 @@ const net = require('net'); const https = require('https'); const fs = require('fs'); const os = require('os'); -const url = require('url'); const path = require('path'); -const request = require('request'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const Cache = require('./cache'); const getLogger = require('./logger'); -// To avoid 'DEPTH_ZERO_SELF_SIGNED_CERT' error on some setups -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - exports.log = null; exports.cache = null; @@ -29,6 +25,7 @@ const cycleCheckHeader = 'x-npm-proxy-cache-' + Math.round(Math.random() * 10000 exports.powerup = function(opts) { exports.opts = opts || {}; + opts = exports.opts; const options = { key: fs.readFileSync(path.join(__dirname, '/../cert/dummy.key'), 'utf8'), @@ -56,7 +53,7 @@ exports.powerup = function(opts) { mitmAddress = opts.internalPort; } else { - mitmAddress = path.join(os.tmpdir(), 'mitm.sock'); + mitmAddress = path.join(os.tmpdir(), 'npm-proxy-cache-mitm-' + process.pid + '.sock'); // Cleanup MITM socket for unix platforms if (fs.existsSync(mitmAddress)) @@ -68,7 +65,8 @@ exports.powerup = function(opts) { // start HTTP server with custom request handler callback function const server = http.createServer(exports.httpHandler).listen(opts.port, opts.host, function(err) { if (err) throw err; - exports.log.info('Listening on %s:%s [%d]', opts.host, opts.port, process.pid); + const address = server.address(); + exports.log.info('Listening on %s:%s [%d]', address.address, address.port, process.pid); }); // add handler for HTTPS (which issues a CONNECT to the proxy) @@ -81,9 +79,13 @@ exports.powerup = function(opts) { exports.httpHandler = function(req, res) { const cache = exports.cache, log = exports.log, - path = url.parse(req.url).path, - schema = req.client.pair || req.connection.encrypted ? 'https' : 'http', - dest = schema + '://' + req.headers['host'] + path; + dest = getDestination(req); + + if (!dest) { + res.writeHead(400); + res.end('A valid Host header is required.'); + return; + } if (req.headers[cycleCheckHeader]) { res.writeHead(502); @@ -93,7 +95,6 @@ exports.httpHandler = function(req, res) { const params = { headers: {}, - rejectUnauthorized: false, url: dest }; @@ -105,10 +106,6 @@ exports.httpHandler = function(req, res) { params.headers[name] = req.headers[name]; }); - if (exports.opts.proxy) - params.proxy = exports.opts.proxy; - - // Skipping other than GET methods // Skipping metadata requests when configured if ( @@ -133,17 +130,24 @@ exports.httpHandler = function(req, res) { const onResponse = function(err, response) { // don't save responses with codes other than 200 if (!err && response.statusCode === 200) { - cache.write(dest, r, function(err, meta) { - if (err) - throw err; + cache.write(dest, response, function(err, meta) { + if (err) { + log.error('Unable to cache response:', err); + res.writeHead(502); + res.end(err.toString()); + return; + } respondWithCache(dest, cache, meta, res); }); } else { // serve expired cache if user wants so - if (exports.opts.expired && meta.status === Cache.EXPIRED) + if (exports.opts.expired && meta.status === Cache.EXPIRED) { + if (response) + response.resume(); return respondWithCache(dest, cache, meta, res); + } log.error('An error occurred: "%s", status code "%s"', err ? err.message : 'Unknown', @@ -154,16 +158,15 @@ exports.httpHandler = function(req, res) { if (meta.status !== Cache.NOT_FOUND) cache.unlink(dest); + if (response) + response.resume(); + res.writeHead(502); res.end(err ? err.toString() : 'Status ' + response.statusCode + ' returned'); } }; - const r = request(params); - r.on('response', onResponse.bind(null, null)); - r.on('error', onResponse.bind(null)); - r.on('end', function() { - log.debug('end'); - }); + const upstream = requestUpstream(params, null, onResponse.bind(null, null)); + upstream.on('error', onResponse.bind(null)); }); }; @@ -220,40 +223,66 @@ exports.httpsHandler = function(request, socketRequest, bodyhead) { function bypass(req, res, params) { - const length = parseInt(req.headers['content-length']); const log = exports.log; + params.method = req.method; + params.headers = Object.assign({}, req.headers, params.headers); + delete params.headers['proxy-connection']; - const onEnd = function(params, res) { - return request(params) - .on('error', function(err) { - log.error('bypass', err); - }) - .pipe(res, { end: false }); - }; - - if (!isFinite(length)) { - onEnd(params, res); - return; - } + const upstream = requestUpstream(params, req, function(response) { + res.writeHead(response.statusCode, response.headers); + response.pipe(res); + }); + upstream.on('error', function(err) { + log.error('bypass', err); + if (!res.headersSent) + res.writeHead(502); + res.end(err.toString()); + }); +} - const raw = new Buffer(length); - let pointer = 0; +function requestUpstream(params, source, onResponse) { + const target = new URL(params.url); + const transport = target.protocol === 'https:' ? https : http; + const headers = {}; - req.on('data', function(chunk) { - chunk.copy(raw, pointer); - pointer += chunk.length; + Object.keys(params.headers).forEach(function(name) { + if (params.headers[name] !== undefined) + headers[name] = params.headers[name]; }); - req.on('end', function() { - params.method = req.method; - if (raw.length > 0) { - params.body = raw; - } - params.headers = { - 'Content-Type': req.headers['content-type'] - }; - onEnd(params, res); - }); + const options = { + protocol: target.protocol, + hostname: target.hostname, + port: target.port || undefined, + path: target.pathname + target.search, + method: params.method || 'GET', + headers: headers, + rejectUnauthorized: false + }; + + if (exports.opts.proxy) + options.agent = new HttpsProxyAgent(exports.opts.proxy); + + const upstream = transport.request(options, onResponse); + if (source) + source.pipe(upstream); + else + upstream.end(); + return upstream; +} + +function getDestination(req) { + try { + if (/^https?:\/\//i.test(req.url)) + return new URL(req.url).toString(); + if (!req.headers.host) + return null; + + const schema = req.socket.encrypted ? 'https' : 'http'; + return new URL(req.url, schema + '://' + req.headers.host).toString(); + } catch { + return null; + } } diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 80a3bc5..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1758 +0,0 @@ -{ - "name": "npm-proxy-cache", - "version": "2.2.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@gimenete/type-writer": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@gimenete/type-writer/-/type-writer-0.1.3.tgz", - "integrity": "sha512-vhpvVfM/fYqb1aAnkgOvtDKoOgU3ZYIvDnKSDAFSoBvallmGURMlHOE0/VG/gqunUZVXGCFBGHxI8swjBh+sIA==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "prettier": "^1.13.7" - } - }, - "@octokit/rest": { - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.10.0.tgz", - "integrity": "sha512-xZ4ejCZoqvKrIN3tQOKZlJ6nDQxaOdLcjRsamDnbckU7V5YTn2xheIqFXnQ2vLvxqVwyI8+2dfsODYbHxtwtSw==", - "dev": true, - "requires": { - "@gimenete/type-writer": "^0.1.3", - "before-after-hook": "^1.1.0", - "btoa-lite": "^1.0.0", - "debug": "^3.1.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.0", - "lodash": "^4.17.4", - "node-fetch": "^2.1.1", - "url-template": "^2.0.8" - } - }, - "acorn": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz", - "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==", - "dev": true - }, - "acorn-jsx": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", - "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", - "dev": true, - "requires": { - "acorn": "^5.0.3" - } - }, - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "before-after-hook": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.1.0.tgz", - "integrity": "sha512-VOMDtYPwLbIncTxNoSzRyvaMxtXmLWLUqr8k5AfC1BzLk34HvBXaQX8snOwQZ4c0aX8aSERqtJSiI9/m2u5kuA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", - "dev": true - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "circular-json": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.5.tgz", - "integrity": "sha512-13YaR6kiz0kBNmIVM87Io8Hp7bWOo4r61vkEANy8iH9R9bc6avud/1FT0SBpqR1RpIQADOh/Q+yHZDA1iL6ysA==" - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", - "dev": true, - "requires": { - "color-name": "1.1.1" - } - }, - "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", - "dev": true - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-format": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", - "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", - "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", - "dev": true, - "requires": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.2", - "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^4.0.3", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", - "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", - "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", - "dev": true, - "requires": { - "acorn": "^5.6.0", - "acorn-jsx": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - }, - "dependencies": { - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "github-publish-release": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/github-publish-release/-/github-publish-release-5.0.0.tgz", - "integrity": "sha512-LLE++vKy9mIUHJIln1xgmQoC33jgc0gjz9FujX0Pbl+L14feFYn18XBEBC5Ho4qmvsxROW9awShoO93uRPt0GA==", - "dev": true, - "requires": { - "@octokit/rest": "^15.10.0", - "lodash": "^4.17.10" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "dev": true, - "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "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 - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "log4js": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz", - "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==", - "requires": { - "circular-json": "^0.5.5", - "date-format": "^1.2.0", - "debug": "^3.1.0", - "rfdc": "^1.1.2", - "streamroller": "0.7.0" - } - }, - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" - }, - "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", - "requires": { - "mime-db": "~1.35.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "dependencies": { - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "requires": { - "glob": "^6.0.1" - } - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-fetch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", - "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz", - "integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "regexpp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rfdc": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", - "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==" - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", - "dev": true, - "requires": { - "symbol-observable": "1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "streamroller": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", - "requires": { - "date-format": "^1.2.0", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "readable-stream": "^2.3.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "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 - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, - "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", - "dev": true, - "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", - "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "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 - } - } - }, - "url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } - } -} diff --git a/package.json b/package.json index b846245..f5950c1 100644 --- a/package.json +++ b/package.json @@ -5,23 +5,19 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { - "commander": "^2.17.1", - "log4js": "^3.0.6", - "mkdirp": "^0.5.1", - "mv": "^2.1.1", - "request": "^2.88.0" + "https-proxy-agent": "^9.1.0", + "log4js": "^6.9.1" }, "description": "HTTP/HTTPS caching proxy for work with `npm` utility / registry", "main": "index.js", "devDependencies": { - "eslint": "^5.4.0", - "github-publish-release": "^5.0.0", - "mocha": "^5.2.0", - "rimraf": "^2.6.2" + "@eslint/js": "^10.0.1", + "eslint": "^10.8.0", + "globals": "^17.8.0", + "mocha": "^11.7.6" }, "scripts": { "lint": "scripts/lint", - "release": "scripts/release", "test": "scripts/test" }, "repository": { @@ -49,6 +45,14 @@ "url": "https://github.com/runk/npm-proxy-cache/issues" }, "engines": { - "node": ">=6" + "node": ">=22.13.0" + }, + "overrides": { + "mocha": { + "diff": "^8.0.3", + "glob": "^13.0.0", + "minimatch": "^10.2.0", + "serialize-javascript": "^7.0.5" + } } } diff --git a/scripts/lint b/scripts/lint index 03d7992..59eb8c3 100755 --- a/scripts/lint +++ b/scripts/lint @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Exit immediately if a pipeline returns non-zero status # http://www.gnu.org/software/bash/manual/bashref.html#The-Set-Builtin @@ -15,4 +15,4 @@ if [ -n "$npm_config_fix" ]; then FIX="--fix" fi -if [ "$LIST" ]; then eslint $FIX --config .eslintrc $LIST; fi +if [ "$LIST" ]; then eslint $FIX $LIST; fi diff --git a/scripts/release b/scripts/release deleted file mode 100755 index ceec696..0000000 --- a/scripts/release +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Exit immediately if a pipeline returns non-zero status -# http://www.gnu.org/software/bash/manual/bashref.html#The-Set-Builtin -set -e - -npm run lint -npm test - -if [ -n "$npm_config_major" ]; then SEMVER="major" -elif [ -n "$npm_config_patch" ]; then SEMVER="patch" -else SEMVER="minor"; fi - -npm version $SEMVER -github-publish-release -git push --follow-tags -npm publish diff --git a/scripts/test b/scripts/test index 87eb053..520216d 100755 --- a/scripts/test +++ b/scripts/test @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Exit immediately if a pipeline returns non-zero status # http://www.gnu.org/software/bash/manual/bashref.html#The-Set-Builtin diff --git a/test/cache.js b/test/cache.js index 7cf6d93..e883d7c 100644 --- a/test/cache.js +++ b/test/cache.js @@ -2,7 +2,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('fs'); -const rimraf = require('rimraf'); +const { PassThrough, Readable } = require('stream'); const Cache = require('../lib/cache'); describe('cache', function() { @@ -16,12 +16,12 @@ describe('cache', function() { opts = { path: filepath, ttl: 10 }; }); - before(function(done) { - rimraf(filepath, done); + before(function() { + fs.rmSync(filepath, { recursive: true, force: true }); }); - after(function(done) { - rimraf(filepath, done); + after(function() { + fs.rmSync(filepath, { recursive: true, force: true }); }); @@ -30,17 +30,23 @@ describe('cache', function() { const cache = new Cache(opts); assert(cache instanceof Cache); }); + + it('should use defaults when options are omitted', function() { + const cache = new Cache(); + assert.equal(cache.opts.ttl, 1800 * 1000); + assert.equal(cache.opts.friendlyNames, undefined); + }); }); describe('write()', function() { - const readStream = fs.createReadStream(path.join(__dirname, 'dummy.data')); - it('should write the file', function(done) { const cache = new Cache(opts); const key = '/-/foo/bar.dat'; const pathInfo = cache.getPath(key); + const readStream = fs.createReadStream(path.join(__dirname, 'dummy.data')); cache.write(key, readStream, function(err, meta) { + if (err) return done(err); assert.equal(meta.size, 31); assert.equal(meta.status, 4); assert.equal(fs.readFileSync(pathInfo.full, 'utf8'), dummy); @@ -62,6 +68,46 @@ describe('cache', function() { assert(cache.locks[key], 'Lock should be set'); assert(!fs.existsSync(pathInfo.full)); }); + + it('should release the lock when the input stream fails', function(done) { + const cache = new Cache(opts); + const key = '/-/foo/failure.dat'; + const readStream = new Readable({ + read: function() { + this.destroy(new Error('stream failed')); + } + }); + + cache.write(key, readStream, function(err) { + assert.match(err.message, /stream failed/); + assert(!cache.locks[key], 'Lock should be released after an error'); + assert(!fs.existsSync(cache.getPath(key).full)); + done(); + }); + }); + + it('should make metadata readers wait for an active write', function(done) { + const cache = new Cache(opts); + const key = '/-/foo/delayed.dat'; + const readStream = new PassThrough(); + let metaCalled = false; + + cache.write(key, readStream, function(err) { + if (err) return done(err); + }); + cache.meta(key, function(err, meta) { + if (err) return done(err); + metaCalled = true; + assert.equal(meta.status, Cache.FRESH); + assert.equal(meta.size, dummy.length); + done(); + }); + + setTimeout(function() { + assert.equal(metaCalled, false); + readStream.end(dummy); + }, 20); + }); }); diff --git a/test/cli.js b/test/cli.js new file mode 100644 index 0000000..b4fd9dc --- /dev/null +++ b/test/cli.js @@ -0,0 +1,33 @@ +'use strict'; +const assert = require('assert'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +describe('CLI', function() { + const cli = path.join(__dirname, '../bin/npm-proxy-cache'); + + it('should display help with the native argument parser', function() { + const result = spawnSync(process.execPath, [ + cli, + '--help' + ], { encoding: 'utf8' }); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /Usage: npm-proxy-cache \[options\]/); + assert.match(result.stdout, /--internal-port /); + }); + + it('should display the package version', function() { + const result = spawnSync(process.execPath, [cli, '--version'], { encoding: 'utf8' }); + + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stdout.trim(), require('../package.json').version); + }); + + it('should reject invalid numeric options', function() { + const result = spawnSync(process.execPath, [cli, '--port', 'invalid'], { encoding: 'utf8' }); + + assert.equal(result.status, 1); + assert.match(result.stderr, /--port must be a non-negative integer/); + }); +}); diff --git a/test/proxy.js b/test/proxy.js index ac9528f..ef92980 100644 --- a/test/proxy.js +++ b/test/proxy.js @@ -1,64 +1,238 @@ 'use strict'; const assert = require('assert'); +const fs = require('fs'); const http = require('http'); -const request = require('request'); - +const https = require('https'); +const os = require('os'); +const path = require('path'); +const { HttpsProxyAgent } = require('https-proxy-agent'); describe('proxy', function() { - - const MOCK_PORT = 5000; - const PROXY_PORT = 32403; const calledMock = {}; + let cachePath; + let mockPort; let proxy; + let proxyPort; + let secureMockPort; const server = http.createServer(function(req, res) { - calledMock[req.method + req.url] = calledMock[req.method + req.url] + 1 || 1; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ random: Math.random() })); + const key = req.method + req.url; + calledMock[key] = (calledMock[key] || 0) + 1; + + const chunks = []; + req.on('data', function(chunk) { + chunks.push(chunk); + }); + req.on('end', function() { + if (req.url === '/missing') { + res.writeHead(404); + res.end('missing'); + return; + } + + if (req.method === 'POST') { + res.writeHead(201, { + 'content-type': 'text/plain', + 'x-upstream': 'true' + }); + res.end(Buffer.concat(chunks)); + return; + } + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ random: Math.random() })); + }); + }); + const secureServer = https.createServer({ + key: fs.readFileSync(path.join(__dirname, '../cert/dummy.key')), + cert: fs.readFileSync(path.join(__dirname, '../cert/dummy.crt')) + }, function(req, res) { + calledMock[req.method + req.url] = (calledMock[req.method + req.url] || 0) + 1; + res.end('secure response'); }); - before(function(done) { - server.listen(MOCK_PORT, function(err) { - if (err) return done(err); + before(async function() { + cachePath = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-proxy-cache-test-')); + await Promise.all([listen(server), listen(secureServer)]); + mockPort = server.address().port; + secureMockPort = secureServer.address().port; - proxy = require('../lib/proxy').powerup({ - port: PROXY_PORT, - metadataExcluded: true, - ttl: 1800 - }); - done(); + proxy = require('../lib/proxy').powerup({ + host: '127.0.0.1', + port: 0, + storage: cachePath, + metadataExcluded: true, + ttl: 1800 }); + await Promise.all([ + listening(proxy.httpServer), + listening(proxy.httpsServer) + ]); + proxyPort = proxy.httpServer.address().port; }); - after(function() { - proxy.httpServer.close(); - proxy.httpsServer.close(); - server.close(); + after(async function() { + await Promise.all([ + close(proxy && proxy.httpServer), + close(proxy && proxy.httpsServer), + close(server), + close(secureServer) + ]); + fs.rmSync(cachePath, { recursive: true, force: true }); }); - function performNpmRequest(fn) { - request({ - baseUrl: 'http://127.0.0.1:' + PROXY_PORT, - uri: '/test', - headers: { - accept: 'application/json', - host: '127.0.0.1:' + MOCK_PORT - } - }, fn); - } + it('should not cache metadata requests', async function() { + const first = await performRequest({ + path: '/metadata', + headers: { accept: 'application/json' } + }); + const second = await performRequest({ + path: '/metadata', + headers: { accept: 'application/json' } + }); - it('should not cache metadata requests', function(done) { - performNpmRequest(function(err, res, resBody) { - if (err) return done(err); + assert.notEqual(second.body, first.body); + assert.equal(calledMock['GET/metadata'], 2); + }); - const originalBody = resBody; + it('should cache successful GET requests', async function() { + const first = await performRequest({ path: '/package.tgz' }); + const second = await performRequest({ path: '/package.tgz' }); - performNpmRequest(function(err, res, resBody) { - if (err) return done(err); - assert.notDeepEqual(resBody, originalBody); - assert.equal(calledMock['GET/test'], 2, 'Mock is not called twice'); - done(); - }); + assert.equal(second.body, first.body); + assert.equal(second.headers['x-cache-hit'], 'true'); + assert.equal(calledMock['GET/package.tgz'], 1); + }); + + it('should forward chunked request bodies and upstream response metadata', async function() { + const response = await performRequest({ + method: 'POST', + path: '/publish', + body: 'published body', + headers: { 'content-type': 'text/plain' } + }); + + assert.equal(response.statusCode, 201); + assert.equal(response.headers['x-upstream'], 'true'); + assert.equal(response.body, 'published body'); + assert.equal(calledMock['POST/publish'], 1); + }); + + it('should accept absolute-form proxy URLs', async function() { + const response = await performRequest({ + path: 'http://127.0.0.1:' + mockPort + '/absolute', + headers: { accept: 'application/json' } }); + + assert.equal(response.statusCode, 200); + assert.equal(calledMock['GET/absolute'], 1); + }); + + it('should proxy HTTPS CONNECT requests through the MITM server', async function() { + const response = await performHttpsRequest('/secure'); + + assert.equal(response.statusCode, 200); + assert.equal(response.body, 'secure response'); + assert.equal(calledMock['GET/secure'], 1); + }); + + it('should not cache non-successful responses', async function() { + const first = await performRequest({ path: '/missing' }); + const second = await performRequest({ path: '/missing' }); + + assert.match(first.body, /Status 404 returned/); + assert.match(second.body, /Status 404 returned/); + assert.equal(first.statusCode, 502); + assert.equal(second.statusCode, 502); + assert.equal(calledMock['GET/missing'], 2); }); + + function performRequest(options) { + return new Promise(function(resolve, reject) { + const headers = Object.assign({ + host: '127.0.0.1:' + mockPort + }, options.headers); + const req = http.request({ + hostname: '127.0.0.1', + port: proxyPort, + method: options.method || 'GET', + path: options.path, + headers: headers + }, function(res) { + const chunks = []; + res.on('data', function(chunk) { + chunks.push(chunk); + }); + res.on('end', function() { + resolve({ + body: Buffer.concat(chunks).toString(), + headers: res.headers, + statusCode: res.statusCode + }); + }); + }); + req.on('error', reject); + if (options.body) + req.write(options.body); + req.end(); + }); + } + + function performHttpsRequest(requestPath) { + return new Promise(function(resolve, reject) { + const agent = new HttpsProxyAgent('http://127.0.0.1:' + proxyPort); + const req = https.request({ + hostname: '127.0.0.1', + port: secureMockPort, + path: requestPath, + agent: agent, + rejectUnauthorized: false + }, function(res) { + const chunks = []; + res.on('data', function(chunk) { + chunks.push(chunk); + }); + res.on('end', function() { + resolve({ + body: Buffer.concat(chunks).toString(), + statusCode: res.statusCode + }); + }); + }); + req.on('error', reject); + req.end(); + }); + } }); + +function listen(server) { + return new Promise(function(resolve, reject) { + server.once('error', reject); + server.listen(0, '127.0.0.1', function() { + server.removeListener('error', reject); + resolve(); + }); + }); +} + +function listening(server) { + if (server.listening) + return Promise.resolve(); + return new Promise(function(resolve, reject) { + server.once('listening', resolve); + server.once('error', reject); + }); +} + +function close(server) { + if (!server || !server.listening) + return Promise.resolve(); + return new Promise(function(resolve, reject) { + server.close(function(err) { + if (err) + reject(err); + else + resolve(); + }); + }); +}