From f5666afa5102e05dad4b0f0eec2a6c60fad5c61f Mon Sep 17 00:00:00 2001 From: formidable Date: Wed, 19 Jul 2017 16:44:28 -0700 Subject: [PATCH 01/21] feat(#64) added multiple aggregate levels --- .vscode/launch.json | 37 + lib/config.js | 26 +- lib/dashboard.js | 10 +- lib/providers/metrics-provider.js | 458 +++++- lib/views/base-line-graph.js | 6 +- lib/views/help.js | 3 +- yarn.lock | 2222 +++++++++++++++++++++++++++++ 7 files changed, 2742 insertions(+), 20 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 yarn.lock diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0f68716 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach by Process ID", + "processId": "${command:PickProcess}", + "localRoot": "${workspaceRoot}" + }, + { + "type": "node", + "request": "launch", + "name": "Launch via NPM", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run-script", + "debug" + ], + "port": 5858 + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}/bin/nodejs-dashboard.js", + "args": [ + "node", + "test/app/index.js" + ], + "console": "internalConsole" + } + ] +} diff --git a/lib/config.js b/lib/config.js index 7abd799..0709997 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,6 +2,29 @@ var pkg = require("../package.json"); +/* eslint-disable no-magic-numbers */ + +// these define the time buckets that data will be aggregated against +// each number is in milliseconds + +// For example, 5000 = 5s and means that one of the aggregate levels +// will be at 5s increments of time + +// 300000 = 30s and the corresponding zoom will show 30s aggregates +var AGGREGATE_TIME_BUCKETS = [ + 5000, + 10000, + 15000, + 30000, + 60000, + 300000, + 600000, + 900000, + 1800000, + 3600000 +]; +/* eslint-enable no-magic-numbers */ + module.exports = { PORT: 9838, PORT_KEY: pkg.name + "_PORT", @@ -9,5 +32,6 @@ module.exports = { REFRESH_INTERVAL_KEY: pkg.name + "_REFRESH_INTERVAL", BLOCKED_THRESHOLD: 10, BLOCKED_THRESHOLD_KEY: pkg.name + "_BLOCKED_THRESHOLD", - LAYOUTS: "" + LAYOUTS: "", + AGGREGATE_TIME_BUCKETS: AGGREGATE_TIME_BUCKETS }; diff --git a/lib/dashboard.js b/lib/dashboard.js index ef5a493..a65ea4d 100644 --- a/lib/dashboard.js +++ b/lib/dashboard.js @@ -75,6 +75,12 @@ Dashboard.prototype._configureKeys = function () { this.screen.key(["q", "C-c"], function () { process.exit(0); // eslint-disable-line no-process-exit }); + + this.screen.key(["up", "down"], _.throttle(function (ch, key) { + var zoom = key.name === "down" ? -1 : 1; + this.screen.emit("zoomAggregate", zoom); + this._showLayout(this.currentLayout, true); + }.bind(this), THROTTLE_TIMEOUT)); }; Dashboard.prototype.onEvent = function (event) { @@ -94,8 +100,8 @@ var VIEW_MAP = { eventLoop: EventLoopView }; -Dashboard.prototype._showLayout = function (id) { - if (this.currentLayout === id) { +Dashboard.prototype._showLayout = function (id, forced) { + if (this.currentLayout === id && !forced) { return; } _.each(this.views, function (view) { diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 8cb8e84..fe7c3cf 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -1,25 +1,461 @@ "use strict"; var EventEmitter = require("events").EventEmitter; +var config = require("../config.js"); -var MetricsProvider = function MetricsProvider(screen) { - EventEmitter.call(this); +// time scaling factors +var timeScales = [ + { + units: "ms", + divisor: 1 + }, { + units: "s", + divisor: 1000 + }, { + units: "m", + divisor: 60 + }, { + units: "h", + divisor: 60 + }, { + units: "d", + divisor: 24 + }, { + units: "y", + divisor: 365.24 + } +]; - this._metrics = []; +/** + * Compute the logical array index for a given data point in time, relative + * to start, considering measure of time units. + * + * @param {Number} startTime + * The start of the process. + * + * @param {Number} metricTime + * The moment in time for this data point. + * + * @param {Number} aggregateTimeUnits + * The measure of units of time for scaling. + * + * @returns {Number} + * The logical array index is returned. + */ +var getAggregateTimeIndex = + function getAggregateTimeIndex(startTime, metricTime, aggregateTimeUnits) { + return Math.floor((metricTime - startTime) / aggregateTimeUnits); + }; - screen.on("metrics", this._onMetrics.bind(this)); -}; +/** + * Given a metric data object, construct an initialized aggregator. + * + * @param {Object} data + * The metric data received. + * + * @returns {Object} + * The initialized aggregator object is returned. + */ +var getInitializedAggregate = + function getInitializedAggregate(data) { + var aggregate = {}; + + for (var dataKey in data) { + aggregate[dataKey] = {}; + + for (var dataMetricKey in data[dataKey]) { + aggregate[dataKey][dataMetricKey] = { + values: [] + }; + } + } + + return aggregate; + }; + +/** + * Given a metric data template and an aggregator, average the data. + * + * @param {Object} data + * The metric data received. + * + * @param {Object} aggregate + * The aggregator object to aggregate. + * + * @returns {Object} + * The average of the aggregator object is returned. + */ +var getAveragedAggregate = + function getAveragedAggregate(data, aggregate) { + var averagedAggregate = {}; + + for (var dataKey in data) { + averagedAggregate[dataKey] = {}; + + for (var dataMetricKey in data[dataKey]) { + averagedAggregate[dataKey][dataMetricKey] = 0; + + // the empty aggregate is created with zero values + if (!aggregate) { + continue; + } + + // you can compute an average of a set of numbers two ways + // first, you can add all the numbers together and then divide by the count + // second, you call divide each number by the count and add the quotients + // the first method is more accurate, however you can overflow an accumulator + // and result with NaN + // the second method is employed here to ensure no overflows + for (var index = 0; index < aggregate[dataKey][dataMetricKey].values.length; index++) { + averagedAggregate[dataKey][dataMetricKey] += + aggregate[dataKey][dataMetricKey].values[index] / + aggregate[dataKey][dataMetricKey].values.length; + } + + // truncate the number to one decimal point + averagedAggregate[dataKey][dataMetricKey] = + +averagedAggregate[dataKey][dataMetricKey].toFixed(1); + } + } + + return averagedAggregate; + }; + +/** + * Given a metric data template, and a metric data point, accumulate into the + * aggregator. + * + * @param {Object} data + * The metric data template received. + * + * @param {Object} metric + * The metric data point received. + * + * @param {Object} aggregate + * The aggregator object. + * + * @returns {void} + */ +var accumulateAggregate = + function accumulateAggregate(data, metric, aggregate) { + for (var dataKey in data) { + for (var dataMetricKey in data[dataKey]) { + aggregate[dataKey][dataMetricKey].values.push(metric[dataKey][dataMetricKey]); + } + } + }; + +/** + * This is the constructor for the MetricsProvider + * + * @param {Object} screen + * The blessed screen object. + * + * @returns {void} + */ +var MetricsProvider = + function MetricsProvider(screen) { + EventEmitter.call(this); + + this._metrics = []; + // construct the aggregation container + this._aggregation = {}; + for (var index = 0; index < config.AGGREGATE_TIME_BUCKETS.length; index++) { + this._aggregation[config.AGGREGATE_TIME_BUCKETS[index]] = { + lastIndex: 0, + data: [] + }; + } + + this.aggregationLevels = Object.keys(this._aggregation); + this.currentAggregateZoom = -1; + this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; + this.minimumAggregationInterval = +this.aggregationLevels[0]; + + this._startTime = Date.now(); + this._lastAggregation = Date.now(); + + screen.on("metrics", this._onMetrics.bind(this)); + + screen.on("zoomAggregate", function zoomAggregate(zoom) { + // apply zoom delta and check for boundaries + this.currentAggregateZoom += zoom; + if (this.currentAggregateZoom < -1) { + this.currentAggregateZoom = -1; + } else if (this.currentAggregateZoom >= this.aggregationLevels.length - 1) { + this.currentAggregateZoom = this.aggregationLevels.length - 1; + } + + // now, reflect the change to the zoom level + this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; + + // if there is an aggregate at this level, but there is no data, go back a level + while ( + this.currentAggregateZoomKey + && this._aggregation[this.currentAggregateZoomKey].data.length === 0) { + this.currentAggregateZoom = Math.max(this.currentAggregateZoom - 1, -1); + this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; + } + }.bind(this)); + }; + +// MetricsProvider inherits from EventEmitter MetricsProvider.prototype = Object.create(EventEmitter.prototype); -MetricsProvider.prototype._onMetrics = function (data) { - this._metrics.push(data); +/** + * Perform event-driven aggregation at all configured units of time. + * + * @param {Number} currentTime + * The current time of the aggregation. + * + * @param {Object} data + * The metric data template received. + * + * @this MetricsProvider + * + * @returns {void} + */ +var aggregateMetrics = + function aggregateMetrics(currentTime, data) { + var aggregate; + var aggregateKey; + var metricIndex; + var lastTimeIndex; + var thisTimeIndex; - this.emit("metrics", data); -}; + /** + * Given the current time and last time index, add any missing logical + * time slots to have a complete picture of data. + * + * @this MetricsProvider + * + * @returns {void} + */ + var addMissingTimeSlots = + function addMissingTimeSlots() { + // compute the delta in the two logical time slots + var missingTimeSlots = thisTimeIndex - lastTimeIndex; + + // see if there is a gap in time + if (missingTimeSlots > 1) { + // add empty spots for missed time + while (--missingTimeSlots > 0) { + // populate these missing time slots with an empty aggregate + this._aggregation[aggregateKey].data.push(this.emptyAggregate); + + // emit to keep the display in sync + if (aggregateKey === this.currentAggregateZoomKey) { + this.emit("metrics", this.emptyAggregate); + } + } + } + }; + + /** + * Add a new average aggregate to the aggregate container. + * + * @this MetricsProvider + * + * @returns {void} + */ + var addAverageAggregate = + function addAverageAggregate() { + // average the aggregate data + var averagedAggregate = getAveragedAggregate(data, aggregate); + + // add the aggregate + this._aggregation[aggregateKey].data.push(averagedAggregate); + + // when in aggregate mode and the aggregate just produced is in the current view + // emit the average now + if (aggregateKey === this.currentAggregateZoomKey) { + this.emit("metrics", averagedAggregate); + } + + // construct an initialized aggregate + aggregate = getInitializedAggregate(data); + }; + + /** + * Process one row of metric data into aggregate. + * + * @this MetricsProvider + * + * @returns {void} + */ + var processRow = function processRow() { + // compute the time index of the aggregate + thisTimeIndex = + getAggregateTimeIndex( + this._startTime, + this._metrics[metricIndex].__currentTime, + +aggregateKey + ); + + // when the time index changes, average the data accumulated thus far + if (thisTimeIndex !== lastTimeIndex) { + // except for the first one + if (lastTimeIndex !== undefined) { + // add in any missing logical time slots + addMissingTimeSlots.call(this); + + // add a new averaged aggregate to the aggregate structure + addAverageAggregate.call(this); + + // remember where we stopped + this._aggregation[aggregateKey].lastIndex = metricIndex; + } + + // level-break + lastTimeIndex = thisTimeIndex; + } + + // accumulate the data + accumulateAggregate(data, this._metrics[metricIndex], aggregate); + }; + + // iterate over the configured aggregation time buckets + for (aggregateKey in this._aggregation) { + // construct an initialized aggregate + aggregate = getInitializedAggregate(data); + + // iterate through metrics, beginning where we left off + for ( + metricIndex = this._aggregation[aggregateKey].lastIndex; + metricIndex < this._metrics.length; + metricIndex++ + ) { + processRow.call(this); + } + + // reset for the next value + lastTimeIndex = undefined; + } + }; + +/** + * When metrics are received collect, aggregate, and emit them. + * + * @param {Object} data + * The metrics data received. + * + * @returns {void} + */ +MetricsProvider.prototype._onMetrics = + function _onMetrics(data) { + var currentTime = Date.now(); + + // attach the current clock reading to the metrics + this._metrics.push(Object.assign({ __currentTime: currentTime }, data)); + + // one time, build an empty aggregate - used for missing time slots + if (!this.emptyAggregate) { + this.emptyAggregate = getAveragedAggregate(data); + } + + // see if it is time once again to aggregate some data + if (currentTime - this._lastAggregation >= this.minimumAggregationInterval) { + aggregateMetrics.call(this, currentTime, data); + + // aggregation is complete; save this position in time + this._lastAggregation = currentTime; + } + + // if we are showing aggregate data, that is emitted in the aggregateMetrics + // function; otherwise, emit the newly received metric data here + if (!this.currentAggregateZoomKey) { + this.emit("metrics", data); + } + }; + +/** + * Provide all the metrics desired, up to the limit. + * + * @param {Number} limit + * The limit of the metrics to return. + * + * @returns {Number[]} + * The array of metrics is returned. + */ +MetricsProvider.prototype.getMetrics = + function getMetrics(limit) { + var metrics; + + if (this.currentAggregateZoomKey) { + metrics = this._aggregation[this.currentAggregateZoomKey].data.slice(-limit); + } else { + metrics = this._metrics.slice(-limit); + } + + if (metrics.length === 0 && this.emptyAggregate) { + metrics.push(this.emptyAggregate); + } + + return metrics; + }; + +/** + * Given a time index and unit of time measure, scale the value for screen real estate. + * + * @param {Number} timeIndex + * The logical index of time. + * + * @param {Number} aggregateTimeUnits + * The unit of time measure. + * + * @returns {String} + * A scaled, string-representation of time at the index is returned. + */ +var getTimeIndexScale = + function getTimeIndexScale(timeIndex, aggregateTimeUnits) { + var timeValue = timeIndex * aggregateTimeUnits; + var units; + + if (timeIndex === 0) { + return "0s"; + } + + for (var scaleIndex = 0; scaleIndex < timeScales.length; scaleIndex++) { + if (timeValue >= timeScales[scaleIndex].divisor) { + timeValue /= timeScales[scaleIndex].divisor; + units = timeScales[scaleIndex].units; + } else { + break; + } + } + + if (timeValue !== Math.floor(timeValue)) { + return timeValue.toFixed(1) + units; + } + + return timeValue + units; + }; + +/** + * Return the X-Axis for the metrics. + * + * @param {Number} limit + * The limit of the X-Axis size. + * + * @returns {String[]} + * The X-Axis labels array is returned. + */ +MetricsProvider.prototype.getXAxis = function getXAxis(limit) { + var timeIndex; + var xAxis = []; + + if (this.currentAggregateZoomKey) { + for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { + xAxis.push(getTimeIndexScale(timeIndex, +this.currentAggregateZoomKey)); + } + } else { + for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { + xAxis.push(timeIndex + "s"); + } + } -MetricsProvider.prototype.getMetrics = function (limit) { - return this._metrics.slice(-limit); + return xAxis; }; module.exports = MetricsProvider; diff --git a/lib/views/base-line-graph.js b/lib/views/base-line-graph.js index 2faf9bf..f13cfbe 100644 --- a/lib/views/base-line-graph.js +++ b/lib/views/base-line-graph.js @@ -21,7 +21,7 @@ var BaseLineGraph = function BaseLineGraph(options) { this.limit = this.layoutConfig.limit; this.seriesOptions = options.series; - var xAxis = this._getXAxis(); + var xAxis = this.metricsProvider.getXAxis(this.layoutConfig.limit); this.series = _.mapValues(options.series, function (seriesConfig) { if (seriesConfig.highwater && !seriesConfig.color) { seriesConfig.color = "red"; @@ -87,10 +87,6 @@ BaseLineGraph.prototype._updateLabel = function () { this.node.setLabel(util.format("%s%s ", this.label, seriesLabels)); }; -BaseLineGraph.prototype._getXAxis = function () { - return _.reverse(_.times(this.limit, String)); -}; - BaseLineGraph.prototype._createGraph = function (options) { this.node = contrib.line({ label: this.label, diff --git a/lib/views/help.js b/lib/views/help.js index bd5befb..0158767 100644 --- a/lib/views/help.js +++ b/lib/views/help.js @@ -9,6 +9,7 @@ var HelpView = function HelpView(options) { "{center}{bold}keybindings{/bold}{/center}", "", "{cyan-fg} left, right{/} rotate through layouts", + "{cyan-fg} up, down{/} increase / decrease graph units of time", "{cyan-fg} esc{/} close popup window / return to default layout", "{cyan-fg} h, ?{/} toggle this window", "{cyan-fg} ctrl-c, q{/} quit", @@ -22,7 +23,7 @@ var HelpView = function HelpView(options) { left: "center", // using fixed numbers to support use of alignment tags width: 64, - height: 10 + height: 11 }, border: "line", padding: { diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..1327853 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2222 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + +accepts@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + dependencies: + mime-types "~2.1.11" + negotiator "0.6.1" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" + +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0, ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-term@>=0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/ansi-term/-/ansi-term-0.0.2.tgz#fd753efa4beada0eac99981bc52a3f6ff019deb7" + dependencies: + x256 ">=0.0.1" + +ansicolors@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +arraybuffer.slice@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + +async@1.x, async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-eslint@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-6.1.2.tgz#5293419fe3672d66598d327da9694567ba6a5f2f" + dependencies: + babel-traverse "^6.0.20" + babel-types "^6.0.19" + babylon "^6.0.18" + lodash.assign "^4.0.0" + lodash.pickby "^4.0.0" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-runtime@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-traverse@^6.0.20: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + babylon "^6.17.2" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.0.19, babel-types@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.0.18, babylon@^6.17.2: + version "6.17.4" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" + +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64id@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + +blessed-contrib@^3.5.5: + version "3.5.5" + resolved "https://registry.yarnpkg.com/blessed-contrib/-/blessed-contrib-3.5.5.tgz#85b295e30363054797dcd3a7bf47a4e5bbdfe38c" + dependencies: + ansi-term ">=0.0.2" + chalk "^1.1.0" + drawille-canvas-blessed-contrib ">=0.1.3" + lodash ">=3.0.0" + map-canvas ">=0.1.5" + marked "^0.3.3" + marked-terminal "^1.5.0" + memory-streams "^0.1.0" + memorystream "^0.3.1" + picture-tube "0.0.4" + request "^2.53.0" + sparkline "^0.1.1" + strip-ansi "^3.0.0" + term-canvas "0.0.5" + x256 ">=0.0.1" + +blessed@^0.1.81: + version "0.1.81" + resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" + +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + +blocked@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blocked/-/blocked-1.2.1.tgz#e22efe767863c65ab8197f6252929104e1ec9ce2" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +bresenham@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/bresenham/-/bresenham-0.0.3.tgz#abdab9e5b194e27c757cd314d8444314f299877a" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +caller-id@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-id/-/caller-id-0.1.0.tgz#59bdac0893d12c3871408279231f97458364f07b" + dependencies: + stack-trace "~0.0.7" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +cardinal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-1.0.0.tgz#50e21c1b0aa37729f9377def196b5a9cec932ee9" + dependencies: + ansicolors "~0.2.1" + redeyed "~1.0.0" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chai@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + 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" + +charm@~0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" + +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + +cli-cursor@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + dependencies: + colors "1.0.3" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" + +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.4.6: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + +debug@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" + dependencies: + ms "0.7.2" + +debug@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" + dependencies: + ms "0.7.2" + +debug@^2.1.1, debug@^2.2.0: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + 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@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +diff@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" + +doctrine@1.3.x: + version "1.3.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.3.0.tgz#13e75682b55518424276f7c173783456ef913d26" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^1.2.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +drawille-blessed-contrib@>=0.0.1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/drawille-blessed-contrib/-/drawille-blessed-contrib-1.0.0.tgz#15c27934f57a0056ad13596e1561637bc941f0b7" + +drawille-canvas-blessed-contrib@>=0.0.1, drawille-canvas-blessed-contrib@>=0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/drawille-canvas-blessed-contrib/-/drawille-canvas-blessed-contrib-0.1.3.tgz#212f078a722bfd2ecc267ea86ab6dddc1081fd48" + dependencies: + ansi-term ">=0.0.2" + bresenham "0.0.3" + drawille-blessed-contrib ">=0.0.1" + gl-matrix "^2.1.0" + x256 ">=0.0.1" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +engine.io-client@~1.8.4: + version "1.8.4" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.4.tgz#9fe85dee25853ca6babe25bd2ad68710863e91c2" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "2.3.3" + engine.io-parser "1.3.2" + has-cors "1.1.0" + indexof "0.0.1" + parsejson "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + ws "1.1.2" + xmlhttprequest-ssl "1.5.3" + yeast "0.1.2" + +engine.io-parser@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a" + dependencies: + after "0.8.2" + arraybuffer.slice "0.0.6" + base64-arraybuffer "0.1.5" + blob "0.0.4" + has-binary "0.1.7" + wtf-8 "1.0.0" + +engine.io@~1.8.4: + version "1.8.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.4.tgz#77bce12b80e5d60429337fec3b0daf691ebc9003" + dependencies: + accepts "1.3.3" + base64id "1.0.0" + cookie "0.3.1" + debug "2.3.3" + engine.io-parser "1.3.2" + ws "1.1.4" + +es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.24" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" + dependencies: + es6-iterator "2" + es6-symbol "~3.1" + +es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-symbol "^3.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@^0.1.4, es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.8.x: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-config-formidable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-formidable/-/eslint-config-formidable-2.0.1.tgz#995b8f4a15d80109fbfbe409e537686dfaf41fc2" + +eslint-import-resolver-node@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c" + dependencies: + debug "^2.2.0" + object-assign "^4.0.1" + resolve "^1.1.6" + +eslint-plugin-filenames@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-filenames/-/eslint-plugin-filenames-1.2.0.tgz#aee9c1c90189c95d2e49902c160eceefecd99f53" + dependencies: + lodash.camelcase "4.3.0" + lodash.kebabcase "4.1.1" + lodash.snakecase "4.1.1" + lodash.upperfirst "4.3.1" + +eslint-plugin-import@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-1.16.0.tgz#b2fa07ebcc53504d0f2a4477582ec8bff1871b9f" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.2.0" + doctrine "1.3.x" + es6-map "^0.1.3" + es6-set "^0.1.4" + eslint-import-resolver-node "^0.2.0" + has "^1.0.1" + lodash.cond "^4.3.0" + lodash.endswith "^4.0.1" + lodash.find "^4.3.0" + lodash.findindex "^4.3.0" + minimatch "^3.0.3" + object-assign "^4.0.1" + pkg-dir "^1.0.0" + pkg-up "^1.0.0" + +eslint@^2.10.2: + version "2.13.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" + dependencies: + chalk "^1.1.3" + concat-stream "^1.4.6" + debug "^2.1.1" + doctrine "^1.2.2" + es6-map "^0.1.3" + escope "^3.6.0" + espree "^3.1.6" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^1.1.1" + glob "^7.0.3" + globals "^9.2.0" + ignore "^3.1.2" + imurmurhash "^0.1.4" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" + is-resolvable "^1.0.0" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" + levn "^0.3.0" + lodash "^4.0.0" + mkdirp "^0.5.0" + optionator "^0.8.1" + path-is-absolute "^1.0.0" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.6.0" + strip-json-comments "~1.0.1" + table "^3.7.8" + text-table "~0.2.0" + user-home "^2.0.0" + +espree@^3.1.6: + version "3.4.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + dependencies: + acorn "^5.0.1" + acorn-jsx "^3.0.0" + +esprima@2.7.x, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esprima@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + +event-stream@~0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-0.9.8.tgz#5da9cf3c7900975989db5a68c28e5b3c98ebe03a" + dependencies: + optimist "0.2" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + +extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +file-entry-cache@^1.1.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + dependencies: + samsam "~1.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.0.2, function-bind@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +gl-matrix@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-2.3.2.tgz#aac808c74af7d5db05fe04cb60ca1a0fcb174d74" + +glob@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.3, glob@^7.0.5: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + 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" + +globals@^9.0.0, globals@^9.2.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + 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" + +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +growl@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" + +handlebars@^4.0.1: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-binary@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c" + dependencies: + isarray "0.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +here@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/here/-/here-0.0.2.tgz#69c1af3f02121f3d8788e02e84dc8b3905d71195" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +ignore@^3.1.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + figures "^1.3.5" + lodash "^4.3.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + +invariant@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-my-json-valid@^2.10.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@3.x, js-yaml@^3.5.1: + version "3.9.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +jsonschema@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.1.1.tgz#3cede8e3e411d377872eefbc9fdf26383cbc3ed9" + +jsprim@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + dependencies: + assert-plus "1.0.0" + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash._baseassign@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" + dependencies: + lodash._basecopy "^3.0.0" + lodash.keys "^3.0.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basecreate@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash.assign@^4.0.0, lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + +lodash.camelcase@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + +lodash.create@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" + dependencies: + lodash._baseassign "^3.0.0" + lodash._basecreate "^3.0.0" + lodash._isiterateecall "^3.0.0" + +lodash.endswith@^4.0.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + +lodash.find@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + +lodash.findindex@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.kebabcase@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.pickby@^4.0.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" + +lodash.snakecase@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + +lodash.upperfirst@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" + +lodash@>=3.0.0, lodash@^4.0.0, lodash@^4.16.2, lodash@^4.2.0, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +map-canvas@>=0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/map-canvas/-/map-canvas-0.1.5.tgz#8be6bade0bf3e9f9a8b56e8836a1d1d133cab186" + dependencies: + drawille-canvas-blessed-contrib ">=0.0.1" + xml2js "^0.4.5" + +marked-terminal@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-1.7.0.tgz#c8c460881c772c7604b64367007ee5f77f125904" + dependencies: + cardinal "^1.0.0" + chalk "^1.1.3" + cli-table "^0.3.1" + lodash.assign "^4.2.0" + node-emoji "^1.4.1" + +marked@^0.3.3: + version "0.3.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" + +memory-streams@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/memory-streams/-/memory-streams-0.1.2.tgz#273ff777ab60fec599b116355255282cca2c50c2" + dependencies: + readable-stream "~1.0.2" + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.7: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^3.1.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594" + dependencies: + browser-stdout "1.3.0" + commander "2.9.0" + debug "2.6.0" + diff "3.2.0" + escape-string-regexp "1.0.5" + glob "7.1.1" + growl "1.9.2" + json3 "3.3.2" + lodash.create "3.1.1" + mkdirp "0.5.1" + supports-color "3.1.2" + +mock-require@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-2.0.2.tgz#1eaa71aad23013773d127dc7e91a3fbb4837d60d" + dependencies: + caller-id "^0.1.0" + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +node-emoji@^1.4.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.7.0.tgz#a400490aac409b616d13941532200f128af037f9" + dependencies: + lodash.toarray "^4.4.0" + string.prototype.codepointat "^0.2.0" + +nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +nopt@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-2.1.2.tgz#6cccd977b80132a07731d6e8ce58c2c8303cf9af" + dependencies: + abbrev "1" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + +object-keys@^1.0.10, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + +once@1.x, once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + +optimist@0.2: + version "0.2.8" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.2.8.tgz#e981ab7e268b457948593b55674c099a815cac31" + dependencies: + wordwrap ">=0.0.1 <0.1.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optimist@~0.3.4: + version "0.3.7" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" + dependencies: + wordwrap "~0.0.2" + +optionator@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + 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" + +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +parsejson@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" + dependencies: + better-assert "~1.0.0" + +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +picture-tube@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/picture-tube/-/picture-tube-0.0.4.tgz#21de02b7179ccb73af083f112f5267632fc6e8c6" + dependencies: + buffers "~0.1.1" + charm "~0.1.0" + event-stream "~0.9.8" + optimist "~0.3.4" + png-js "~0.1.0" + request "~2.9.202" + x256 "~0.0.1" + +pidusage@^1.0.8: + version "1.1.5" + resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.1.5.tgz#b8c8d32bdfaf36212ca9e741028876ea33217e66" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-up@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26" + dependencies: + find-up "^1.0.0" + +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" + +png-js@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/png-js/-/png-js-0.1.1.tgz#1cc7c212303acabe74263ec3ac78009580242d93" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +pretty-bytes@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf" + dependencies: + number-is-nan "^1.0.0" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +readable-stream@^2.2.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@~1.0.2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + +redeyed@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" + dependencies: + esprima "~3.0.0" + +regenerator-runtime@^0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +request@^2.53.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@~2.9.202: + version "2.9.203" + resolved "https://registry.yarnpkg.com/request/-/request-2.9.203.tgz#6c1711a5407fb94a114219563e44145bcbf4723a" + +require-uncached@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.1.x: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.1.6: + version "1.3.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@^2.2.8: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" + dependencies: + once "^1.3.0" + +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" + +safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +samsam@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + +samsam@~1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +shelljs@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" + +sinon-chai@^2.8.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.11.0.tgz#93d90f989fff67ce45767077ffe575dde1faea6d" + +sinon@^1.17.6: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +socket.io-adapter@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" + dependencies: + debug "2.3.3" + socket.io-parser "2.3.1" + +socket.io-client@1.7.4, socket.io-client@^1.4.8: + version "1.7.4" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.4.tgz#ec9f820356ed99ef6d357f0756d648717bdd4281" + dependencies: + backo2 "1.0.2" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "2.3.3" + engine.io-client "~1.8.4" + has-binary "0.1.7" + indexof "0.0.1" + object-component "0.0.3" + parseuri "0.0.5" + socket.io-parser "2.3.1" + to-array "0.1.4" + +socket.io-parser@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" + dependencies: + component-emitter "1.1.2" + debug "2.2.0" + isarray "0.0.1" + json3 "3.3.2" + +socket.io@^1.4.8: + version "1.7.4" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.4.tgz#2f7ecedc3391bf2d5c73e291fe233e6e34d4dd00" + dependencies: + debug "2.3.3" + engine.io "~1.8.4" + has-binary "0.1.7" + object-assign "4.1.0" + socket.io-adapter "0.5.0" + socket.io-client "1.7.4" + socket.io-parser "2.3.1" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +sparkline@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/sparkline/-/sparkline-0.1.2.tgz#c3bde46252b1354e710c4b200d54816bd9f07a32" + dependencies: + here "0.0.2" + nopt "~2.1.2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stack-trace@~0.0.7: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string.prototype.codepointat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" + +supports-color@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" + dependencies: + has-flag "^1.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + +term-canvas@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/term-canvas/-/term-canvas-0.0.5.tgz#597afac2fa6369a6f17860bce9c5f66d6ea0ca96" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + +to-fast-properties@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + dependencies: + os-homedir "^1.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +"util@>=0.10.3 <1": + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +which@^1.1.1, which@^1.2.9: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + dependencies: + isexe "^2.0.0" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +"wordwrap@>=0.0.1 <0.1.0", wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@^1.0.0, wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +ws@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +ws@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +wtf-8@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" + +x256@>=0.0.1, x256@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/x256/-/x256-0.0.2.tgz#c9af18876f7a175801d564fe70ad9e8317784934" + +xml2js@^0.4.5: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + +xmlhttprequest-ssl@1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" + +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" From 22bc27a0f629221f8a5c0f80d5b0902d5918ec07 Mon Sep 17 00:00:00 2001 From: "Michael-P\\Michael P. Scott" Date: Thu, 20 Jul 2017 14:45:52 -0700 Subject: [PATCH 02/21] fix(#64) remove vscode file first, then gitignore it --- .vscode/launch.json | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 0f68716..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - // Use IntelliSense to learn about possible Node.js debug attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "Attach by Process ID", - "processId": "${command:PickProcess}", - "localRoot": "${workspaceRoot}" - }, - { - "type": "node", - "request": "launch", - "name": "Launch via NPM", - "runtimeExecutable": "npm", - "runtimeArgs": [ - "run-script", - "debug" - ], - "port": 5858 - }, - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceRoot}/bin/nodejs-dashboard.js", - "args": [ - "node", - "test/app/index.js" - ], - "console": "internalConsole" - } - ] -} From bdd3adfe80c66d2a4f402c643368331dbb351a6f Mon Sep 17 00:00:00 2001 From: "Michael-P\\Michael P. Scott" Date: Thu, 20 Jul 2017 14:47:24 -0700 Subject: [PATCH 03/21] fix(#64) gitignore vs code --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2993f19..3641346 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules npm-debug.log coverage +.vscode From e74e99215c29a6fa3243db8df613f0c0330082dc Mon Sep 17 00:00:00 2001 From: formidable Date: Fri, 21 Jul 2017 21:35:53 -0700 Subject: [PATCH 04/21] test(#64) added unit tests for MetricsProvider Also, corrected the use of sinon-chai and fixed a cross-platform issue with mock-require. Included gulp and gulpfile for unit test debugging. New constants.js file for configuration. Lots of performance improvements and code cleanup in MetricsProvider aggregation logic. Lots of documentation for MetricsProvider. --- gulpfile.js | 9 + lib/config.js | 26 +- lib/constants.js | 52 ++ lib/dashboard.js | 2 +- lib/providers/metrics-provider.js | 563 +++++++----- package.json | 2 + test/lib/dashboard-agent.spec.js | 34 +- test/lib/generate-layouts.spec.js | 11 +- test/lib/providers/metrics-provider.spec.js | 224 ++++- test/lib/views/base-line-graph.spec.js | 12 +- test/lib/views/base-view.spec.js | 5 +- test/lib/views/cpu-view.spec.js | 5 +- test/lib/views/eventloop-view.spec.js | 5 +- test/lib/views/memory-gauge-view.spec.js | 8 +- test/lib/views/stream-view.spec.js | 6 +- yarn.lock | 903 +++++++++++++++++++- 16 files changed, 1565 insertions(+), 302 deletions(-) create mode 100644 gulpfile.js create mode 100644 lib/constants.js diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..b184256 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,9 @@ +"use strict"; + +var gulp = require("gulp"); +var mocha = require("gulp-mocha"); + +gulp.task("test-debug", function () { + gulp.src(["test/**/*.spec.js"]) + .pipe(mocha()); +}); diff --git a/lib/config.js b/lib/config.js index 0709997..7abd799 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,29 +2,6 @@ var pkg = require("../package.json"); -/* eslint-disable no-magic-numbers */ - -// these define the time buckets that data will be aggregated against -// each number is in milliseconds - -// For example, 5000 = 5s and means that one of the aggregate levels -// will be at 5s increments of time - -// 300000 = 30s and the corresponding zoom will show 30s aggregates -var AGGREGATE_TIME_BUCKETS = [ - 5000, - 10000, - 15000, - 30000, - 60000, - 300000, - 600000, - 900000, - 1800000, - 3600000 -]; -/* eslint-enable no-magic-numbers */ - module.exports = { PORT: 9838, PORT_KEY: pkg.name + "_PORT", @@ -32,6 +9,5 @@ module.exports = { REFRESH_INTERVAL_KEY: pkg.name + "_REFRESH_INTERVAL", BLOCKED_THRESHOLD: 10, BLOCKED_THRESHOLD_KEY: pkg.name + "_BLOCKED_THRESHOLD", - LAYOUTS: "", - AGGREGATE_TIME_BUCKETS: AGGREGATE_TIME_BUCKETS + LAYOUTS: "" }; diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 0000000..a8383b8 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,52 @@ +"use strict"; + +// these define the time levels that data will be aggregated into +// each number is in milliseconds + +// since these are used as object keys, they are strings + +// For example, 5000 = 5s and means that one of the aggregate levels +// will be at 5s increments of time + +// 300000 = 30s and the corresponding zoom will show 30s aggregates +var AGGREGATE_TIME_LEVELS = [ + "5000", + "10000", + "15000", + "30000", + "60000", + "300000", + "600000", + "900000", + "1800000", + "3600000" +]; + +// this array object is used to reduce ms to its highest human-readable form +// see lib/providers/metrics-provider.js::getTimeIndexLabel +var TIME_SCALES = [ + { + units: "ms", + divisor: 1 + }, { + units: "s", + divisor: 1000 + }, { + units: "m", + divisor: 60 + }, { + units: "h", + divisor: 60 + }, { + units: "d", + divisor: 24 + }, { + units: "y", + divisor: 365.24 + } +]; + +module.exports = { + AGGREGATE_TIME_LEVELS: AGGREGATE_TIME_LEVELS, + TIME_SCALES: TIME_SCALES +}; diff --git a/lib/dashboard.js b/lib/dashboard.js index a65ea4d..6431316 100644 --- a/lib/dashboard.js +++ b/lib/dashboard.js @@ -78,7 +78,7 @@ Dashboard.prototype._configureKeys = function () { this.screen.key(["up", "down"], _.throttle(function (ch, key) { var zoom = key.name === "down" ? -1 : 1; - this.screen.emit("zoomAggregate", zoom); + this.screen.emit("zoomGraphs", zoom); this._showLayout(this.currentLayout, true); }.bind(this), THROTTLE_TIMEOUT)); }; diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index fe7c3cf..170fcd8 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -1,149 +1,99 @@ "use strict"; -var EventEmitter = require("events").EventEmitter; -var config = require("../config.js"); - -// time scaling factors -var timeScales = [ - { - units: "ms", - divisor: 1 - }, { - units: "s", - divisor: 1000 - }, { - units: "m", - divisor: 60 - }, { - units: "h", - divisor: 60 - }, { - units: "d", - divisor: 24 - }, { - units: "y", - divisor: 365.24 - } -]; - /** - * Compute the logical array index for a given data point in time, relative - * to start, considering measure of time units. + * This module provides metric data for various data points. + * The data provided is streamed in two modes: real time and aggregate. * - * @param {Number} startTime - * The start of the process. + * The real time data is simply a slice of the data that has been collected + * over time. * - * @param {Number} metricTime - * The moment in time for this data point. + * The aggregate data is data that has been rolled-up to various time increments. * - * @param {Number} aggregateTimeUnits - * The measure of units of time for scaling. + * The strategy for aggregation centers around time indexes. Imagine an array + * of values and each element of that array being a data point. If the data + * is real-time, then each index is just one logical second. When the data + * is aggregated, each data point is the average of the data, for one logical + * grouping of time. * - * @returns {Number} - * The logical array index is returned. - */ -var getAggregateTimeIndex = - function getAggregateTimeIndex(startTime, metricTime, aggregateTimeUnits) { - return Math.floor((metricTime - startTime) / aggregateTimeUnits); - }; - -/** - * Given a metric data object, construct an initialized aggregator. + * To determine which data elements from the real-time array are to be grouped, + * the difference in time from the start of the process and the moment a data + * point was captured is passed through this formula: * - * @param {Object} data - * The metric data received. + * Time Index = Math.floor((Data Point Time - Process Start Time) / Time Units) * - * @returns {Object} - * The initialized aggregator object is returned. - */ -var getInitializedAggregate = - function getInitializedAggregate(data) { - var aggregate = {}; - - for (var dataKey in data) { - aggregate[dataKey] = {}; - - for (var dataMetricKey in data[dataKey]) { - aggregate[dataKey][dataMetricKey] = { - values: [] - }; - } - } - - return aggregate; - }; - -/** - * Given a metric data template and an aggregator, average the data. + * All Times are expressed in milliseconds (ms) and are obtained from Date.now(). * - * @param {Object} data - * The metric data received. + * Example: Process Start Time 8798172749871 + * Data Point Time 8798172756481 + * Delta 6610 + * Time Units 5000 + * Quotient 1.322 + * Floor 1 * - * @param {Object} aggregate - * The aggregator object to aggregate. + * The above example demonstrates that for a 5000ms (5s) aggregate, the time index + * for the data point is one. This formula is applied to all data points, so + * when many data points share the same logical time index, they can be averaged. + * When two or more array elements have a common time index, they form a time band. * - * @returns {Object} - * The average of the aggregator object is returned. - */ -var getAveragedAggregate = - function getAveragedAggregate(data, aggregate) { - var averagedAggregate = {}; - - for (var dataKey in data) { - averagedAggregate[dataKey] = {}; - - for (var dataMetricKey in data[dataKey]) { - averagedAggregate[dataKey][dataMetricKey] = 0; - - // the empty aggregate is created with zero values - if (!aggregate) { - continue; - } - - // you can compute an average of a set of numbers two ways - // first, you can add all the numbers together and then divide by the count - // second, you call divide each number by the count and add the quotients - // the first method is more accurate, however you can overflow an accumulator - // and result with NaN - // the second method is employed here to ensure no overflows - for (var index = 0; index < aggregate[dataKey][dataMetricKey].values.length; index++) { - averagedAggregate[dataKey][dataMetricKey] += - aggregate[dataKey][dataMetricKey].values[index] / - aggregate[dataKey][dataMetricKey].values.length; - } - - // truncate the number to one decimal point - averagedAggregate[dataKey][dataMetricKey] = - +averagedAggregate[dataKey][dataMetricKey].toFixed(1); - } - } - - return averagedAggregate; - }; - -/** - * Given a metric data template, and a metric data point, accumulate into the - * aggregator. + * To efficiently perform the average, care is taken to reduce the number of + * CPU cycles required to analyze the data. To do this, any calculation that is + * done on a data point (such as its time index) is stored. This is necessary + * because a given data point can be considered for aggregation into any number + * of configured time aggregations. * - * @param {Object} data - * The metric data template received. + * Also, to prevent re-aggregating the data needlessly, each aggregate level + * maintains its position in the base array, thereby keeping track of where it + * left off. This is done using two simple numbers: the last array element + * examined and the start of the current time band. * - * @param {Object} metric - * The metric data point received. + * So that data is still streamed on an event-basis, an aggregate data point is + * only captured and emitted when it is complete. To detect when an aggregate + * is complete, the algorithm traverses the base real-time array of data, beginning + * where it left off for any given aggregate. Walking each element from there + * forward, a simplistic level-break algorithm is implemented. As and when a + * logical time index changes, this indicates that the previous set of data is + * complete. * - * @param {Object} aggregate - * The aggregator object. + * Once this occurs, the program then averages the data from the starting index + * through the ending index that spans a logical time unit, regardless of aggregate + * level (using the time index formula above). * - * @returns {void} + * Image a set of tuples: (physical index, logical time index, value): + * + * [ 0,0,1.3 ], [ 1,0,4.5 ], [ 2,0,3.7], [3,1,4], [4,2,5.6], [5,2,0.3], [6,5,9.1] + * + * elements[0..2] have the same time index and when element[3] is encountered, a + * change in time index is detected. In state variables, the program knows to re-read + * elements[0..2] and perform the average (shown below). State variables update to + * reflect this new end point where aggregation has been completed for that aggregate + * level. + * + * The program continues and at the end of reading the data above, the average + * would be: + * + * [ 3.2, 4, 2.9 ] + * + * The final tuple would not be processed because nothing came after it. But when it + * did get processed, the program would also include 0-averages for logical time + * elements 3 and 4. When a gap is detected between two logical time elements, + * the program replaces them with averages equal to zero. + * + * To ensure no overflows (and therefore NaN) are produced, the average is done + * using the formula on the left-side of this equivalence: + * + * k[0] k[1] k[2] k[n] sum([k0...kn]) + * ------ + ------ + ------ + ... + ------ = ---------------- = avg([k0...kn]) + * n - 1 n - 1 n - 1 n - 1 n - 1 + * + * The sum of memory usage even over a 10s aggregate is enough to produce NaN. */ -var accumulateAggregate = - function accumulateAggregate(data, metric, aggregate) { - for (var dataKey in data) { - for (var dataMetricKey in data[dataKey]) { - aggregate[dataKey][dataMetricKey].values.push(metric[dataKey][dataMetricKey]); - } - } - }; + +var EventEmitter = require("events").EventEmitter; +var _ = require("lodash"); + +// get the defined aggregation levels +var AGGREGATE_TIME_LEVELS = require("../constants.js").AGGREGATE_TIME_LEVELS; +var TIME_SCALES = require("../constants.js").TIME_SCALES; /** * This is the constructor for the MetricsProvider @@ -155,47 +105,67 @@ var accumulateAggregate = */ var MetricsProvider = function MetricsProvider(screen) { - EventEmitter.call(this); - this._metrics = []; + /** + * Setup the process to aggregate the data as and when it is necessary. + * + * @this MetricsProvider + * + * @returns {void} + */ + var setupAggregation = + function setupAggregation() { + // construct the aggregation container + this._aggregation = {}; + for (var index = 0; index < AGGREGATE_TIME_LEVELS.length; index++) { + this._aggregation[AGGREGATE_TIME_LEVELS[index]] = { + data: [], + lastIndex: 0, + nextAggregateIndex: 0 + }; + } + + // an array of all available aggregation levels and metadata + this.aggregationLevels = Object.keys(this._aggregation); + this.minimumAggregationInterval = +this.aggregationLevels[0]; + this.highestAggregationKey = this.aggregationLevels.slice(-1)[0]; + + // this is an exploit; arrays are zero-based so -1 index will be undefined + // that is intentional - when the zoomLevelKey is undefined, we use real-time + this.zoomLevel = -1; + this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; - // construct the aggregation container - this._aggregation = {}; - for (var index = 0; index < config.AGGREGATE_TIME_BUCKETS.length; index++) { - this._aggregation[config.AGGREGATE_TIME_BUCKETS[index]] = { - lastIndex: 0, - data: [] + // remember when all this started + this._startTime = Date.now(); + this._lastAggregation = this._startTime; }; - } - this.aggregationLevels = Object.keys(this._aggregation); - this.currentAggregateZoom = -1; - this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; - this.minimumAggregationInterval = +this.aggregationLevels[0]; + EventEmitter.call(this); - this._startTime = Date.now(); - this._lastAggregation = Date.now(); + // the low-level container of all metrics provided + this._metrics = []; + // setup for aggregation + setupAggregation.call(this); + + // metrics data receiver callback handler screen.on("metrics", this._onMetrics.bind(this)); - screen.on("zoomAggregate", function zoomAggregate(zoom) { - // apply zoom delta and check for boundaries - this.currentAggregateZoom += zoom; - if (this.currentAggregateZoom < -1) { - this.currentAggregateZoom = -1; - } else if (this.currentAggregateZoom >= this.aggregationLevels.length - 1) { - this.currentAggregateZoom = this.aggregationLevels.length - 1; - } + // zoom callback handler + screen.on("zoomGraphs", function zoomGraphs(zoom) { + // apply zoom delta while staying in boundaries + this.zoomLevel = + _.clamp(this.zoomLevel + zoom, -1, this.aggregationLevels.length - 1); // now, reflect the change to the zoom level - this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; + this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; // if there is an aggregate at this level, but there is no data, go back a level while ( - this.currentAggregateZoomKey - && this._aggregation[this.currentAggregateZoomKey].data.length === 0) { - this.currentAggregateZoom = Math.max(this.currentAggregateZoom - 1, -1); - this.currentAggregateZoomKey = this.aggregationLevels[this.currentAggregateZoom]; + this.zoomLevelKey + && this._aggregation[this.zoomLevelKey].data.length === 0) { + this.zoomLevel = Math.max(this.zoomLevel - 1, -1); + this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; } }.bind(this)); }; @@ -203,13 +173,37 @@ var MetricsProvider = // MetricsProvider inherits from EventEmitter MetricsProvider.prototype = Object.create(EventEmitter.prototype); +/** + * Given a metric data object, construct an initialized average. + * + * @param {Object} data + * The metric data received. + * + * @returns {Object} + * The initialized average object is returned. + */ +var getInitializedAverage = + function getInitializedAverage(data) { + var average = {}; + + for (var dataKey in data) { + average[dataKey] = {}; + + for (var dataMetricKey in data[dataKey]) { + average[dataKey][dataMetricKey] = 0; + } + } + + return average; + }; + /** * Perform event-driven aggregation at all configured units of time. * * @param {Number} currentTime * The current time of the aggregation. * - * @param {Object} data + * @param {Object} metricData * The metric data template received. * * @this MetricsProvider @@ -217,123 +211,225 @@ MetricsProvider.prototype = Object.create(EventEmitter.prototype); * @returns {void} */ var aggregateMetrics = - function aggregateMetrics(currentTime, data) { - var aggregate; + function aggregateMetrics(currentTime, metricData) { var aggregateKey; - var metricIndex; var lastTimeIndex; - var thisTimeIndex; /** * Given the current time and last time index, add any missing logical * time slots to have a complete picture of data. * + * @param {Number} currentTimeIndex + * The time index currently being processed. + * + * @param {Number} previousTimeIndex + * The time index previously processed. + * * @this MetricsProvider * * @returns {void} */ var addMissingTimeSlots = - function addMissingTimeSlots() { + function addMissingTimeSlots(currentTimeIndex, previousTimeIndex) { // compute the delta in the two logical time slots - var missingTimeSlots = thisTimeIndex - lastTimeIndex; + var missingTimeSlots = currentTimeIndex - previousTimeIndex; // see if there is a gap in time if (missingTimeSlots > 1) { // add empty spots for missed time while (--missingTimeSlots > 0) { // populate these missing time slots with an empty aggregate - this._aggregation[aggregateKey].data.push(this.emptyAggregate); + this._aggregation[aggregateKey].data.push(this.emptyAverage); // emit to keep the display in sync - if (aggregateKey === this.currentAggregateZoomKey) { - this.emit("metrics", this.emptyAggregate); + if (aggregateKey === this.zoomLevelKey) { + this.emit("metrics", this.emptyAverage); } } } }; /** - * Add a new average aggregate to the aggregate container. + * After having detected a new sampling, aggregate the relevant data points * - * @this MetricsProvider + * @param {Object[]} rows + * The array reference. + * + * @param {Number} startIndex + * The starting index to derive an average. + * + * @param {Number} endIndex + * The ending index to derive an average. * * @returns {void} */ - var addAverageAggregate = - function addAverageAggregate() { - // average the aggregate data - var averagedAggregate = getAveragedAggregate(data, aggregate); - - // add the aggregate - this._aggregation[aggregateKey].data.push(averagedAggregate); - - // when in aggregate mode and the aggregate just produced is in the current view - // emit the average now - if (aggregateKey === this.currentAggregateZoomKey) { - this.emit("metrics", averagedAggregate); + var getAveragedAggregate = + function getAveragedAggregate(rows, startIndex, endIndex) { + var averagedAggregate = getInitializedAverage(metricData); + + // this is the number of elements we will aggregate + var aggregateCount = endIndex - startIndex + 1; + + // you can compute an average of a set of numbers two ways + // first, you can add all the numbers together and then divide by the count + // second, you call divide each number by the count and add the quotients + // the first method is more accurate, however you can overflow an accumulator + // and result with NaN + // the second method is employed here to ensure no overflows + + for (var dataKey in metricData) { + for (var dataMetricKey in metricData[dataKey]) { + for (var rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) { + averagedAggregate[dataKey][dataMetricKey] += + rows[rowIndex][dataKey][dataMetricKey] / aggregateCount; + } + + // after the average is done, truncate the averages to one decimal point + averagedAggregate[dataKey][dataMetricKey] = + +averagedAggregate[dataKey][dataMetricKey].toFixed(1); + } } - // construct an initialized aggregate - aggregate = getInitializedAggregate(data); + return averagedAggregate; + }; + + /** + * Reclaim no longer necessary metadata storage space. Once the highest + * aggregation level configured has been computed, the metadata for all + * those elements are no longer necessary and be reclaimed. + * + * @param {Object[]} rows + * The array reference. + * + * @param {Number} startIndex + * The start of the array where metadata can be discarded. + * + * @param {Number} endIndex + * The end of the array where metadata can be discarded. + * + * @returns {void} + */ + var reclaimMetadataStorage = + function reclaimMetadataStorage(rows, startIndex, endIndex) { + for (var rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) { + delete rows[rowIndex].__timeIndices; + } }; /** * Process one row of metric data into aggregate. * + * @param {Object} row + * The row being processed. + * + * @param {Number} rowIndex + * The index of the row being processed. + * + * @param {Object[]} rows + * The array reference. + * * @this MetricsProvider * * @returns {void} */ - var processRow = function processRow() { - // compute the time index of the aggregate - thisTimeIndex = - getAggregateTimeIndex( - this._startTime, - this._metrics[metricIndex].__currentTime, - +aggregateKey - ); - - // when the time index changes, average the data accumulated thus far + var processRow = function processRow(row, rowIndex, rows) { + var averagedAggregate; + + // get the time index of the aggregate (this is precomputed metadata) + var thisTimeIndex = row.__timeIndices[aggregateKey]; + + // when the time index changes, average the data for the time band if (thisTimeIndex !== lastTimeIndex) { // except for the first one if (lastTimeIndex !== undefined) { // add in any missing logical time slots - addMissingTimeSlots.call(this); + addMissingTimeSlots.call(this, thisTimeIndex, lastTimeIndex); + + // get the average across the discovered time band + averagedAggregate = getAveragedAggregate( + rows, + this._aggregation[aggregateKey].nextAggregateIndex, + rowIndex - 1 + ); + + // add the average + this._aggregation[aggregateKey].data.push(averagedAggregate); + + // when in aggregate mode and the aggregate just produced is in the current view + // emit the average now + if (aggregateKey === this.zoomLevelKey) { + this.emit("metrics", averagedAggregate); + } - // add a new averaged aggregate to the aggregate structure - addAverageAggregate.call(this); + // we can reclaim some storage now since the time metadata is not needed anymore + if (aggregateKey === this.highestAggregationKey) { + reclaimMetadataStorage( + rows, + this._aggregation[aggregateKey].nextAggregateIndex, + rowIndex - 1 + ); + } - // remember where we stopped - this._aggregation[aggregateKey].lastIndex = metricIndex; + // now we know where the next aggregate begins + this._aggregation[aggregateKey].nextAggregateIndex = rowIndex; } // level-break lastTimeIndex = thisTimeIndex; } - - // accumulate the data - accumulateAggregate(data, this._metrics[metricIndex], aggregate); }; // iterate over the configured aggregation time buckets for (aggregateKey in this._aggregation) { - // construct an initialized aggregate - aggregate = getInitializedAggregate(data); - // iterate through metrics, beginning where we left off for ( - metricIndex = this._aggregation[aggregateKey].lastIndex; - metricIndex < this._metrics.length; - metricIndex++ + var rowIndex = this._aggregation[aggregateKey].lastIndex; + rowIndex < this._metrics.length; + rowIndex++ ) { - processRow.call(this); + processRow.call(this, this._metrics[rowIndex], rowIndex, this._metrics); } + // remember where we will begin again for this aggregate level + this._aggregation[aggregateKey].lastIndex = this._metrics.length; + // reset for the next value lastTimeIndex = undefined; } }; +/** + * Given a metric data point and a point in time, apply time metadata. + * + * @param {Object} metric + * The metric data point received. + * + * @param {Number} currentTime + * The current moment in time. + * + * @this MetricsProvider + * + * @returns {Object} + * The metric data point object is returned with time metadata applied. + */ +var applyMetricTimeMetadata = + function applyMetricTimeMetadata(metric, currentTime) { + var metadata = { + __timeIndices: {} + }; + + // for all the aggregation levels, stamp each metric with its logical time indices + for (var aggregateKey in this._aggregation) { + // take the difference of now and start then divide by units of time and + // drop any remainder; this is the logical time index for that unit of measure + metadata.__timeIndices[aggregateKey] = + Math.floor((currentTime - this._startTime) / +aggregateKey); + } + + // now apply the metric data to the time metadata + return Object.assign(metadata, metric); + }; + /** * When metrics are received collect, aggregate, and emit them. * @@ -344,14 +440,15 @@ var aggregateMetrics = */ MetricsProvider.prototype._onMetrics = function _onMetrics(data) { + // get the current moment in time var currentTime = Date.now(); - // attach the current clock reading to the metrics - this._metrics.push(Object.assign({ __currentTime: currentTime }, data)); + // attach the current clock reading and metedata to the metrics + this._metrics.push(applyMetricTimeMetadata.call(this, data, currentTime)); - // one time, build an empty aggregate - used for missing time slots - if (!this.emptyAggregate) { - this.emptyAggregate = getAveragedAggregate(data); + // one time, build an empty average - used for missing time slots + if (!this.emptyAverage) { + this.emptyAverage = getInitializedAverage(data); } // see if it is time once again to aggregate some data @@ -364,7 +461,7 @@ MetricsProvider.prototype._onMetrics = // if we are showing aggregate data, that is emitted in the aggregateMetrics // function; otherwise, emit the newly received metric data here - if (!this.currentAggregateZoomKey) { + if (!this.zoomLevelKey) { this.emit("metrics", data); } }; @@ -382,21 +479,17 @@ MetricsProvider.prototype.getMetrics = function getMetrics(limit) { var metrics; - if (this.currentAggregateZoomKey) { - metrics = this._aggregation[this.currentAggregateZoomKey].data.slice(-limit); + if (this.zoomLevelKey) { + metrics = this._aggregation[this.zoomLevelKey].data.slice(-limit); } else { metrics = this._metrics.slice(-limit); } - if (metrics.length === 0 && this.emptyAggregate) { - metrics.push(this.emptyAggregate); - } - return metrics; }; /** - * Given a time index and unit of time measure, scale the value for screen real estate. + * Given a time index and unit of time measure, compute a condensed, human-readable label. * * @param {Number} timeIndex * The logical index of time. @@ -407,29 +500,29 @@ MetricsProvider.prototype.getMetrics = * @returns {String} * A scaled, string-representation of time at the index is returned. */ -var getTimeIndexScale = - function getTimeIndexScale(timeIndex, aggregateTimeUnits) { +var getTimeIndexLabel = + function getTimeIndexLabel(timeIndex, aggregateTimeUnits) { + var scaleIndex = 0; var timeValue = timeIndex * aggregateTimeUnits; - var units; if (timeIndex === 0) { return "0s"; } - for (var scaleIndex = 0; scaleIndex < timeScales.length; scaleIndex++) { - if (timeValue >= timeScales[scaleIndex].divisor) { - timeValue /= timeScales[scaleIndex].divisor; - units = timeScales[scaleIndex].units; - } else { - break; - } + // progressively reduce by units of time + while (scaleIndex < TIME_SCALES.length && timeValue >= TIME_SCALES[scaleIndex].divisor) { + timeValue /= TIME_SCALES[scaleIndex++].divisor; } + // convert to one decimal point, if any if (timeValue !== Math.floor(timeValue)) { - return timeValue.toFixed(1) + units; + return timeValue.toFixed(1) + TIME_SCALES[scaleIndex - 1].units; } - return timeValue + units; + // return the label (ex: 2.4m) + // it is noteworthy that I did create the Clash of Clans style label too + // (ex 2m 24s = 2.4m but it didn't look that hot when it was on the screen) + return timeValue + TIME_SCALES[scaleIndex - 1].units; }; /** @@ -445,9 +538,9 @@ MetricsProvider.prototype.getXAxis = function getXAxis(limit) { var timeIndex; var xAxis = []; - if (this.currentAggregateZoomKey) { + if (this.zoomLevelKey) { for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { - xAxis.push(getTimeIndexScale(timeIndex, +this.currentAggregateZoomKey)); + xAxis.push(getTimeIndexLabel(timeIndex, +this.zoomLevelKey)); } } else { for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { diff --git a/package.json b/package.json index 548bb95..6b850c5 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "eslint-config-formidable": "^2.0.1", "eslint-plugin-filenames": "^1.1.0", "eslint-plugin-import": "^1.16.0", + "gulp": "^3.9.1", + "gulp-mocha": "=3.0.1", "istanbul": "^0.4.5", "mocha": "^3.1.1", "mock-require": "^2.0.1", diff --git a/test/lib/dashboard-agent.spec.js b/test/lib/dashboard-agent.spec.js index e879e82..48f746e 100644 --- a/test/lib/dashboard-agent.spec.js +++ b/test/lib/dashboard-agent.spec.js @@ -1,3 +1,5 @@ +/* eslint-disable no-magic-numbers */ + "use strict"; var expect = require("chai").expect; @@ -64,18 +66,38 @@ describe("dashboard-agent", function () { var checkMetrics = function (metrics) { expect(metrics).to.be.an("object"); - expect(metrics.eventLoop).to.deep.equal({ delay: 0, high: 0 }); - expect(metrics.mem.systemTotal).to.be.above(0); - expect(metrics.mem.rss).to.be.above(0); - expect(metrics.mem.heapTotal).to.be.above(0); - expect(metrics.mem.heapUsed).to.be.above(0); - expect(metrics.cpu.utilization).to.be.above(0); + expect(metrics.eventLoop.delay).to.be.a("number"); + expect(metrics.eventLoop.high).to.be.a("number"); + expect(metrics.mem.systemTotal).to.equal(20); + expect(metrics.mem.rss).to.equal(30); + expect(metrics.mem.heapTotal).to.equal(40); + expect(metrics.mem.heapUsed).to.equal(50); + expect(metrics.cpu.utilization).to.equal(60); }; + var stubProcess = sinon.stub(process, "memoryUsage", function () { + return { + systemTotal: 20, + rss: 30, + heapTotal: 40, + heapUsed: 50 + }; + }); + + var stubPUsage = sinon.stub(pusage, "stat", function (processId, callback) { + expect(processId).to.equal(process.pid); + expect(callback).to.be.a("function"); + + callback(null, { cpu: 60 }); + }); + agent._getStats(function (err, metrics) { tryCatch(done, function () { expect(err).to.be.null; checkMetrics(metrics); + + stubProcess.restore(); + stubPUsage.restore(); }); }); }); diff --git a/test/lib/generate-layouts.spec.js b/test/lib/generate-layouts.spec.js index 73b8407..a37d391 100644 --- a/test/lib/generate-layouts.spec.js +++ b/test/lib/generate-layouts.spec.js @@ -5,8 +5,13 @@ var expect = require("chai").expect; // Strict mode leads to odd bug in Node < 0.12 var mockRequire = require("mock-require"); +// to ensure cross-platform consistency with mixed Posix & Win32 paths, use normalize() +// ideally, this would be taken care of as a fix for mockRequire. +// see https://github.com/boblauer/mock-require/issues/20 +var normalize = require("path").normalize; + var mock = function (path, obj) { - return mockRequire(process.cwd() + "/" + path, obj); + return mockRequire(normalize(process.cwd() + "/" + path), obj); }; var generateLayouts = require("../../lib/generate-layouts"); @@ -17,7 +22,7 @@ var parent = { }; describe("generate-layouts", function () { - beforeEach(function () { + beforeEach(function (done) { mock("fake/empty-layout", []); mock("fake/invalid-config-layout", { invalid: "config" }); mock("fake/fill-view-layout", [[ @@ -149,6 +154,8 @@ describe("generate-layouts", function () { ] } ]]); + + done(); }); it("should validate default layout", function () { diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 44ada60..0dd3abe 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -1,36 +1,242 @@ +/* eslint-disable max-statements,no-magic-numbers,max-nested-callbacks */ + "use strict"; var expect = require("chai").expect; var sinon = require("sinon"); var _ = require("lodash"); +var AGGREGATE_TIME_LEVELS = require("../../../lib/constants.js").AGGREGATE_TIME_LEVELS; + var utils = require("../../utils"); var MetricsProvider = require("../../../lib/providers/metrics-provider"); describe("MetricsProvider", function () { - var sandbox; var testContainer; var metricsProvider; - before(function () { + var stubNow; + var mockStart = 1000000; + var mockNow = 1000000; + var mockTimeInterval = 2500; + + var metricsRequiredToAchieveHighestAggregation = + +AGGREGATE_TIME_LEVELS.slice(-1)[0] / mockTimeInterval; + + var mockMetrics = []; + var mockMetricCount; + + before(function (done) { sandbox = sinon.sandbox.create(); + + // generate some fake metrics for processing + mockMetricCount = + Math.ceil(Math.random() * 500 + metricsRequiredToAchieveHighestAggregation); + + for (var index = 0; index < mockMetricCount; index++) { + mockMetrics.push({ + metricA: { + valueA: Math.random() * 100 + }, + metricB: { + valueA: Math.random() * 100, + valueB: Math.random() * 100 + } + }); + } + + done(); }); - beforeEach(function () { + after(function (done) { + done(); + }); + + beforeEach(function (done) { + mockNow = mockStart; + + stubNow = sinon.stub(Date, "now", function () { + mockNow += mockTimeInterval; + return mockNow - mockTimeInterval; + }); + testContainer = utils.getTestContainer(sandbox); metricsProvider = new MetricsProvider(testContainer.screen); + + done(); }); - afterEach(function () { + afterEach(function (done) { + stubNow.restore(); sandbox.restore(); + + done(); }); - it("should store metrics", function () { - _.each(["a", "b", "c", "d", "e"], function (data) { - metricsProvider._onMetrics(data); + describe("constructor", function () { + it("builds an aggregation container from configuration", function (done) { + expect(metricsProvider).to.be.an.instanceOf(MetricsProvider); + + expect(Object.keys(metricsProvider._aggregation)).to.deep.equal(AGGREGATE_TIME_LEVELS); + + var index = 0; + _.each(metricsProvider._aggregation, function (value, key) { + expect(key) + .to.be.a("string") + .that.equals(AGGREGATE_TIME_LEVELS[index++]); + + expect(value) + .to.be.an("object") + .that.deep.equals({ + data: [], + lastIndex: 0, + nextAggregateIndex: 0 + }); + }); + + expect(metricsProvider.aggregationLevels) + .to.be.an("array") + .that.deep.equals(AGGREGATE_TIME_LEVELS); + + expect(metricsProvider.minimumAggregationInterval) + .to.be.a("number") + .that.equals(+AGGREGATE_TIME_LEVELS[0]); + + expect(metricsProvider.highestAggregationKey) + .to.be.a("string") + .that.equals(AGGREGATE_TIME_LEVELS.slice(-1)[0]); + + expect(metricsProvider.zoomLevel) + .to.be.a("number") + .that.equals(-1); + + expect(metricsProvider.zoomLevelKey).to.be.undefined; + + expect(metricsProvider._startTime) + .to.be.a("number") + .that.equals(mockStart); + + expect(metricsProvider._lastAggregation) + .to.be.a("number") + .that.equals(mockStart); + + expect(metricsProvider._metrics) + .to.be.an("array") + .that.deep.equals([]); + + done(); }); - var limit = 3; - expect(metricsProvider.getMetrics(limit)).to.deep.equal(["c", "d", "e"]); + }); + + describe("_onMetrics", function () { + // due to the quantity of data processed, notice the .timeout at the bottom - this allows + // for a longer running test + it("retains metrics received, while aggregating them into time buckets", function (done) { + // load with mock metric data already computed + _.each(mockMetrics, function (value) { + metricsProvider._onMetrics(value); + }); + + // the number of data points retained must match the number provided + expect(metricsProvider._metrics) + .to.be.an("array") + .that.has.lengthOf(mockMetricCount); + + // now, examine each metric + _.each(metricsProvider._metrics, function (value, index) { + expect(value) + .to.be.an("object") + .with.property("metricA") + .with.property("valueA") + .that.equals(mockMetrics[index].metricA.valueA); + + expect(value) + .to.be.an("object") + .with.property("metricB") + .with.property("valueA") + .that.equals(mockMetrics[index].metricB.valueA); + + expect(value) + .to.be.an("object") + .with.property("metricB") + .with.property("valueB") + .that.equals(mockMetrics[index].metricB.valueB); + + // verify the memory deallocation logic is working + if (index < metricsRequiredToAchieveHighestAggregation - 1) { + expect(value) + .to.not.have.property("__timeIndices"); + } + + // for those that are not deallocated, verify those properties + if (index >= metricsRequiredToAchieveHighestAggregation - 1) { + expect(value) + .to.have.property("__timeIndices"); + + _.each(AGGREGATE_TIME_LEVELS, function (level) { + // reverse-engineer the expected time index + var timeIndex = Math.floor((index + 1) * mockTimeInterval / +level); + + expect(value) + .to.be.an("object") + .with.property("__timeIndices") + .with.property(level) + .to.be.a("number") + .that.equals(timeIndex); + }); + } + }); + + _.each(metricsProvider._aggregation, function (value, key) { + _.each(value.data, function (row, index) { + var startTimeBand = index * Math.floor(+key / mockTimeInterval) - 1; + var endTimeBand = startTimeBand + Math.floor(+key / mockTimeInterval); + + if (index === 0) { + startTimeBand = 0; + } + + var averageA = { + valueA: 0 + }; + + var averageB = { + valueA: 0, + valueB: 0 + }; + + _.each(metricsProvider._metrics.slice(startTimeBand, endTimeBand), function (metric) { + averageA.valueA += metric.metricA.valueA / (endTimeBand - startTimeBand); + + averageB.valueA += metric.metricB.valueA / (endTimeBand - startTimeBand); + averageB.valueB += metric.metricB.valueB / (endTimeBand - startTimeBand); + }); + + expect(row) + .to.be.an("object") + .with.property("metricA") + .with.property("valueA") + .that.is.a("number") + .that.equals(+averageA.valueA.toFixed(1)); + + expect(row) + .to.be.an("object") + .with.property("metricB") + .with.property("valueA") + .that.is.a("number") + .that.equals(+averageB.valueA.toFixed(1)); + + expect(row) + .to.be.an("object") + .with.property("metricB") + .with.property("valueB") + .that.is.a("number") + .that.equals(+averageB.valueB.toFixed(1)); + }); + }); + + done(); + }).timeout(10000); }); }); diff --git a/test/lib/views/base-line-graph.spec.js b/test/lib/views/base-line-graph.spec.js index f58566b..2041b06 100644 --- a/test/lib/views/base-line-graph.spec.js +++ b/test/lib/views/base-line-graph.spec.js @@ -1,9 +1,13 @@ "use strict"; +var chai = require("chai"); +var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); + var contrib = require("blessed-contrib"); -var expect = require("chai").expect; var _ = require("lodash"); -var sinon = require("sinon"); var BaseView = require("../../../lib/views/base-view"); var BaseLineGraph = require("../../../lib/views/base-line-graph"); @@ -134,7 +138,7 @@ describe("BaseLineGraph", function () { expect(baseGraph).to.have.deep.property("series.a.y").that.deep.equals([0, 0, 0]); expect(baseGraph).to.have.deep.property("series.high").that.deep.equals({ - x: ["2", "1", "0"], + x: ["2s", "1s", "0s"], y: [0, 0, 0], style: { line: "red" } }); @@ -142,7 +146,7 @@ describe("BaseLineGraph", function () { baseGraph.update({ a: 2, high: 4 }); expect(baseGraph).to.have.deep.property("series.a.y").that.deep.equals([0, 0, 2]); expect(baseGraph).to.have.deep.property("series.high").that.deep.equals({ - x: ["2", "1", "0"], + x: ["2s", "1s", "0s"], y: [4, 4, 4], style: { line: "red" } }); diff --git a/test/lib/views/base-view.spec.js b/test/lib/views/base-view.spec.js index 71a671d..2775c63 100644 --- a/test/lib/views/base-view.spec.js +++ b/test/lib/views/base-view.spec.js @@ -1,7 +1,10 @@ "use strict"; -var expect = require("chai").expect; +var chai = require("chai"); var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); var BaseView = require("../../../lib/views/base-view"); var utils = require("../../utils"); diff --git a/test/lib/views/cpu-view.spec.js b/test/lib/views/cpu-view.spec.js index 8f81186..04647d8 100644 --- a/test/lib/views/cpu-view.spec.js +++ b/test/lib/views/cpu-view.spec.js @@ -1,7 +1,10 @@ "use strict"; -var expect = require("chai").expect; +var chai = require("chai"); var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); var CpuView = require("../../../lib/views/cpu-view"); var BaseLineGraph = require("../../../lib/views/base-line-graph"); diff --git a/test/lib/views/eventloop-view.spec.js b/test/lib/views/eventloop-view.spec.js index bb875f7..f470f54 100644 --- a/test/lib/views/eventloop-view.spec.js +++ b/test/lib/views/eventloop-view.spec.js @@ -1,7 +1,10 @@ "use strict"; -var expect = require("chai").expect; +var chai = require("chai"); var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); var BaseView = require("../../../lib/views/base-view"); var BaseLineGraph = require("../../../lib/views/base-line-graph"); diff --git a/test/lib/views/memory-gauge-view.spec.js b/test/lib/views/memory-gauge-view.spec.js index aeeab14..14b4d1a 100644 --- a/test/lib/views/memory-gauge-view.spec.js +++ b/test/lib/views/memory-gauge-view.spec.js @@ -1,9 +1,13 @@ "use strict"; +var chai = require("chai"); +var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); + var blessed = require("blessed"); var contrib = require("blessed-contrib"); -var expect = require("chai").expect; -var sinon = require("sinon"); var MemoryGaugeView = require("../../../lib/views/memory-gauge-view"); var utils = require("../../utils"); diff --git a/test/lib/views/stream-view.spec.js b/test/lib/views/stream-view.spec.js index be92636..ef085f3 100644 --- a/test/lib/views/stream-view.spec.js +++ b/test/lib/views/stream-view.spec.js @@ -1,7 +1,11 @@ "use strict"; -var expect = require("chai").expect; +var chai = require("chai"); var sinon = require("sinon"); +var sinonChai = require("sinon-chai"); +var expect = chai.expect; +chai.use(sinonChai); + var blessed = require("blessed"); var StreamView = require("../../../lib/views/stream-view"); diff --git a/yarn.lock b/yarn.lock index 1327853..374564e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,22 +84,52 @@ ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" dependencies: sprintf-js "~1.0.2" +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + +array-slice@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1: +array-uniq@^1.0.1, array-uniq@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + arraybuffer.slice@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" @@ -220,6 +250,10 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +beeper@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" + better-assert@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" @@ -264,13 +298,21 @@ boom@2.x.x: dependencies: hoek "2.x.x" -brace-expansion@^1.1.7: +brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + bresenham@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/bresenham/-/bresenham-0.0.3.tgz#abdab9e5b194e27c757cd314d8444314f299877a" @@ -379,6 +421,18 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + +clone@^1.0.0, clone@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -476,6 +530,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dateformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17" + debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -514,6 +572,12 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +defaults@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + dependencies: + clone "^1.0.2" + define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -537,6 +601,16 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" +deprecated@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" + +detect-file@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" + dependencies: + fs-exists-sync "^0.1.0" + diff@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" @@ -569,12 +643,24 @@ drawille-canvas-blessed-contrib@>=0.0.1, drawille-canvas-blessed-contrib@>=0.1.3 gl-matrix "^2.1.0" x256 ">=0.0.1" +duplexer2@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + dependencies: + readable-stream "~1.1.9" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" dependencies: jsbn "~0.1.0" +end-of-stream@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" + dependencies: + once "~1.3.0" + engine.io-client@~1.8.4: version "1.8.4" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.4.tgz#9fe85dee25853ca6babe25bd2ad68710863e91c2" @@ -825,14 +911,51 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" -extend@~3.0.0: +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + dependencies: + os-homedir "^1.0.1" + +expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + dependencies: + homedir-polyfill "^1.0.1" + +extend@^3.0.0, extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +fancy-log@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" + dependencies: + chalk "^1.1.1" + time-stamp "^1.0.0" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -851,6 +974,24 @@ file-entry-cache@^1.1.1: flat-cache "^1.2.1" object-assign "^4.0.1" +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -858,6 +999,33 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +findup-sync@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" + dependencies: + detect-file "^0.1.0" + is-glob "^2.0.1" + micromatch "^2.3.7" + resolve-dir "^0.1.0" + +fined@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +first-chunk-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" + +flagged-respawn@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" + flat-cache@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" @@ -867,6 +1035,22 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -889,6 +1073,10 @@ formatio@1.1.1: dependencies: samsam "~1.1" +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -897,6 +1085,12 @@ function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +gaze@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" + dependencies: + globule "~0.1.0" + generate-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" @@ -917,6 +1111,42 @@ gl-matrix@^2.1.0: version "2.3.2" resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-2.3.2.tgz#aac808c74af7d5db05fe04cb60ca1a0fcb174d74" +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob-stream@^3.1.5: + version "3.1.18" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" + dependencies: + glob "^4.3.1" + glob2base "^0.0.12" + minimatch "^2.0.1" + ordered-read-streams "^0.1.0" + through2 "^0.6.1" + unique-stream "^1.0.0" + +glob-watcher@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" + dependencies: + gaze "^0.5.1" + +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -928,6 +1158,15 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^4.3.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -949,6 +1188,30 @@ glob@^7.0.3, glob@^7.0.5: once "^1.3.0" path-is-absolute "^1.0.0" +glob@~3.1.21: + version "3.1.21" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" + dependencies: + graceful-fs "~1.2.0" + inherits "1" + minimatch "~0.2.11" + +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" + globals@^9.0.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -964,10 +1227,34 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globule@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" + dependencies: + glob "~3.1.21" + lodash "~1.0.1" + minimatch "~0.2.11" + +glogg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" + dependencies: + sparkles "^1.0.0" + +graceful-fs@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" + dependencies: + natives "^1.1.0" + graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +graceful-fs@~1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -976,6 +1263,64 @@ growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +gulp-mocha@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-3.0.1.tgz#ab0ca2c39403718174dddad750e63a61be17e041" + dependencies: + gulp-util "^3.0.0" + mocha "^3.0.0" + plur "^2.1.0" + req-cwd "^1.0.1" + temp "^0.8.3" + through "^2.3.4" + +gulp-util@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" + dependencies: + array-differ "^1.0.0" + array-uniq "^1.0.2" + beeper "^1.0.0" + chalk "^1.0.0" + dateformat "^2.0.0" + fancy-log "^1.1.0" + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash._reescape "^3.0.0" + lodash._reevaluate "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.template "^3.0.0" + minimist "^1.1.0" + multipipe "^0.1.2" + object-assign "^3.0.0" + replace-ext "0.0.1" + through2 "^2.0.0" + vinyl "^0.5.0" + +gulp@^3.9.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" + dependencies: + archy "^1.0.0" + chalk "^1.0.0" + deprecated "^0.0.1" + gulp-util "^3.0.0" + interpret "^1.0.0" + liftoff "^2.1.0" + minimist "^1.1.0" + orchestrator "^0.3.0" + pretty-hrtime "^1.0.0" + semver "^4.1.0" + tildify "^1.0.0" + v8flags "^2.0.2" + vinyl-fs "^0.3.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + dependencies: + glogg "^1.0.0" + handlebars@^4.0.1: version "4.0.10" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" @@ -1017,6 +1362,12 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-gulplog@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" + dependencies: + sparkles "^1.0.0" + has@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" @@ -1040,6 +1391,12 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -1067,6 +1424,10 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" +inherits@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" + inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -1075,6 +1436,10 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" +ini@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -1093,16 +1458,49 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +interpret@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" + invariant@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: loose-envify "^1.0.0" +irregular-plurals@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.3.0.tgz#7af06931bdf74be33dcf585a13e06fccc16caecf" + +is-absolute@^0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" + dependencies: + is-relative "^0.2.1" + is-windows "^0.2.0" + is-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -1113,6 +1511,12 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + is-my-json-valid@^2.10.0: version "2.16.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" @@ -1122,6 +1526,18 @@ is-my-json-valid@^2.10.0: jsonpointer "^4.0.0" xtend "^4.0.0" +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -1138,10 +1554,30 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-plain-object@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-relative@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" + dependencies: + is-unc-path "^0.1.1" + is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -1152,11 +1588,25 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-unc-path@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" + dependencies: + unc-path-regex "^0.1.0" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1164,6 +1614,16 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +isobject@^2.0.0, isobject@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1247,6 +1707,12 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -1258,6 +1724,20 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +liftoff@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" + dependencies: + extend "^3.0.0" + findup-sync "^0.4.2" + fined "^1.0.1" + flagged-respawn "^0.3.2" + lodash.isplainobject "^4.0.4" + lodash.isstring "^4.0.1" + lodash.mapvalues "^4.4.0" + rechoir "^0.6.2" + resolve "^1.1.7" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -1273,6 +1753,14 @@ lodash._basecreate@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" +lodash._basetostring@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" + +lodash._basevalues@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" + lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" @@ -1281,6 +1769,22 @@ lodash._isiterateecall@^3.0.0: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" +lodash._reescape@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" + +lodash._reevaluate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + lodash.assign@^4.0.0, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -1305,6 +1809,12 @@ lodash.endswith@^4.0.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" +lodash.escape@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" + dependencies: + lodash._root "^3.0.0" + lodash.find@^4.3.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" @@ -1321,6 +1831,14 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isplainobject@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.kebabcase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -1333,14 +1851,43 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.mapvalues@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" + lodash.pickby@^4.0.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + lodash.snakecase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" +lodash.template@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" + dependencies: + lodash._basecopy "^3.0.0" + lodash._basetostring "^3.0.0" + lodash._basevalues "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.keys "^3.0.0" + lodash.restparam "^3.0.0" + lodash.templatesettings "^3.0.0" + +lodash.templatesettings@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" @@ -1353,6 +1900,10 @@ lodash@>=3.0.0, lodash@^4.0.0, lodash@^4.16.2, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" + lolex@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" @@ -1367,6 +1918,10 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -1374,6 +1929,10 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +map-cache@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + map-canvas@>=0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/map-canvas/-/map-canvas-0.1.5.tgz#8be6bade0bf3e9f9a8b56e8836a1d1d133cab186" @@ -1405,6 +1964,24 @@ memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" +micromatch@^2.3.7: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" @@ -1421,10 +1998,27 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.7: dependencies: brace-expansion "^1.1.7" +minimatch@^2.0.1: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + dependencies: + brace-expansion "^1.0.0" + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + dependencies: + lru-cache "2" + sigmund "~1.0.0" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" +minimist@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -1435,7 +2029,7 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "0.0.8" -mocha@^3.1.1: +mocha@^3.0.0, mocha@^3.1.1: version "3.4.2" resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594" dependencies: @@ -1469,10 +2063,20 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multipipe@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" + dependencies: + duplexer2 "0.0.2" + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" +natives@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -1496,6 +2100,12 @@ nopt@~2.1.2: dependencies: abbrev "1" +normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -1508,6 +2118,10 @@ object-assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -1528,12 +2142,40 @@ object.assign@^4.0.4: function-bind "^1.1.0" object-keys "^1.0.10" +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.pick@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.2.0.tgz#b5392bee9782da6d9fb7d6afaf539779f1234c2b" + dependencies: + isobject "^2.1.0" + once@1.x, once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" +once@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + dependencies: + wrappy "1" + onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -1572,10 +2214,47 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" -os-homedir@^1.0.0: +orchestrator@^0.3.0: + version "0.3.8" + resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" + dependencies: + end-of-stream "~0.1.5" + sequencify "~0.0.7" + stream-consume "~0.1.0" + +ordered-read-streams@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" + +os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +parse-filepath@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73" + dependencies: + is-absolute "^0.2.3" + map-cache "^0.2.0" + path-root "^0.1.1" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + parsejson@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" @@ -1612,6 +2291,16 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + dependencies: + path-root-regex "^0.1.0" + performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -1658,6 +2347,12 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" +plur@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + dependencies: + irregular-plurals "^1.0.0" + pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" @@ -1670,12 +2365,20 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + pretty-bytes@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf" dependencies: number-is-nan "^1.0.0" +pretty-hrtime@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -1696,7 +2399,23 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -readable-stream@^2.2.2: +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.1.5, readable-stream@^2.2.2: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -1708,9 +2427,9 @@ readable-stream@^2.2.2: string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@~1.0.2: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -1725,6 +2444,12 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + redeyed@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" @@ -1735,10 +2460,41 @@ regenerator-runtime@^0.10.0: version "0.10.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" +regex-cache@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +remove-trailing-separator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + +req-cwd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-1.0.1.tgz#0d73aeae9266e697a78f7976019677e76acf0fff" + dependencies: + req-from "^1.0.1" + +req-from@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/req-from/-/req-from-1.0.1.tgz#bf81da5147947d32d13b947dc12a58ad4587350e" + dependencies: + resolve-from "^2.0.0" + request@^2.53.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -1777,15 +2533,26 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6: +resolve@^1.1.6, resolve@^1.1.7: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: @@ -1810,6 +2577,10 @@ rimraf@^2.2.8: dependencies: glob "^7.0.5" +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -1836,10 +2607,22 @@ sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" +semver@^4.1.0: + version "4.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" + +sequencify@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" + shelljs@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + sinon-chai@^2.8.0: version "2.11.0" resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.11.0.tgz#93d90f989fff67ce45767077ffe575dde1faea6d" @@ -1923,6 +2706,10 @@ source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +sparkles@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" + sparkline@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/sparkline/-/sparkline-0.1.2.tgz#c3bde46252b1354e710c4b200d54816bd9f07a32" @@ -1952,6 +2739,10 @@ stack-trace@~0.0.7: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" +stream-consume@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -1997,6 +2788,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-bom@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" + dependencies: + first-chunk-stream "^1.0.0" + is-utf8 "^0.2.0" + strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -2028,6 +2826,13 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" +temp@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" + dependencies: + os-tmpdir "^1.0.0" + rimraf "~2.2.6" + term-canvas@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/term-canvas/-/term-canvas-0.0.5.tgz#597afac2fa6369a6f17860bce9c5f66d6ea0ca96" @@ -2036,10 +2841,34 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through@^2.3.6: +through2@^0.6.1: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through2@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@^2.3.4, through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +tildify@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" + dependencies: + os-homedir "^1.0.0" + +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -2103,6 +2932,18 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" +unc-path-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + +unique-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + user-home@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" @@ -2123,13 +2964,47 @@ uuid@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +v8flags@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + user-home "^1.1.1" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" dependencies: extsprintf "1.0.2" -which@^1.1.1, which@^1.2.9: +vinyl-fs@^0.3.0: + version "0.3.14" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" + dependencies: + defaults "^1.0.0" + glob-stream "^3.1.5" + glob-watcher "^0.0.6" + graceful-fs "^3.0.0" + mkdirp "^0.5.0" + strip-bom "^1.0.0" + through2 "^0.6.1" + vinyl "^0.4.0" + +vinyl@^0.4.0: + version "0.4.6" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" + dependencies: + clone "^0.2.0" + clone-stats "^0.0.1" + +vinyl@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +which@^1.1.1, which@^1.2.12, which@^1.2.9: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" dependencies: @@ -2200,7 +3075,7 @@ xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" -xtend@^4.0.0: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 64e7805b06cee833bb4b49e0aaee486c21ecfd8a Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sat, 22 Jul 2017 12:08:22 -0700 Subject: [PATCH 05/21] test(#64) removed gulp, removed duplicate setup --- gulpfile.js | 9 - package.json | 2 - test/lib/providers/metrics-provider.spec.js | 109 +-- test/lib/views/base-line-graph.spec.js | 5 +- test/lib/views/base-view.spec.js | 5 +- test/lib/views/cpu-view.spec.js | 5 +- test/lib/views/eventloop-view.spec.js | 5 +- test/lib/views/memory-gauge-view.spec.js | 9 +- test/lib/views/stream-view.spec.js | 5 +- yarn.lock | 903 +------------------- 10 files changed, 83 insertions(+), 974 deletions(-) delete mode 100644 gulpfile.js diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index b184256..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -var gulp = require("gulp"); -var mocha = require("gulp-mocha"); - -gulp.task("test-debug", function () { - gulp.src(["test/**/*.spec.js"]) - .pipe(mocha()); -}); diff --git a/package.json b/package.json index 6b850c5..548bb95 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,6 @@ "eslint-config-formidable": "^2.0.1", "eslint-plugin-filenames": "^1.1.0", "eslint-plugin-import": "^1.16.0", - "gulp": "^3.9.1", - "gulp-mocha": "=3.0.1", "istanbul": "^0.4.5", "mocha": "^3.1.1", "mock-require": "^2.0.1", diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 0dd3abe..31dd68d 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -16,7 +16,6 @@ describe("MetricsProvider", function () { var testContainer; var metricsProvider; - var stubNow; var mockStart = 1000000; var mockNow = 1000000; var mockTimeInterval = 2500; @@ -27,9 +26,7 @@ describe("MetricsProvider", function () { var mockMetrics = []; var mockMetricCount; - before(function (done) { - sandbox = sinon.sandbox.create(); - + before(function () { // generate some fake metrics for processing mockMetricCount = Math.ceil(Math.random() * 500 + metricsRequiredToAchieveHighestAggregation); @@ -45,40 +42,35 @@ describe("MetricsProvider", function () { } }); } - - done(); }); - after(function (done) { - done(); - }); + beforeEach(function () { + sandbox = sinon.sandbox.create(); - beforeEach(function (done) { mockNow = mockStart; - stubNow = sinon.stub(Date, "now", function () { + sandbox.stub(Date, "now", function () { mockNow += mockTimeInterval; return mockNow - mockTimeInterval; }); testContainer = utils.getTestContainer(sandbox); metricsProvider = new MetricsProvider(testContainer.screen); - - done(); }); - afterEach(function (done) { - stubNow.restore(); + afterEach(function () { sandbox.restore(); - - done(); }); describe("constructor", function () { - it("builds an aggregation container from configuration", function (done) { + it("builds an aggregation container from configuration", function () { expect(metricsProvider).to.be.an.instanceOf(MetricsProvider); - expect(Object.keys(metricsProvider._aggregation)).to.deep.equal(AGGREGATE_TIME_LEVELS); + expect(metricsProvider) + .to.be.an("object") + .with.property("_aggregation") + .which.is.an("object") + .with.keys(AGGREGATE_TIME_LEVELS); var index = 0; _.each(metricsProvider._aggregation, function (value, key) { @@ -95,52 +87,69 @@ describe("MetricsProvider", function () { }); }); - expect(metricsProvider.aggregationLevels) - .to.be.an("array") + expect(metricsProvider) + .to.be.an("object") + .with.property("aggregationLevels") + .which.is.an("array") .that.deep.equals(AGGREGATE_TIME_LEVELS); - expect(metricsProvider.minimumAggregationInterval) - .to.be.a("number") + expect(metricsProvider) + .to.be.an("object") + .with.property("minimumAggregationInterval") + .which.is.a("number") .that.equals(+AGGREGATE_TIME_LEVELS[0]); - expect(metricsProvider.highestAggregationKey) - .to.be.a("string") + expect(metricsProvider) + .to.be.an("object") + .with.property("highestAggregationKey") + .which.is.a("string") .that.equals(AGGREGATE_TIME_LEVELS.slice(-1)[0]); - expect(metricsProvider.zoomLevel) - .to.be.a("number") + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevel") + .which.is.a("number") .that.equals(-1); - expect(metricsProvider.zoomLevelKey).to.be.undefined; + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevelKey") + .that.is.undefined; - expect(metricsProvider._startTime) - .to.be.a("number") + expect(metricsProvider) + .to.be.an("object") + .with.property("_startTime") + .which.is.a("number") .that.equals(mockStart); - expect(metricsProvider._lastAggregation) - .to.be.a("number") + expect(metricsProvider) + .to.be.an("object") + .with.property("_lastAggregation") + .which.is.a("number") .that.equals(mockStart); - expect(metricsProvider._metrics) - .to.be.an("array") + expect(metricsProvider) + .to.be.an("object") + .with.property("_metrics") + .which.is.an("array") .that.deep.equals([]); - - done(); }); }); describe("_onMetrics", function () { // due to the quantity of data processed, notice the .timeout at the bottom - this allows // for a longer running test - it("retains metrics received, while aggregating them into time buckets", function (done) { + it("retains metrics received, while aggregating them into time buckets", function () { // load with mock metric data already computed _.each(mockMetrics, function (value) { metricsProvider._onMetrics(value); }); // the number of data points retained must match the number provided - expect(metricsProvider._metrics) - .to.be.an("array") + expect(metricsProvider) + .to.be.an("object") + .with.property("_metrics") + .which.is.an("array") .that.has.lengthOf(mockMetricCount); // now, examine each metric @@ -182,7 +191,7 @@ describe("MetricsProvider", function () { .to.be.an("object") .with.property("__timeIndices") .with.property(level) - .to.be.a("number") + .which.is.a("number") .that.equals(timeIndex); }); } @@ -190,9 +199,11 @@ describe("MetricsProvider", function () { _.each(metricsProvider._aggregation, function (value, key) { _.each(value.data, function (row, index) { + // reverse-engineer the start and end of this time band var startTimeBand = index * Math.floor(+key / mockTimeInterval) - 1; var endTimeBand = startTimeBand + Math.floor(+key / mockTimeInterval); + // the first time band is offset by a time interval (call to Date.now at start) if (index === 0) { startTimeBand = 0; } @@ -206,37 +217,39 @@ describe("MetricsProvider", function () { valueB: 0 }; + var metricCount = endTimeBand - startTimeBand; + + // recompute the average manually _.each(metricsProvider._metrics.slice(startTimeBand, endTimeBand), function (metric) { - averageA.valueA += metric.metricA.valueA / (endTimeBand - startTimeBand); + averageA.valueA += metric.metricA.valueA / metricCount; - averageB.valueA += metric.metricB.valueA / (endTimeBand - startTimeBand); - averageB.valueB += metric.metricB.valueB / (endTimeBand - startTimeBand); + averageB.valueA += metric.metricB.valueA / metricCount; + averageB.valueB += metric.metricB.valueB / metricCount; }); + // verify expect(row) .to.be.an("object") .with.property("metricA") .with.property("valueA") - .that.is.a("number") + .which.is.a("number") .that.equals(+averageA.valueA.toFixed(1)); expect(row) .to.be.an("object") .with.property("metricB") .with.property("valueA") - .that.is.a("number") + .which.is.a("number") .that.equals(+averageB.valueA.toFixed(1)); expect(row) .to.be.an("object") .with.property("metricB") .with.property("valueB") - .that.is.a("number") + .which.is.a("number") .that.equals(+averageB.valueB.toFixed(1)); }); }); - - done(); }).timeout(10000); }); }); diff --git a/test/lib/views/base-line-graph.spec.js b/test/lib/views/base-line-graph.spec.js index 2041b06..315c700 100644 --- a/test/lib/views/base-line-graph.spec.js +++ b/test/lib/views/base-line-graph.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var contrib = require("blessed-contrib"); var _ = require("lodash"); diff --git a/test/lib/views/base-view.spec.js b/test/lib/views/base-view.spec.js index 2775c63..71a671d 100644 --- a/test/lib/views/base-view.spec.js +++ b/test/lib/views/base-view.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var BaseView = require("../../../lib/views/base-view"); var utils = require("../../utils"); diff --git a/test/lib/views/cpu-view.spec.js b/test/lib/views/cpu-view.spec.js index 04647d8..8f81186 100644 --- a/test/lib/views/cpu-view.spec.js +++ b/test/lib/views/cpu-view.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var CpuView = require("../../../lib/views/cpu-view"); var BaseLineGraph = require("../../../lib/views/base-line-graph"); diff --git a/test/lib/views/eventloop-view.spec.js b/test/lib/views/eventloop-view.spec.js index f470f54..bb875f7 100644 --- a/test/lib/views/eventloop-view.spec.js +++ b/test/lib/views/eventloop-view.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var BaseView = require("../../../lib/views/base-view"); var BaseLineGraph = require("../../../lib/views/base-line-graph"); diff --git a/test/lib/views/memory-gauge-view.spec.js b/test/lib/views/memory-gauge-view.spec.js index 14b4d1a..2f99beb 100644 --- a/test/lib/views/memory-gauge-view.spec.js +++ b/test/lib/views/memory-gauge-view.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var blessed = require("blessed"); var contrib = require("blessed-contrib"); @@ -86,8 +83,8 @@ describe("MemoryGaugeView", function () { memory.onEvent({ mem: mem }); expect(memory.update).to.have.been.calledTwice - .and.to.been.calledWithExactly(memory.heapGauge, mem.heapUsed, mem.heapTotal) - .and.to.been.calledWithExactly(memory.rssGauge, mem.rss, mem.systemTotal); + .and.to.have.been.calledWithExactly(memory.heapGauge, mem.heapUsed, mem.heapTotal) + .and.to.have.been.calledWithExactly(memory.rssGauge, mem.rss, mem.systemTotal); }); }); diff --git a/test/lib/views/stream-view.spec.js b/test/lib/views/stream-view.spec.js index ef085f3..ecdc568 100644 --- a/test/lib/views/stream-view.spec.js +++ b/test/lib/views/stream-view.spec.js @@ -1,10 +1,7 @@ "use strict"; -var chai = require("chai"); +var expect = require("chai").expect; var sinon = require("sinon"); -var sinonChai = require("sinon-chai"); -var expect = chai.expect; -chai.use(sinonChai); var blessed = require("blessed"); diff --git a/yarn.lock b/yarn.lock index 374564e..1327853 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,52 +84,22 @@ ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" dependencies: sprintf-js "~1.0.2" -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - dependencies: - arr-flatten "^1.0.1" - -arr-flatten@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - -array-slice@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f" - array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1, array-uniq@^1.0.2: +array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - arraybuffer.slice@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" @@ -250,10 +220,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - better-assert@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" @@ -298,21 +264,13 @@ boom@2.x.x: dependencies: hoek "2.x.x" -brace-expansion@^1.0.0, brace-expansion@^1.1.7: +brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - bresenham@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/bresenham/-/bresenham-0.0.3.tgz#abdab9e5b194e27c757cd314d8444314f299877a" @@ -421,18 +379,6 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - -clone@^1.0.0, clone@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -530,10 +476,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dateformat@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17" - debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -572,12 +514,6 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -defaults@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - dependencies: - clone "^1.0.2" - define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -601,16 +537,6 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - -detect-file@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" - dependencies: - fs-exists-sync "^0.1.0" - diff@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" @@ -643,24 +569,12 @@ drawille-canvas-blessed-contrib@>=0.0.1, drawille-canvas-blessed-contrib@>=0.1.3 gl-matrix "^2.1.0" x256 ">=0.0.1" -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - dependencies: - readable-stream "~1.1.9" - ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" dependencies: jsbn "~0.1.0" -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - dependencies: - once "~1.3.0" - engine.io-client@~1.8.4: version "1.8.4" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.4.tgz#9fe85dee25853ca6babe25bd2ad68710863e91c2" @@ -911,51 +825,14 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - dependencies: - is-posix-bracket "^0.1.0" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - dependencies: - os-homedir "^1.0.1" - -expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - dependencies: - homedir-polyfill "^1.0.1" - -extend@^3.0.0, extend@~3.0.0: +extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - dependencies: - is-extglob "^1.0.0" - extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" -fancy-log@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" - dependencies: - chalk "^1.1.1" - time-stamp "^1.0.0" - fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -974,24 +851,6 @@ file-entry-cache@^1.1.1: flat-cache "^1.2.1" object-assign "^4.0.1" -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -999,33 +858,6 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" - dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" - -fined@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - -flagged-respawn@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" - flat-cache@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" @@ -1035,22 +867,6 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - dependencies: - for-in "^1.0.1" - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - dependencies: - for-in "^1.0.1" - foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -1073,10 +889,6 @@ formatio@1.1.1: dependencies: samsam "~1.1" -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1085,12 +897,6 @@ function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - dependencies: - globule "~0.1.0" - generate-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" @@ -1111,42 +917,6 @@ gl-matrix@^2.1.0: version "2.3.2" resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-2.3.2.tgz#aac808c74af7d5db05fe04cb60ca1a0fcb174d74" -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" - -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - dependencies: - gaze "^0.5.1" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - dependencies: - find-index "^0.1.1" - glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -1158,15 +928,6 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -1188,30 +949,6 @@ glob@^7.0.3, glob@^7.0.5: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" - -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" - -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - dependencies: - homedir-polyfill "^1.0.0" - ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" - globals@^9.0.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -1227,34 +964,10 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - -glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" - dependencies: - sparkles "^1.0.0" - -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - dependencies: - natives "^1.1.0" - graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -1263,64 +976,6 @@ growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" -gulp-mocha@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-3.0.1.tgz#ab0ca2c39403718174dddad750e63a61be17e041" - dependencies: - gulp-util "^3.0.0" - mocha "^3.0.0" - plur "^2.1.0" - req-cwd "^1.0.1" - temp "^0.8.3" - through "^2.3.4" - -gulp-util@^3.0.0: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - dependencies: - archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" - pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - dependencies: - glogg "^1.0.0" - handlebars@^4.0.1: version "4.0.10" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" @@ -1362,12 +1017,6 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - dependencies: - sparkles "^1.0.0" - has@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" @@ -1391,12 +1040,6 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" - dependencies: - parse-passwd "^1.0.0" - http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -1424,10 +1067,6 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -1436,10 +1075,6 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -1458,49 +1093,16 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -interpret@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" - invariant@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: loose-envify "^1.0.0" -irregular-plurals@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.3.0.tgz#7af06931bdf74be33dcf585a13e06fccc16caecf" - -is-absolute@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" - dependencies: - is-relative "^0.2.1" - is-windows "^0.2.0" - is-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -1511,12 +1113,6 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - dependencies: - is-extglob "^1.0.0" - is-my-json-valid@^2.10.0: version "2.16.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" @@ -1526,18 +1122,6 @@ is-my-json-valid@^2.10.0: jsonpointer "^4.0.0" xtend "^4.0.0" -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - dependencies: - kind-of "^3.0.2" - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -1554,30 +1138,10 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-object@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-relative@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" - dependencies: - is-unc-path "^0.1.1" - is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -1588,25 +1152,11 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is-unc-path@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" - dependencies: - unc-path-regex "^0.1.0" - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - -is-windows@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1614,16 +1164,6 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isobject@^2.0.0, isobject@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1707,12 +1247,6 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - dependencies: - is-buffer "^1.1.5" - lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -1724,20 +1258,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -liftoff@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" - dependencies: - extend "^3.0.0" - findup-sync "^0.4.2" - fined "^1.0.1" - flagged-respawn "^0.3.2" - lodash.isplainobject "^4.0.4" - lodash.isstring "^4.0.1" - lodash.mapvalues "^4.4.0" - rechoir "^0.6.2" - resolve "^1.1.7" - lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -1753,14 +1273,6 @@ lodash._basecreate@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" @@ -1769,22 +1281,6 @@ lodash._isiterateecall@^3.0.0: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - lodash.assign@^4.0.0, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -1809,12 +1305,6 @@ lodash.endswith@^4.0.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - dependencies: - lodash._root "^3.0.0" - lodash.find@^4.3.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" @@ -1831,14 +1321,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isplainobject@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.kebabcase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -1851,43 +1333,14 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.mapvalues@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - lodash.pickby@^4.0.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - lodash.snakecase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" @@ -1900,10 +1353,6 @@ lodash@>=3.0.0, lodash@^4.0.0, lodash@^4.16.2, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - lolex@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" @@ -1918,10 +1367,6 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -1929,10 +1374,6 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -map-cache@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - map-canvas@>=0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/map-canvas/-/map-canvas-0.1.5.tgz#8be6bade0bf3e9f9a8b56e8836a1d1d133cab186" @@ -1964,24 +1405,6 @@ memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" -micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" @@ -1998,27 +1421,10 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.7: dependencies: brace-expansion "^1.1.7" -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - dependencies: - brace-expansion "^1.0.0" - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - dependencies: - lru-cache "2" - sigmund "~1.0.0" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -2029,7 +1435,7 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "0.0.8" -mocha@^3.0.0, mocha@^3.1.1: +mocha@^3.1.1: version "3.4.2" resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594" dependencies: @@ -2063,20 +1469,10 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - dependencies: - duplexer2 "0.0.2" - mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" -natives@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31" - negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -2100,12 +1496,6 @@ nopt@~2.1.2: dependencies: abbrev "1" -normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -2118,10 +1508,6 @@ object-assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2142,40 +1528,12 @@ object.assign@^4.0.4: function-bind "^1.1.0" object-keys "^1.0.10" -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.2.0.tgz#b5392bee9782da6d9fb7d6afaf539779f1234c2b" - dependencies: - isobject "^2.1.0" - once@1.x, once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - dependencies: - wrappy "1" - onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -2214,47 +1572,10 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" - -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -parse-filepath@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73" - dependencies: - is-absolute "^0.2.3" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - parsejson@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" @@ -2291,16 +1612,6 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - dependencies: - path-root-regex "^0.1.0" - performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -2347,12 +1658,6 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" -plur@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" - dependencies: - irregular-plurals "^1.0.0" - pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" @@ -2365,20 +1670,12 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - pretty-bytes@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf" dependencies: number-is-nan "^1.0.0" -pretty-hrtime@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -2399,23 +1696,7 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.2: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.1.5, readable-stream@^2.2.2: +readable-stream@^2.2.2: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -2427,9 +1708,9 @@ readable-stream@^2.1.5, readable-stream@^2.2.2: string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" +readable-stream@~1.0.2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -2444,12 +1725,6 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - dependencies: - resolve "^1.1.6" - redeyed@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" @@ -2460,41 +1735,10 @@ regenerator-runtime@^0.10.0: version "0.10.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" -regex-cache@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" - dependencies: - is-equal-shallow "^0.1.3" - is-primitive "^2.0.0" - -remove-trailing-separator@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - -req-cwd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-1.0.1.tgz#0d73aeae9266e697a78f7976019677e76acf0fff" - dependencies: - req-from "^1.0.1" - -req-from@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/req-from/-/req-from-1.0.1.tgz#bf81da5147947d32d13b947dc12a58ad4587350e" - dependencies: - resolve-from "^2.0.0" - request@^2.53.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -2533,26 +1777,15 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" - resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" -resolve-from@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" - resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.1.7: +resolve@^1.1.6: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: @@ -2577,10 +1810,6 @@ rimraf@^2.2.8: dependencies: glob "^7.0.5" -rimraf@~2.2.6: - version "2.2.8" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" - run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -2607,22 +1836,10 @@ sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -semver@^4.1.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - shelljs@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - sinon-chai@^2.8.0: version "2.11.0" resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.11.0.tgz#93d90f989fff67ce45767077ffe575dde1faea6d" @@ -2706,10 +1923,6 @@ source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - sparkline@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/sparkline/-/sparkline-0.1.2.tgz#c3bde46252b1354e710c4b200d54816bd9f07a32" @@ -2739,10 +1952,6 @@ stack-trace@~0.0.7: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" -stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -2788,13 +1997,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -2826,13 +2028,6 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" -temp@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" - dependencies: - os-tmpdir "^1.0.0" - rimraf "~2.2.6" - term-canvas@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/term-canvas/-/term-canvas-0.0.5.tgz#597afac2fa6369a6f17860bce9c5f66d6ea0ca96" @@ -2841,34 +2036,10 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through2@^0.6.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through@^2.3.4, through@^2.3.6: +through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -tildify@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - dependencies: - os-homedir "^1.0.0" - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -2932,18 +2103,6 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" -unc-path-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - user-home@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" @@ -2964,47 +2123,13 @@ uuid@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - dependencies: - user-home "^1.1.1" - verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" dependencies: extsprintf "1.0.2" -vinyl-fs@^0.3.0: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" - -vinyl@^0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -which@^1.1.1, which@^1.2.12, which@^1.2.9: +which@^1.1.1, which@^1.2.9: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" dependencies: @@ -3075,7 +2200,7 @@ xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 89a6fa2d2a6bcb9d2850b6bd7a7b3c55163ef9f6 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sat, 22 Jul 2017 12:09:45 -0700 Subject: [PATCH 06/21] test(#64) removed unnecessary done() --- test/lib/generate-layouts.spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/lib/generate-layouts.spec.js b/test/lib/generate-layouts.spec.js index a37d391..dea7832 100644 --- a/test/lib/generate-layouts.spec.js +++ b/test/lib/generate-layouts.spec.js @@ -22,7 +22,7 @@ var parent = { }; describe("generate-layouts", function () { - beforeEach(function (done) { + beforeEach(function () { mock("fake/empty-layout", []); mock("fake/invalid-config-layout", { invalid: "config" }); mock("fake/fill-view-layout", [[ @@ -154,8 +154,6 @@ describe("generate-layouts", function () { ] } ]]); - - done(); }); it("should validate default layout", function () { From 07871d17118df38092a498ccc1c3c27c26ca27a6 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sat, 22 Jul 2017 12:24:22 -0700 Subject: [PATCH 07/21] fix(#64) test use sandbox stub --- test/lib/dashboard-agent.spec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/lib/dashboard-agent.spec.js b/test/lib/dashboard-agent.spec.js index 48f746e..02c0101 100644 --- a/test/lib/dashboard-agent.spec.js +++ b/test/lib/dashboard-agent.spec.js @@ -75,7 +75,7 @@ describe("dashboard-agent", function () { expect(metrics.cpu.utilization).to.equal(60); }; - var stubProcess = sinon.stub(process, "memoryUsage", function () { + sandbox.stub(process, "memoryUsage", function () { return { systemTotal: 20, rss: 30, @@ -84,7 +84,7 @@ describe("dashboard-agent", function () { }; }); - var stubPUsage = sinon.stub(pusage, "stat", function (processId, callback) { + sandbox.stub(pusage, "stat", function (processId, callback) { expect(processId).to.equal(process.pid); expect(callback).to.be.a("function"); @@ -95,9 +95,6 @@ describe("dashboard-agent", function () { tryCatch(done, function () { expect(err).to.be.null; checkMetrics(metrics); - - stubProcess.restore(); - stubPUsage.restore(); }); }); }); From f3b8f2ca2529b055614ce613b754069f2ade97f2 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sat, 22 Jul 2017 12:30:45 -0700 Subject: [PATCH 08/21] fix(#64) cross-platform fix to coverage See https://github.com/gotwarlost/istanbul/issues/90 for reasoning --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 548bb95..a2c26c9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "npm run lint && npm run test-only", "test-only": "mocha -c --require test/setup.js --recursive ./test", "test-app": "./bin/nodejs-dashboard.js node test/app/index.js", - "coverage": "istanbul cover --include-all-sources _mocha -- -c --recursive --require test/setup.js ./test" + "coverage": "istanbul cover --include-all-sources node_modules/mocha/bin/_mocha -- -c --recursive --require test/setup.js ./test" }, "repository": { "type": "git", From 6dce469b22267383cf73c2b5474ef4184e2fbf37 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sun, 23 Jul 2017 15:48:07 -0700 Subject: [PATCH 09/21] chore(#64) simplified aggregation --- lib/providers/metrics-provider.js | 152 ++++++-------------- test/lib/providers/metrics-provider.spec.js | 38 ++--- 2 files changed, 54 insertions(+), 136 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 170fcd8..f3eddf6 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -81,9 +81,9 @@ * To ensure no overflows (and therefore NaN) are produced, the average is done * using the formula on the left-side of this equivalence: * - * k[0] k[1] k[2] k[n] sum([k0...kn]) - * ------ + ------ + ------ + ... + ------ = ---------------- = avg([k0...kn]) - * n - 1 n - 1 n - 1 n - 1 n - 1 + * k[0] k[1] k[2] k[n - 1] sum([k0...kn-1]) + * ------ + ------ + ------ + ... + ---------- = ---------------- = avg([k0...kn-1]) + * n n n n n * * The sum of memory usage even over a 10s aggregate is enough to produce NaN. */ @@ -120,7 +120,7 @@ var MetricsProvider = for (var index = 0; index < AGGREGATE_TIME_LEVELS.length; index++) { this._aggregation[AGGREGATE_TIME_LEVELS[index]] = { data: [], - lastIndex: 0, + lastTimeIndex: undefined, nextAggregateIndex: 0 }; } @@ -128,7 +128,7 @@ var MetricsProvider = // an array of all available aggregation levels and metadata this.aggregationLevels = Object.keys(this._aggregation); this.minimumAggregationInterval = +this.aggregationLevels[0]; - this.highestAggregationKey = this.aggregationLevels.slice(-1)[0]; + this.highestAggregationKey = _.last(this.aggregationLevels); // this is an exploit; arrays are zero-based so -1 index will be undefined // that is intentional - when the zoomLevelKey is undefined, we use real-time @@ -137,7 +137,10 @@ var MetricsProvider = // remember when all this started this._startTime = Date.now(); - this._lastAggregation = this._startTime; + this._lastAggregationTime = this._startTime; + + // this is where we stopped aggregating + this._lastAggregationIndex = 0; }; EventEmitter.call(this); @@ -152,27 +155,29 @@ var MetricsProvider = screen.on("metrics", this._onMetrics.bind(this)); // zoom callback handler - screen.on("zoomGraphs", function zoomGraphs(zoom) { - // apply zoom delta while staying in boundaries - this.zoomLevel = - _.clamp(this.zoomLevel + zoom, -1, this.aggregationLevels.length - 1); - - // now, reflect the change to the zoom level - this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; - - // if there is an aggregate at this level, but there is no data, go back a level - while ( - this.zoomLevelKey - && this._aggregation[this.zoomLevelKey].data.length === 0) { - this.zoomLevel = Math.max(this.zoomLevel - 1, -1); - this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; - } - }.bind(this)); + screen.on("zoomGraphs", this.adjustZoomLevel.bind(this)); }; // MetricsProvider inherits from EventEmitter MetricsProvider.prototype = Object.create(EventEmitter.prototype); +// the zoomGraphs callback handler +MetricsProvider.prototype.adjustZoomLevel = function adjustZoomLevel(zoom) { + // apply zoom delta while staying in boundaries + this.zoomLevel = _.clamp(this.zoomLevel + zoom, -1, this.aggregationLevels.length - 1); + + // now, reflect the change to the zoom level + this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; + + // if there is an aggregate at this level, but there is no data, go back a level + while ( + this.zoomLevelKey + && this._aggregation[this.zoomLevelKey].data.length === 0) { + this.zoomLevel = Math.max(this.zoomLevel - 1, -1); + this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; + } +}; + /** * Given a metric data object, construct an initialized average. * @@ -213,7 +218,6 @@ var getInitializedAverage = var aggregateMetrics = function aggregateMetrics(currentTime, metricData) { var aggregateKey; - var lastTimeIndex; /** * Given the current time and last time index, add any missing logical @@ -293,29 +297,6 @@ var aggregateMetrics = return averagedAggregate; }; - /** - * Reclaim no longer necessary metadata storage space. Once the highest - * aggregation level configured has been computed, the metadata for all - * those elements are no longer necessary and be reclaimed. - * - * @param {Object[]} rows - * The array reference. - * - * @param {Number} startIndex - * The start of the array where metadata can be discarded. - * - * @param {Number} endIndex - * The end of the array where metadata can be discarded. - * - * @returns {void} - */ - var reclaimMetadataStorage = - function reclaimMetadataStorage(rows, startIndex, endIndex) { - for (var rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) { - delete rows[rowIndex].__timeIndices; - } - }; - /** * Process one row of metric data into aggregate. * @@ -335,15 +316,19 @@ var aggregateMetrics = var processRow = function processRow(row, rowIndex, rows) { var averagedAggregate; - // get the time index of the aggregate (this is precomputed metadata) - var thisTimeIndex = row.__timeIndices[aggregateKey]; + // get the time index of the aggregate + var thisTimeIndex = Math.floor((currentTime - this._startTime) / +aggregateKey); // when the time index changes, average the data for the time band - if (thisTimeIndex !== lastTimeIndex) { + if (thisTimeIndex !== this._aggregation[aggregateKey].lastTimeIndex) { // except for the first one - if (lastTimeIndex !== undefined) { + if (this._aggregation[aggregateKey].lastTimeIndex !== undefined) { // add in any missing logical time slots - addMissingTimeSlots.call(this, thisTimeIndex, lastTimeIndex); + addMissingTimeSlots.call( + this, + thisTimeIndex, + this._aggregation[aggregateKey].lastTimeIndex + ); // get the average across the discovered time band averagedAggregate = getAveragedAggregate( @@ -361,21 +346,12 @@ var aggregateMetrics = this.emit("metrics", averagedAggregate); } - // we can reclaim some storage now since the time metadata is not needed anymore - if (aggregateKey === this.highestAggregationKey) { - reclaimMetadataStorage( - rows, - this._aggregation[aggregateKey].nextAggregateIndex, - rowIndex - 1 - ); - } - // now we know where the next aggregate begins this._aggregation[aggregateKey].nextAggregateIndex = rowIndex; } // level-break - lastTimeIndex = thisTimeIndex; + this._aggregation[aggregateKey].lastTimeIndex = thisTimeIndex; } }; @@ -383,51 +359,16 @@ var aggregateMetrics = for (aggregateKey in this._aggregation) { // iterate through metrics, beginning where we left off for ( - var rowIndex = this._aggregation[aggregateKey].lastIndex; + var rowIndex = this._lastAggregationIndex; rowIndex < this._metrics.length; rowIndex++ ) { processRow.call(this, this._metrics[rowIndex], rowIndex, this._metrics); } - - // remember where we will begin again for this aggregate level - this._aggregation[aggregateKey].lastIndex = this._metrics.length; - - // reset for the next value - lastTimeIndex = undefined; } - }; -/** - * Given a metric data point and a point in time, apply time metadata. - * - * @param {Object} metric - * The metric data point received. - * - * @param {Number} currentTime - * The current moment in time. - * - * @this MetricsProvider - * - * @returns {Object} - * The metric data point object is returned with time metadata applied. - */ -var applyMetricTimeMetadata = - function applyMetricTimeMetadata(metric, currentTime) { - var metadata = { - __timeIndices: {} - }; - - // for all the aggregation levels, stamp each metric with its logical time indices - for (var aggregateKey in this._aggregation) { - // take the difference of now and start then divide by units of time and - // drop any remainder; this is the logical time index for that unit of measure - metadata.__timeIndices[aggregateKey] = - Math.floor((currentTime - this._startTime) / +aggregateKey); - } - - // now apply the metric data to the time metadata - return Object.assign(metadata, metric); + // remember where we will begin again + this._lastAggregationIndex = this._metrics.length; }; /** @@ -443,21 +384,16 @@ MetricsProvider.prototype._onMetrics = // get the current moment in time var currentTime = Date.now(); - // attach the current clock reading and metedata to the metrics - this._metrics.push(applyMetricTimeMetadata.call(this, data, currentTime)); + // capture the metrics + this._metrics.push(data); // one time, build an empty average - used for missing time slots if (!this.emptyAverage) { this.emptyAverage = getInitializedAverage(data); } - // see if it is time once again to aggregate some data - if (currentTime - this._lastAggregation >= this.minimumAggregationInterval) { - aggregateMetrics.call(this, currentTime, data); - - // aggregation is complete; save this position in time - this._lastAggregation = currentTime; - } + // run aggregation process + aggregateMetrics.call(this, currentTime, data); // if we are showing aggregate data, that is emitted in the aggregateMetrics // function; otherwise, emit the newly received metric data here diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 31dd68d..b4ca6af 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -21,7 +21,7 @@ describe("MetricsProvider", function () { var mockTimeInterval = 2500; var metricsRequiredToAchieveHighestAggregation = - +AGGREGATE_TIME_LEVELS.slice(-1)[0] / mockTimeInterval; + _.last(AGGREGATE_TIME_LEVELS) / mockTimeInterval; var mockMetrics = []; var mockMetricCount; @@ -82,7 +82,7 @@ describe("MetricsProvider", function () { .to.be.an("object") .that.deep.equals({ data: [], - lastIndex: 0, + lastTimeIndex: undefined, nextAggregateIndex: 0 }); }); @@ -103,7 +103,7 @@ describe("MetricsProvider", function () { .to.be.an("object") .with.property("highestAggregationKey") .which.is.a("string") - .that.equals(AGGREGATE_TIME_LEVELS.slice(-1)[0]); + .that.equals(_.last(AGGREGATE_TIME_LEVELS)); expect(metricsProvider) .to.be.an("object") @@ -124,10 +124,16 @@ describe("MetricsProvider", function () { expect(metricsProvider) .to.be.an("object") - .with.property("_lastAggregation") + .with.property("_lastAggregationTime") .which.is.a("number") .that.equals(mockStart); + expect(metricsProvider) + .to.be.an("object") + .with.property("_lastAggregationIndex") + .which.is.a("number") + .that.equals(0); + expect(metricsProvider) .to.be.an("object") .with.property("_metrics") @@ -171,30 +177,6 @@ describe("MetricsProvider", function () { .with.property("metricB") .with.property("valueB") .that.equals(mockMetrics[index].metricB.valueB); - - // verify the memory deallocation logic is working - if (index < metricsRequiredToAchieveHighestAggregation - 1) { - expect(value) - .to.not.have.property("__timeIndices"); - } - - // for those that are not deallocated, verify those properties - if (index >= metricsRequiredToAchieveHighestAggregation - 1) { - expect(value) - .to.have.property("__timeIndices"); - - _.each(AGGREGATE_TIME_LEVELS, function (level) { - // reverse-engineer the expected time index - var timeIndex = Math.floor((index + 1) * mockTimeInterval / +level); - - expect(value) - .to.be.an("object") - .with.property("__timeIndices") - .with.property(level) - .which.is.a("number") - .that.equals(timeIndex); - }); - } }); _.each(metricsProvider._aggregation, function (value, key) { From 7b373a1ff1e5c33cdeca3e3def6e5f66e485693a Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Sun, 23 Jul 2017 16:11:05 -0700 Subject: [PATCH 10/21] chore(#64) converted to inline aggregation --- lib/providers/metrics-provider.js | 17 +++++++---------- test/lib/providers/metrics-provider.spec.js | 12 ------------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index f3eddf6..cb7e450 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -127,7 +127,6 @@ var MetricsProvider = // an array of all available aggregation levels and metadata this.aggregationLevels = Object.keys(this._aggregation); - this.minimumAggregationInterval = +this.aggregationLevels[0]; this.highestAggregationKey = _.last(this.aggregationLevels); // this is an exploit; arrays are zero-based so -1 index will be undefined @@ -137,7 +136,6 @@ var MetricsProvider = // remember when all this started this._startTime = Date.now(); - this._lastAggregationTime = this._startTime; // this is where we stopped aggregating this._lastAggregationIndex = 0; @@ -358,17 +356,16 @@ var aggregateMetrics = // iterate over the configured aggregation time buckets for (aggregateKey in this._aggregation) { // iterate through metrics, beginning where we left off - for ( - var rowIndex = this._lastAggregationIndex; - rowIndex < this._metrics.length; - rowIndex++ - ) { - processRow.call(this, this._metrics[rowIndex], rowIndex, this._metrics); - } + processRow.call( + this, + this._metrics[this._lastAggregationIndex], + this._lastAggregationIndex, + this._metrics + ); } // remember where we will begin again - this._lastAggregationIndex = this._metrics.length; + this._lastAggregationIndex++; }; /** diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index b4ca6af..3a3eedd 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -93,12 +93,6 @@ describe("MetricsProvider", function () { .which.is.an("array") .that.deep.equals(AGGREGATE_TIME_LEVELS); - expect(metricsProvider) - .to.be.an("object") - .with.property("minimumAggregationInterval") - .which.is.a("number") - .that.equals(+AGGREGATE_TIME_LEVELS[0]); - expect(metricsProvider) .to.be.an("object") .with.property("highestAggregationKey") @@ -122,12 +116,6 @@ describe("MetricsProvider", function () { .which.is.a("number") .that.equals(mockStart); - expect(metricsProvider) - .to.be.an("object") - .with.property("_lastAggregationTime") - .which.is.a("number") - .that.equals(mockStart); - expect(metricsProvider) .to.be.an("object") .with.property("_lastAggregationIndex") From 6dd1f3b21525cd2ce5051531334405c45de0a998 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Mon, 24 Jul 2017 12:36:18 -0700 Subject: [PATCH 11/21] test(#64) 100% code coverage MetricsProvider --- lib/providers/metrics-provider.js | 66 ++-- test/lib/providers/metrics-provider.spec.js | 324 ++++++++++++++++++-- 2 files changed, 325 insertions(+), 65 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index cb7e450..bb2b3e1 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -36,10 +36,8 @@ * When two or more array elements have a common time index, they form a time band. * * To efficiently perform the average, care is taken to reduce the number of - * CPU cycles required to analyze the data. To do this, any calculation that is - * done on a data point (such as its time index) is stored. This is necessary - * because a given data point can be considered for aggregation into any number - * of configured time aggregations. + * CPU cycles required to analyze the data. To accomplish this, the program + * calculates the average inline with the originating transaction. * * Also, to prevent re-aggregating the data needlessly, each aggregate level * maintains its position in the base array, thereby keeping track of where it @@ -121,7 +119,7 @@ var MetricsProvider = this._aggregation[AGGREGATE_TIME_LEVELS[index]] = { data: [], lastTimeIndex: undefined, - nextAggregateIndex: 0 + lastAggregateIndex: 0 }; } @@ -218,35 +216,26 @@ var aggregateMetrics = var aggregateKey; /** - * Given the current time and last time index, add any missing logical + * Given the current time time index, add any missing logical * time slots to have a complete picture of data. * * @param {Number} currentTimeIndex * The time index currently being processed. * - * @param {Number} previousTimeIndex - * The time index previously processed. - * * @this MetricsProvider * * @returns {void} */ var addMissingTimeSlots = - function addMissingTimeSlots(currentTimeIndex, previousTimeIndex) { - // compute the delta in the two logical time slots - var missingTimeSlots = currentTimeIndex - previousTimeIndex; - - // see if there is a gap in time - if (missingTimeSlots > 1) { - // add empty spots for missed time - while (--missingTimeSlots > 0) { - // populate these missing time slots with an empty aggregate - this._aggregation[aggregateKey].data.push(this.emptyAverage); - - // emit to keep the display in sync - if (aggregateKey === this.zoomLevelKey) { - this.emit("metrics", this.emptyAverage); - } + function addMissingTimeSlots(currentTimeIndex) { + var aggregateIndex = this._aggregation[aggregateKey].data.length; + + while (aggregateIndex < currentTimeIndex) { + this._aggregation[aggregateKey].data[aggregateIndex++] = this.emptyAverage; + + // emit to keep the display in sync + if (aggregateKey === this.zoomLevelKey) { + this.emit("metrics", this.emptyAverage); } } }; @@ -298,9 +287,6 @@ var aggregateMetrics = /** * Process one row of metric data into aggregate. * - * @param {Object} row - * The row being processed. - * * @param {Number} rowIndex * The index of the row being processed. * @@ -311,32 +297,29 @@ var aggregateMetrics = * * @returns {void} */ - var processRow = function processRow(row, rowIndex, rows) { + var processRow = function processRow(rowIndex, rows) { var averagedAggregate; + var lastTimeIndex = this._aggregation[aggregateKey].lastTimeIndex; // get the time index of the aggregate - var thisTimeIndex = Math.floor((currentTime - this._startTime) / +aggregateKey); + var currentTimeIndex = Math.floor((currentTime - this._startTime) / +aggregateKey); // when the time index changes, average the data for the time band - if (thisTimeIndex !== this._aggregation[aggregateKey].lastTimeIndex) { + if (currentTimeIndex !== lastTimeIndex) { // except for the first one - if (this._aggregation[aggregateKey].lastTimeIndex !== undefined) { + if (lastTimeIndex !== undefined) { // add in any missing logical time slots - addMissingTimeSlots.call( - this, - thisTimeIndex, - this._aggregation[aggregateKey].lastTimeIndex - ); + addMissingTimeSlots.call(this, lastTimeIndex); // get the average across the discovered time band averagedAggregate = getAveragedAggregate( rows, - this._aggregation[aggregateKey].nextAggregateIndex, + this._aggregation[aggregateKey].lastAggregateIndex, rowIndex - 1 ); - // add the average - this._aggregation[aggregateKey].data.push(averagedAggregate); + // place the average + this._aggregation[aggregateKey].data[lastTimeIndex] = averagedAggregate; // when in aggregate mode and the aggregate just produced is in the current view // emit the average now @@ -345,11 +328,11 @@ var aggregateMetrics = } // now we know where the next aggregate begins - this._aggregation[aggregateKey].nextAggregateIndex = rowIndex; + this._aggregation[aggregateKey].lastAggregateIndex = rowIndex; } // level-break - this._aggregation[aggregateKey].lastTimeIndex = thisTimeIndex; + this._aggregation[aggregateKey].lastTimeIndex = currentTimeIndex; } }; @@ -358,7 +341,6 @@ var aggregateMetrics = // iterate through metrics, beginning where we left off processRow.call( this, - this._metrics[this._lastAggregationIndex], this._lastAggregationIndex, this._metrics ); diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 3a3eedd..a5d120b 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -16,18 +16,34 @@ describe("MetricsProvider", function () { var testContainer; var metricsProvider; - var mockStart = 1000000; - var mockNow = 1000000; - var mockTimeInterval = 2500; + var stubNow; + var mockStart; + var mockNow; + var mockTimeInterval; - var metricsRequiredToAchieveHighestAggregation = - _.last(AGGREGATE_TIME_LEVELS) / mockTimeInterval; - - var mockMetrics = []; + var mockMetrics; var mockMetricCount; - before(function () { + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + mockStart = 10000000; + mockNow = mockStart; + mockTimeInterval = 2500; + + stubNow = sandbox.stub(Date, "now", function () { + var currentTime = mockNow; + mockNow += mockTimeInterval; + return currentTime; + }); + + testContainer = utils.getTestContainer(sandbox); + metricsProvider = new MetricsProvider(testContainer.screen); + // generate some fake metrics for processing + var metricsRequiredToAchieveHighestAggregation = + +_.last(AGGREGATE_TIME_LEVELS) / mockTimeInterval; + mockMetrics = []; mockMetricCount = Math.ceil(Math.random() * 500 + metricsRequiredToAchieveHighestAggregation); @@ -44,20 +60,6 @@ describe("MetricsProvider", function () { } }); - beforeEach(function () { - sandbox = sinon.sandbox.create(); - - mockNow = mockStart; - - sandbox.stub(Date, "now", function () { - mockNow += mockTimeInterval; - return mockNow - mockTimeInterval; - }); - - testContainer = utils.getTestContainer(sandbox); - metricsProvider = new MetricsProvider(testContainer.screen); - }); - afterEach(function () { sandbox.restore(); }); @@ -83,7 +85,7 @@ describe("MetricsProvider", function () { .that.deep.equals({ data: [], lastTimeIndex: undefined, - nextAggregateIndex: 0 + lastAggregateIndex: 0 }); }); @@ -134,6 +136,13 @@ describe("MetricsProvider", function () { // due to the quantity of data processed, notice the .timeout at the bottom - this allows // for a longer running test it("retains metrics received, while aggregating them into time buckets", function () { + // this test case utilizes a uniform time-event approach to validation + // an event is generated exactly evenly until a sufficient number of them have + // been computed + + // given the uniform nature of this data set, the test case can verify the + // aggregation logic works by comparing raw input data to an expected average + // load with mock metric data already computed _.each(mockMetrics, function (value) { metricsProvider._onMetrics(value); @@ -221,5 +230,274 @@ describe("MetricsProvider", function () { }); }); }).timeout(10000); + + it("aggregates data for non-uniform data sets too", function () { + // this test case differs from the previous in that instead of using a perfectly + // uniform set of data, this one computes "time bands", which represent events that + // occur sporadically, but still within a known number of data elements + + // this is captured so that the averages can then still be compared against + // randomly computed data + + // this data set will also produce gaps in time to test the gap logic + + var mockMetricsThisTimeBand; + var timeBands = []; + var totalTimeBands = Math.ceil(+_.last(AGGREGATE_TIME_LEVELS) / +AGGREGATE_TIME_LEVELS[0]); + var maximumTimeGaps = 10; + var timeGaps = 0; + + mockNow = mockStart; + var timeBandStart = mockNow; + + mockMetrics = []; + + stubNow.restore(); + + mockTimeInterval = +AGGREGATE_TIME_LEVELS[0]; + stubNow = sandbox.stub(Date, "now", function () { + var currentTime = mockNow; + mockNow += Math.floor(mockTimeInterval / mockMetricsThisTimeBand) - 1; + return currentTime; + }); + + while (timeBands.length < totalTimeBands) { + mockMetricsThisTimeBand = Math.floor(Math.random() * 5); + + if (timeGaps < maximumTimeGaps && Math.random() < 0.1) { + timeGaps++; + mockMetricsThisTimeBand = 0; + } + + var timeBand = { + count: mockMetricsThisTimeBand, + data: [], + startIndex: mockMetrics.length, + endIndex: mockMetrics.length + mockMetricsThisTimeBand + }; + + for (var metricIndex = 0; metricIndex < timeBand.count; metricIndex++) { + var mockMetric = { + metricA: { + valueA: Math.random() * 100 + }, + metricB: { + valueA: Math.random() * 100, + valueB: Math.random() * 100 + } + }; + + metricsProvider._onMetrics(mockMetric); + mockMetrics.push(mockMetric); + + timeBand.data.push(mockMetric); + } + + timeBands.push(timeBand); + + mockNow = timeBandStart + mockTimeInterval * timeBands.length; + } + + // the number of data points retained must match the number provided + expect(metricsProvider) + .to.be.an("object") + .with.property("_metrics") + .which.is.an("array") + .that.has.lengthOf(mockMetrics.length); + + // now, examine each metric + _.each(metricsProvider._metrics, function (value, index) { + expect(value) + .to.be.an("object") + .with.property("metricA") + .with.property("valueA") + .that.equals(mockMetrics[index].metricA.valueA); + + expect(value) + .to.be.an("object") + .with.property("metricB") + .with.property("valueA") + .that.equals(mockMetrics[index].metricB.valueA); + + expect(value) + .to.be.an("object") + .with.property("metricB") + .with.property("valueB") + .that.equals(mockMetrics[index].metricB.valueB); + }); + + _.each(timeBands, function (value, index) { + var averageA = { + valueA: 0 + }; + + var averageB = { + valueA: 0, + valueB: 0 + }; + + var metricCount = value.endIndex - value.startIndex; + + // recompute the average manually + _.each(value.data, function (metric) { + if (metricCount === 0) { + return; + } + + averageA.valueA += metric.metricA.valueA / metricCount; + + averageB.valueA += metric.metricB.valueA / metricCount; + averageB.valueB += metric.metricB.valueB / metricCount; + }); + + var row = metricsProvider._aggregation[AGGREGATE_TIME_LEVELS[0]].data[index]; + + // the final row only becomes defined when a row after it (in time logic) appears + // so it stands to reason that the final row will be undefined + if (index >= metricsProvider._aggregation[AGGREGATE_TIME_LEVELS[0]].data.length) { + expect(row).to.be.undefined; + } else { + // verify + expect(row) + .to.be.an("object") + .with.property("metricA") + .with.property("valueA") + .which.is.a("number") + .that.equals(+averageA.valueA.toFixed(1)); + + expect(row) + .to.be.an("object") + .with.property("metricB") + .with.property("valueA") + .which.is.a("number") + .that.equals(+averageB.valueA.toFixed(1)); + + expect(row) + .to.be.an("object") + .with.property("metricB") + .with.property("valueB") + .which.is.a("number") + .that.equals(+averageB.valueB.toFixed(1)); + } + }); + }).timeout(10000); + }); + + describe("adjustZoomLevel", function () { + it("allows for changing the zoom level", function () { + // try to adjust the zoom right now + metricsProvider.adjustZoomLevel(1); + + // there is no data, so it shouldn't change + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevel") + .which.is.a("number") + .that.equals(-1); + + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevelKey") + .that.is.undefined; + + // reset mock time + mockTimeInterval = 2500; + + // reuse some mockMetrics above + metricsProvider._onMetrics(mockMetrics[0]); // 2500ms + metricsProvider._onMetrics(mockMetrics[1]); // 5000ms + metricsProvider._onMetrics(mockMetrics[2]); // 7500ms + + // given the uniform data, this should allow for one higher + // aggregation + metricsProvider.adjustZoomLevel(1); + + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevel") + .which.is.a("number") + .that.equals(0); + + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevelKey") + .which.is.a("string") + .that.equals(AGGREGATE_TIME_LEVELS[0]); + + // zooming again should have no change + metricsProvider.adjustZoomLevel(1); + + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevel") + .which.is.a("number") + .that.equals(0); + + expect(metricsProvider) + .to.be.an("object") + .with.property("zoomLevelKey") + .which.is.a("string") + .that.equals(AGGREGATE_TIME_LEVELS[0]); + + // getting metrics should come from the aggregate now + var metrics = metricsProvider.getMetrics(3); + + expect(metrics) + .to.be.an("array") + .that.deep.equals(metricsProvider._aggregation[metricsProvider.zoomLevelKey].data); + + // receiving metrics now would cause an emit + sandbox.stub(metricsProvider, "emit", function (key, data) { + expect(key) + .to.be.a("string") + .that.equals("metrics"); + + expect(data) + .to.be.an("object") + .that.deep.equals(_.last(metricsProvider._aggregation[metricsProvider.zoomLevelKey].data)); + }); + + metricsProvider._onMetrics(mockMetrics[3]); + + // if time were to be skipped, some missing time slots should be generated too + metricsProvider._onMetrics(mockMetrics[4]); + + // advance the time a few slots + mockNow += mockTimeInterval * 10; + + metricsProvider._onMetrics(mockMetrics[4]); + metricsProvider._onMetrics(mockMetrics[5]); + metricsProvider._onMetrics(mockMetrics[6]); + }); + }); + + describe("getXAxis", function () { + it("should return labels appropriate for their highest measure of time", function () { + var limit = 10; + var axis = metricsProvider.getXAxis(limit); + + var expected = _.reverse(_.times(limit, function (index) { + return index + "s"; + })); + + expect(axis) + .to.be.an("array") + .that.deep.equals(expected); + + // lets get some aggregation for another zoom level + _.each(_.slice(mockMetrics, 0, 100), function (value) { + metricsProvider._onMetrics(value); + }); + + // zoom + metricsProvider.adjustZoomLevel(2); + + axis = metricsProvider.getXAxis(limit); + expected = _.reverse(["0s", "10s", "20s", "30s", "40s", "50s", "1m", "1.2m", "1.3m", "1.5m"]); + + expect(axis) + .to.be.an("array") + .that.deep.equals(expected); + }); }); }); From 939ba75c5c219ddb039c755c8959f3c4821b8249 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Mon, 24 Jul 2017 12:44:24 -0700 Subject: [PATCH 12/21] docs(#64) bumped version and added changelog entry --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c419b..4381316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change log +## [v0.5.1] - 2017-07-24 +- **Added:** Longer history for graphs [\#64] + ## [v0.4.1] - 2017-03-21 - **Added:** Historical memory usage graph [\#63] - **Added:** Support for log filtering - see README [\#62] diff --git a/package.json b/package.json index a2c26c9..9c9cd42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nodejs-dashboard", - "version": "0.4.1", + "version": "0.5.0", "description": "Telemetry dashboard for node.js apps from the terminal!", "keywords": [ "dashboard", From 403853a45cadbd4be8a2763de0c7d0f4c8229926 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Mon, 24 Jul 2017 15:23:51 -0700 Subject: [PATCH 13/21] fix(#64) corrections for code review --- CHANGELOG.md | 3 --- lib/constants.js | 6 +++--- package.json | 4 ++-- test/lib/generate-layouts.spec.js | 8 +++----- test/lib/providers/metrics-provider.spec.js | 4 +++- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4381316..61c419b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,5 @@ # Change log -## [v0.5.1] - 2017-07-24 -- **Added:** Longer history for graphs [\#64] - ## [v0.4.1] - 2017-03-21 - **Added:** Historical memory usage graph [\#63] - **Added:** Support for log filtering - see README [\#62] diff --git a/lib/constants.js b/lib/constants.js index a8383b8..64ce926 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -2,12 +2,12 @@ // these define the time levels that data will be aggregated into // each number is in milliseconds - +// // since these are used as object keys, they are strings - +// // For example, 5000 = 5s and means that one of the aggregate levels // will be at 5s increments of time - +// // 300000 = 30s and the corresponding zoom will show 30s aggregates var AGGREGATE_TIME_LEVELS = [ "5000", diff --git a/package.json b/package.json index 9c9cd42..c400dfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nodejs-dashboard", - "version": "0.5.0", + "version": "0.4.1", "description": "Telemetry dashboard for node.js apps from the terminal!", "keywords": [ "dashboard", @@ -15,7 +15,7 @@ "lint": "eslint .", "test": "npm run lint && npm run test-only", "test-only": "mocha -c --require test/setup.js --recursive ./test", - "test-app": "./bin/nodejs-dashboard.js node test/app/index.js", + "test-app": "node bin/nodejs-dashboard.js -- node test/app/index.js", "coverage": "istanbul cover --include-all-sources node_modules/mocha/bin/_mocha -- -c --recursive --require test/setup.js ./test" }, "repository": { diff --git a/test/lib/generate-layouts.spec.js b/test/lib/generate-layouts.spec.js index dea7832..6672625 100644 --- a/test/lib/generate-layouts.spec.js +++ b/test/lib/generate-layouts.spec.js @@ -5,13 +5,11 @@ var expect = require("chai").expect; // Strict mode leads to odd bug in Node < 0.12 var mockRequire = require("mock-require"); -// to ensure cross-platform consistency with mixed Posix & Win32 paths, use normalize() -// ideally, this would be taken care of as a fix for mockRequire. -// see https://github.com/boblauer/mock-require/issues/20 -var normalize = require("path").normalize; +// to ensure cross-platform consistency with mixed Posix & Win32 paths, use path.resolve() +var resolve = require("path").resolve; var mock = function (path, obj) { - return mockRequire(normalize(process.cwd() + "/" + path), obj); + return mockRequire(resolve(path), obj); }; var generateLayouts = require("../../lib/generate-layouts"); diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index a5d120b..6c01c6f 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -454,7 +454,9 @@ describe("MetricsProvider", function () { expect(data) .to.be.an("object") - .that.deep.equals(_.last(metricsProvider._aggregation[metricsProvider.zoomLevelKey].data)); + .that.deep.equals( + _.last(metricsProvider._aggregation[metricsProvider.zoomLevelKey].data) + ); }); metricsProvider._onMetrics(mockMetrics[3]); From b7eec6c56bfbe8f28f38fd5e3b5f4bb3eb72f515 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Mon, 24 Jul 2017 15:43:21 -0700 Subject: [PATCH 14/21] docs(#64) added notes to Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c419b..45e5680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change log +## [Unreleased] - 2017-07-24 +- **Added:** Longer history for graphs [\#64] + ## [v0.4.1] - 2017-03-21 - **Added:** Historical memory usage graph [\#63] - **Added:** Support for log filtering - see README [\#62] From ae31391a9fbc153d7326d65fb81864f9a21cc5f6 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Tue, 25 Jul 2017 08:52:11 -0700 Subject: [PATCH 15/21] fix(#64) no more forced layouts --- lib/dashboard.js | 6 ++--- lib/providers/metrics-provider.js | 2 ++ lib/views/base-line-graph.js | 40 +++++++++++++++++++++++++++++++ lib/views/cpu-view.js | 10 ++++++++ lib/views/eventloop-view.js | 21 ++++++++++++++++ lib/views/memory-graph-view.js | 13 ++++++++++ 6 files changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/dashboard.js b/lib/dashboard.js index 6431316..536c177 100644 --- a/lib/dashboard.js +++ b/lib/dashboard.js @@ -79,7 +79,7 @@ Dashboard.prototype._configureKeys = function () { this.screen.key(["up", "down"], _.throttle(function (ch, key) { var zoom = key.name === "down" ? -1 : 1; this.screen.emit("zoomGraphs", zoom); - this._showLayout(this.currentLayout, true); + this.screen.render(); }.bind(this), THROTTLE_TIMEOUT)); }; @@ -100,8 +100,8 @@ var VIEW_MAP = { eventLoop: EventLoopView }; -Dashboard.prototype._showLayout = function (id, forced) { - if (this.currentLayout === id && !forced) { +Dashboard.prototype._showLayout = function (id) { + if (this.currentLayout === id) { return; } _.each(this.views, function (view) { diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index bb2b3e1..207133d 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -172,6 +172,8 @@ MetricsProvider.prototype.adjustZoomLevel = function adjustZoomLevel(zoom) { this.zoomLevel = Math.max(this.zoomLevel - 1, -1); this.zoomLevelKey = this.aggregationLevels[this.zoomLevel]; } + + this.emit("metricData"); }; /** diff --git a/lib/views/base-line-graph.js b/lib/views/base-line-graph.js index f13cfbe..6472e3c 100644 --- a/lib/views/base-line-graph.js +++ b/lib/views/base-line-graph.js @@ -38,8 +38,10 @@ var BaseLineGraph = function BaseLineGraph(options) { this._createGraph(options); this._boundOnEvent = this.onEvent.bind(this); + this._boundOnMetricData = this.onMetricData.bind(this); options.metricsProvider.on("metrics", this._boundOnEvent); + options.metricsProvider.on("metricData", this._boundOnMetricData); }; BaseLineGraph.prototype = Object.create(BaseView.prototype); @@ -48,6 +50,10 @@ BaseLineGraph.prototype.onEvent = function () { throw new Error("BaseLineGraph onEvent should be overwritten"); }; +BaseLineGraph.prototype.onMetricData = function () { + throw new Error("BaseLineGraph onMetricData should be overwritten"); +}; + BaseLineGraph.prototype._isHighwater = function (name) { return this.seriesOptions[name].highwater; }; @@ -71,6 +77,38 @@ BaseLineGraph.prototype.update = function (values) { this.node.setData(_.values(this.series)); }; +BaseLineGraph.prototype.changeMetricData = function (mapper) { + var data = mapper(this.metricsProvider.getMetrics(this.limit)); + + _.each(data[0], function (value, seriesName) { + if (!this.series[seriesName]) { + return; + } + if (this._isHighwater(seriesName)) { + this.series[seriesName].y = _.times(this.limit, _.constant(value)); + } else { + this.series[seriesName].y = _.times(this.limit, _.constant(0)); + } + this.series[seriesName].x = this.metricsProvider.getXAxis(this.layoutConfig.limit); + }.bind(this)); + + _.each(data, function (values) { + _.each(values, function (value, seriesName) { + if (!this.series[seriesName]) { + return; + } + if (!this._isHighwater(seriesName)) { + this.series[seriesName].y.shift(); + this.series[seriesName].y.push(value); + } + }.bind(this)); + }.bind(this)); + + this._updateLabel(); + + this.node.setData(_.values(this.series)); +}; + BaseLineGraph.prototype._updateLabel = function () { // use view label + series labels/data @@ -116,8 +154,10 @@ BaseLineGraph.prototype.destroy = function () { BaseView.prototype.destroy.call(this); this.metricsProvider.removeListener("metrics", this._boundOnEvent); + this.metricsProvider.removeListener("metricData", this._boundOnMetricData); this._boundOnEvent = null; + this._boundOnMetricData = null; this.metricsProvider = null; }; diff --git a/lib/views/cpu-view.js b/lib/views/cpu-view.js index f9ae096..04482ad 100644 --- a/lib/views/cpu-view.js +++ b/lib/views/cpu-view.js @@ -27,4 +27,14 @@ CpuView.prototype.onEvent = function (data) { this.update({ cpu: data.cpu.utilization.toFixed(1) }); }; +CpuView.prototype.onMetricData = function () { + var mapper = function mapper(rows) { + return _.map(rows, function (row) { + return { cpu: +row.cpu.utilization.toFixed(1) }; + }); + }; + + this.changeMetricData(mapper); +}; + module.exports = CpuView; diff --git a/lib/views/eventloop-view.js b/lib/views/eventloop-view.js index 892a27f..6a029f5 100644 --- a/lib/views/eventloop-view.js +++ b/lib/views/eventloop-view.js @@ -30,4 +30,25 @@ EventLoopView.prototype.onEvent = function (data) { }); }; +EventLoopView.prototype.onMetricData = function () { + var mapper = function mapper(rows) { + var filter = function filter() { + return _.reduce(rows, function (prev, curr) { + return Math.max(prev, curr.eventLoop.high); + }, 0); + }; + + var maxDelay = filter(); + + return _.map(rows, function (row) { + return { + delay: +row.eventLoop.delay.toFixed(1), + high: +maxDelay.toFixed(1) + }; + }); + }; + + this.changeMetricData(mapper); +}; + module.exports = EventLoopView; diff --git a/lib/views/memory-graph-view.js b/lib/views/memory-graph-view.js index f1ce89d..a689e76 100644 --- a/lib/views/memory-graph-view.js +++ b/lib/views/memory-graph-view.js @@ -34,4 +34,17 @@ MemoryGraphView.prototype.onEvent = function (data) { }); }; +MemoryGraphView.prototype.onMetricData = function () { + var mapper = function mapper(rows) { + return _.map(rows, function (row) { + return { + heap: utils.getPercentUsed(row.mem.heapUsed, row.mem.heapTotal), + resident: utils.getPercentUsed(row.mem.rss, row.mem.systemTotal) + }; + }); + }; + + this.changeMetricData(mapper); +}; + module.exports = MemoryGraphView; From 1b618762c05f0a3e50f88e04c86f526faf40a05c Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Wed, 26 Jul 2017 12:37:09 -0700 Subject: [PATCH 16/21] fix(#64) improved time labels Time labels now appear like digital clocks. --- lib/providers/metrics-provider.js | 80 ++++++++++++++------- package.json | 1 + test/lib/providers/metrics-provider.spec.js | 34 +++++++-- test/lib/views/base-line-graph.spec.js | 4 +- yarn.lock | 4 ++ 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 207133d..6e86c3f 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -87,6 +87,7 @@ */ var EventEmitter = require("events").EventEmitter; +var lpad = require("left-pad"); var _ = require("lodash"); // get the defined aggregation levels @@ -417,29 +418,65 @@ MetricsProvider.prototype.getMetrics = * @returns {String} * A scaled, string-representation of time at the index is returned. */ -var getTimeIndexLabel = +MetricsProvider.prototype.getTimeIndexLabel = function getTimeIndexLabel(timeIndex, aggregateTimeUnits) { - var scaleIndex = 0; + var DIGITS_PER_UNIT = 2; + var timeValue = timeIndex * aggregateTimeUnits; + var timeElements = []; - if (timeIndex === 0) { - return "0s"; + if (timeValue === 0) { + return ":00"; } - // progressively reduce by units of time - while (scaleIndex < TIME_SCALES.length && timeValue >= TIME_SCALES[scaleIndex].divisor) { - timeValue /= TIME_SCALES[scaleIndex++].divisor; - } + _.every(TIME_SCALES, function (timeScale, index, timeScales) { + var timeElement = { + units: timeScale.units, + value: 0 + }; - // convert to one decimal point, if any - if (timeValue !== Math.floor(timeValue)) { - return timeValue.toFixed(1) + TIME_SCALES[scaleIndex - 1].units; - } + // stop reducing when it cannot be divided + if (timeValue < timeScale.divisor) { + return false; + } - // return the label (ex: 2.4m) - // it is noteworthy that I did create the Clash of Clans style label too - // (ex 2m 24s = 2.4m but it didn't look that hot when it was on the screen) - return timeValue + TIME_SCALES[scaleIndex - 1].units; + // don't capture a time element for milliseconds + if (timeScale.units !== "ms") { + // reduce by the divisor + timeElement.value = timeValue / timeScale.divisor; + + // if there are more elements after, take the modulo to get the remainder + if (index < timeScales.length - 1) { + timeElement.value = Math.floor(timeElement.value % timeScales[index + 1].divisor); + } else { + timeElement.value = Math.floor(timeElement.value); + } + + timeElements.push(timeElement); + } + + // reduce + timeValue /= timeScale.divisor; + + return true; + }); + + + return _.reduce(timeElements, function (prev, curr, index) { + switch (curr.units) { + case "s": + return ":" + lpad(curr.value, DIGITS_PER_UNIT, "0"); + case "m": + case "h": + if (index < timeElements.length - 1) { + return (curr.units === "m" ? ":" : " ") + lpad(curr.value, DIGITS_PER_UNIT, "0") + prev; + } else { + return curr.value + prev; + } + default: + return curr.value + curr.units + prev; + } + }, ""); }; /** @@ -452,17 +489,10 @@ var getTimeIndexLabel = * The X-Axis labels array is returned. */ MetricsProvider.prototype.getXAxis = function getXAxis(limit) { - var timeIndex; var xAxis = []; - if (this.zoomLevelKey) { - for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { - xAxis.push(getTimeIndexLabel(timeIndex, +this.zoomLevelKey)); - } - } else { - for (timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { - xAxis.push(timeIndex + "s"); - } + for (var timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { + xAxis.push(this.getTimeIndexLabel(timeIndex, +this.zoomLevelKey || TIME_SCALES[1].divisor)); } return xAxis; diff --git a/package.json b/package.json index c400dfc..ae02ec6 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "commander": "^2.9.0", "cross-spawn": "^4.0.2", "jsonschema": "^1.1.1", + "left-pad": "^1.1.3", "lodash": "^4.16.2", "pidusage": "^1.0.8", "pretty-bytes": "^3.0.1", diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 6c01c6f..37e63b7 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -3,6 +3,7 @@ "use strict"; var expect = require("chai").expect; +var lpad = require("left-pad"); var sinon = require("sinon"); var _ = require("lodash"); @@ -478,9 +479,8 @@ describe("MetricsProvider", function () { var limit = 10; var axis = metricsProvider.getXAxis(limit); - var expected = _.reverse(_.times(limit, function (index) { - return index + "s"; - })); + var expected = + _.reverse([":00", ":01", ":02", ":03", ":04", ":05", ":06", ":07", ":08", ":09"]); expect(axis) .to.be.an("array") @@ -495,11 +495,37 @@ describe("MetricsProvider", function () { metricsProvider.adjustZoomLevel(2); axis = metricsProvider.getXAxis(limit); - expected = _.reverse(["0s", "10s", "20s", "30s", "40s", "50s", "1m", "1.2m", "1.3m", "1.5m"]); + expected = + _.reverse([":00", ":10", ":20", ":30", ":40", ":50", "1:00", "1:10", "1:20", "1:30"]); expect(axis) .to.be.an("array") .that.deep.equals(expected); + + // override zoom (hours) + metricsProvider.zoomLevelKey = _.last(AGGREGATE_TIME_LEVELS); + + // there are 8,760 hours in a day, getting an axis of 10,000 will get us full coverage + axis = metricsProvider.getXAxis(10000); + + // here is the expected (use 9999 not 10000 because the last axis element is zero-based) + var years = Math.floor(metricsProvider.zoomLevelKey * 9999 / (1000 * 60 * 60 * 24 * 365.25)); + var days = Math.floor(metricsProvider.zoomLevelKey * 9999 / (1000 * 60 * 60 * 24) % 365.25); + var hours = Math.floor(metricsProvider.zoomLevelKey * 9999 / (1000 * 60 * 60) % 24); + var minutes = Math.floor(metricsProvider.zoomLevelKey * 9999 / (1000 * 60) % 60); + var seconds = Math.floor(metricsProvider.zoomLevelKey * 9999 / 1000 % 60); + + // build a label + var label = + years + "y" + + days + "d " + + hours + ":" + + lpad(minutes, 2, "0") + ":" + + lpad(seconds, 2, "0"); + + expect(axis[0]) + .to.be.a("string") + .that.equals(label); }); }); }); diff --git a/test/lib/views/base-line-graph.spec.js b/test/lib/views/base-line-graph.spec.js index 315c700..136bbbf 100644 --- a/test/lib/views/base-line-graph.spec.js +++ b/test/lib/views/base-line-graph.spec.js @@ -135,7 +135,7 @@ describe("BaseLineGraph", function () { expect(baseGraph).to.have.deep.property("series.a.y").that.deep.equals([0, 0, 0]); expect(baseGraph).to.have.deep.property("series.high").that.deep.equals({ - x: ["2s", "1s", "0s"], + x: [":02", ":01", ":00"], y: [0, 0, 0], style: { line: "red" } }); @@ -143,7 +143,7 @@ describe("BaseLineGraph", function () { baseGraph.update({ a: 2, high: 4 }); expect(baseGraph).to.have.deep.property("series.a.y").that.deep.equals([0, 0, 2]); expect(baseGraph).to.have.deep.property("series.high").that.deep.equals({ - x: ["2s", "1s", "0s"], + x: [":02", ":01", ":00"], y: [4, 4, 4], style: { line: "red" } }); diff --git a/yarn.lock b/yarn.lock index 1327853..8666a32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1251,6 +1251,10 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" +left-pad@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" From d749914bb1bd3d31ea5035ee046e8b9ac901a3a0 Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Wed, 26 Jul 2017 13:21:06 -0700 Subject: [PATCH 17/21] fix(#64) getTimeIndexLabel is not a member function --- lib/providers/metrics-provider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 6e86c3f..34cd327 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -418,7 +418,7 @@ MetricsProvider.prototype.getMetrics = * @returns {String} * A scaled, string-representation of time at the index is returned. */ -MetricsProvider.prototype.getTimeIndexLabel = +var getTimeIndexLabel = function getTimeIndexLabel(timeIndex, aggregateTimeUnits) { var DIGITS_PER_UNIT = 2; @@ -492,7 +492,7 @@ MetricsProvider.prototype.getXAxis = function getXAxis(limit) { var xAxis = []; for (var timeIndex = limit - 1; timeIndex >= 0; timeIndex--) { - xAxis.push(this.getTimeIndexLabel(timeIndex, +this.zoomLevelKey || TIME_SCALES[1].divisor)); + xAxis.push(getTimeIndexLabel(timeIndex, +this.zoomLevelKey || TIME_SCALES[1].divisor)); } return xAxis; From 07ddccb666a8305e0a5b26a481cfd8692a00423b Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Wed, 26 Jul 2017 14:17:01 -0700 Subject: [PATCH 18/21] fix(#64) lodash has padStart --- lib/providers/metrics-provider.js | 7 ++++--- package.json | 1 - test/lib/providers/metrics-provider.spec.js | 5 ++--- yarn.lock | 4 ---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 34cd327..098c727 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -87,7 +87,6 @@ */ var EventEmitter = require("events").EventEmitter; -var lpad = require("left-pad"); var _ = require("lodash"); // get the defined aggregation levels @@ -465,11 +464,13 @@ var getTimeIndexLabel = return _.reduce(timeElements, function (prev, curr, index) { switch (curr.units) { case "s": - return ":" + lpad(curr.value, DIGITS_PER_UNIT, "0"); + return ":" + _.padStart(curr.value, DIGITS_PER_UNIT, "0"); case "m": case "h": if (index < timeElements.length - 1) { - return (curr.units === "m" ? ":" : " ") + lpad(curr.value, DIGITS_PER_UNIT, "0") + prev; + return (curr.units === "m" ? ":" : " ") + + _.padStart(curr.value, DIGITS_PER_UNIT, "0") + + prev; } else { return curr.value + prev; } diff --git a/package.json b/package.json index ae02ec6..c400dfc 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "commander": "^2.9.0", "cross-spawn": "^4.0.2", "jsonschema": "^1.1.1", - "left-pad": "^1.1.3", "lodash": "^4.16.2", "pidusage": "^1.0.8", "pretty-bytes": "^3.0.1", diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 37e63b7..77bc89a 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -3,7 +3,6 @@ "use strict"; var expect = require("chai").expect; -var lpad = require("left-pad"); var sinon = require("sinon"); var _ = require("lodash"); @@ -520,8 +519,8 @@ describe("MetricsProvider", function () { years + "y" + days + "d " + hours + ":" - + lpad(minutes, 2, "0") + ":" - + lpad(seconds, 2, "0"); + + _.padStart(minutes, 2, "0") + ":" + + _.padStart(seconds, 2, "0"); expect(axis[0]) .to.be.a("string") diff --git a/yarn.lock b/yarn.lock index 8666a32..1327853 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1251,10 +1251,6 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -left-pad@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" - levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" From dbd091e16a20140fb962d6499fedac0c6d14d23e Mon Sep 17 00:00:00 2001 From: mscottx88 Date: Thu, 27 Jul 2017 09:56:37 -0700 Subject: [PATCH 19/21] fix(#64) memory guage view frozen The memory guage view was not getting its "metrics" events and appears frozen. Since it is not a historical graph, the "metrics" event needed to be emitted every time. --- lib/providers/metrics-provider.js | 8 +++----- lib/views/cpu-view.js | 7 ++++++- lib/views/eventloop-view.js | 7 ++++++- lib/views/memory-graph-view.js | 7 ++++++- test/lib/providers/metrics-provider.spec.js | 6 +++++- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 098c727..6cc774b 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -376,11 +376,9 @@ MetricsProvider.prototype._onMetrics = // run aggregation process aggregateMetrics.call(this, currentTime, data); - // if we are showing aggregate data, that is emitted in the aggregateMetrics - // function; otherwise, emit the newly received metric data here - if (!this.zoomLevelKey) { - this.emit("metrics", data); - } + // always emit the data, but send a new arg to indicates whether + // zoom is in effect (and therefore should be ignored) + this.emit("metrics", data, this.zoomLevelKey !== undefined); }; /** diff --git a/lib/views/cpu-view.js b/lib/views/cpu-view.js index 04482ad..17d1356 100644 --- a/lib/views/cpu-view.js +++ b/lib/views/cpu-view.js @@ -23,7 +23,12 @@ CpuView.prototype.getDefaultLayoutConfig = function () { }; }; -CpuView.prototype.onEvent = function (data) { +// discardEvent is needed so that the memory guage view can be +// updated real-time while some graphs are aggregate data +CpuView.prototype.onEvent = function (data, discardEvent) { + if (discardEvent) { + return; + } this.update({ cpu: data.cpu.utilization.toFixed(1) }); }; diff --git a/lib/views/eventloop-view.js b/lib/views/eventloop-view.js index 6a029f5..84600f7 100644 --- a/lib/views/eventloop-view.js +++ b/lib/views/eventloop-view.js @@ -23,7 +23,12 @@ EventLoopView.prototype.getDefaultLayoutConfig = function () { }; }; -EventLoopView.prototype.onEvent = function (data) { +// discardEvent is needed so that the memory guage view can be +// updated real-time while some graphs are aggregate data +EventLoopView.prototype.onEvent = function (data, discardEvent) { + if (discardEvent) { + return; + } this.update({ delay: data.eventLoop.delay, high: data.eventLoop.high diff --git a/lib/views/memory-graph-view.js b/lib/views/memory-graph-view.js index a689e76..6a7f85d 100644 --- a/lib/views/memory-graph-view.js +++ b/lib/views/memory-graph-view.js @@ -26,8 +26,13 @@ MemoryGraphView.prototype.getDefaultLayoutConfig = function () { }; }; -MemoryGraphView.prototype.onEvent = function (data) { +// discardEvent is needed so that the memory guage view can be +// updated real-time while some graphs are aggregate data +MemoryGraphView.prototype.onEvent = function (data, discardEvent) { var mem = data.mem; + if (discardEvent) { + return; + } this.update({ heap: utils.getPercentUsed(mem.heapUsed, mem.heapTotal), resident: utils.getPercentUsed(mem.rss, mem.systemTotal) diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 77bc89a..2bb62f8 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -447,7 +447,11 @@ describe("MetricsProvider", function () { .that.deep.equals(metricsProvider._aggregation[metricsProvider.zoomLevelKey].data); // receiving metrics now would cause an emit - sandbox.stub(metricsProvider, "emit", function (key, data) { + sandbox.stub(metricsProvider, "emit", function (key, data, discardEvent) { + if (discardEvent) { + return; + } + expect(key) .to.be.a("string") .that.equals("metrics"); From b3f73194dfee9f799575c32137e2e0fd3d3e6d70 Mon Sep 17 00:00:00 2001 From: "Michael P. Scott" Date: Tue, 8 Aug 2017 10:10:41 -0700 Subject: [PATCH 20/21] docs(#64) separate MD for aggregation --- CHANGELOG.md | 1 + METRIC-AGGREGATION.md | 81 ++++++++++++++++++++++ images/aggregation-visual.png | Bin 0 -> 14073 bytes images/average-equivalence-equation.png | Bin 0 -> 7244 bytes images/time-index-equation-example.png | Bin 0 -> 9371 bytes images/time-index-equation.png | Bin 0 -> 1848 bytes lib/providers/metrics-provider.js | 88 +----------------------- 7 files changed, 85 insertions(+), 85 deletions(-) create mode 100644 METRIC-AGGREGATION.md create mode 100644 images/aggregation-visual.png create mode 100644 images/average-equivalence-equation.png create mode 100644 images/time-index-equation-example.png create mode 100644 images/time-index-equation.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e5680..0ebd2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ [v0.1.2]: https://github.com/FormidableLabs/nodejs-dashboard/compare/v0.1.1...v0.1.2 [v0.1.1]: https://github.com/FormidableLabs/nodejs-dashboard/compare/v0.1.0...v0.1.1 +[\#64]: https://github.com/FormidableLabs/nodejs-dashboard/pull/64 [\#63]: https://github.com/FormidableLabs/nodejs-dashboard/pull/63 [\#62]: https://github.com/FormidableLabs/nodejs-dashboard/pull/62 [\#59]: https://github.com/FormidableLabs/nodejs-dashboard/pull/59 diff --git a/METRIC-AGGREGATION.md b/METRIC-AGGREGATION.md new file mode 100644 index 0000000..25d73a0 --- /dev/null +++ b/METRIC-AGGREGATION.md @@ -0,0 +1,81 @@ +# Metric Aggregation + +The MetricsProvider class provides metric data for various data points. + +The data provided is streamed in two modes: +* Real Time + * A slice of the data that has been collected over time. +* Aggregate + * Data that has been rolled-up to various time increments. + +## Time Indexes + +The strategy for aggregation centers around time indexes. Imagine an array +of values and each element of that array being a data point. If the data +is real-time, then each index is just one logical second. When the data +is aggregated, each data point is the average of the data, for one logical +grouping of time. + +To determine which data elements from the real-time array are to be grouped, +the difference in time from the start of the process and the moment a data +point was captured is passed through this formula: + + + +Where + +* `i` + * Time Index +* `t[k]` + * Time of element `k` +* `t[s]` + * Start time of process +* `u` + * Time unit of measure (e.g. 5000ms=5s) + +All Times are expressed in milliseconds (ms) and are obtained from Date.now(). + +**Example** + + + +The above example demonstrates that for a 5000ms (5s) aggregate, the time index +for the data point is one. This formula is applied to all data points, so +when many data points share the same logical time index, they can be averaged. +When two or more array elements have a common time index, they form a time band. + + + +In the above visual, the start time `t[s]` is `7581298`. The unit of time is `5000ms`. By applying +the time index equation against each data points' moment in time, the time index values are +derived. For those that match, a time band is formed. These are colorized similarly. + +## Averaging + +To efficiently perform the average, care is taken to reduce the number of +CPU cycles required to analyze the data. To accomplish this, the program +calculates the average inline with the originating transaction. + +Also, to prevent re-aggregating the data needlessly, each aggregate level +maintains its position in the base array, thereby keeping track of where it +left off. This is done using two simple numbers: the last array element +examined and the start of the current time band. + +So that data is still streamed on an event-basis, an aggregate data point is +only captured and emitted when it is complete. To detect when an aggregate +is complete, the algorithm traverses the base real-time array of data, beginning +where it left off for any given aggregate. Walking each element from there +forward, a simplistic level-break algorithm is implemented. As and when a +logical time index changes, this indicates that the previous set of data is +complete. + +Once this occurs, the program then averages the data from the starting index +through the ending index that spans a logical time unit, regardless of aggregate +level (using the time index formula above). + +To ensure no overflows (and therefore `NaN`) are produced, the average is done +using the formula on the left-side of this equivalence: + + + +The sum of memory usage even over a 10s aggregate is enough to produce `NaN`. diff --git a/images/aggregation-visual.png b/images/aggregation-visual.png new file mode 100644 index 0000000000000000000000000000000000000000..646462e1815b6d39b56d7cd4a16321fb417529cb GIT binary patch literal 14073 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 d1>gD2jsNdWqm0dGYX0oqt;da=JnMI&^P)Y#l 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 literal 0 HcmV?d00001 diff --git a/images/time-index-equation-example.png b/images/time-index-equation-example.png new file mode 100644 index 0000000000000000000000000000000000000000..e543a89d26c801cc41c41609a820dc9b843bfdf5 GIT binary patch literal 9371 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 phQj;J-UXfJ<>?BAn1qD+m1q--E83{3s z0-`JlMa!a+unCD)Xdpq9&;i4efXMba!6h!J5hsuF`=xjdJhe#$bS?18rIFdmY3srx6 zBM)IsGI+`Q-X&KtwB~D1ONHC9H`>0W*q?;{Tz4YgQylEH6FQScqLiY@pxgl@`aAJS zr+5~NEY4r-*bwi(Deu~%PF)B(CviNF-!%74?=s- zdc1SmxdECsnkO&@($mv-cizhvw+$pFChi`yeI-3@IPS8>d6LP~VuFH#_Q#Kg93Ko< z>)g4T$`3v$o59zI($h9^ALBJLSFjdGD&PV;BV--p>Wc!uYupIv%Vd@w^vGIvBtJjj zD>XAx7rFR1dn8Z5G|%|L_RmHlktmL8YWSb6I$j7iw$&`WVsy*uPhn)mn{HZsHbo)I zLa9_LZ3Fnth1Cdkyq*(cTE!)+=je3<%?bGrOdmnN%a$$KUdBZ}SiXS|Bnd=Lj%a5S zXg>mEs}qEBHoNS=PzXP6jv|aV3ZUOsr#QmsYoX4+-2rLoV8^818}4(26U}06Y9!?f zgia5$t5BQJslRo9a$((A96x$mGPC|^=VkemPE?uf*cnbOhLusLQF_(TdOK3E$n1h| zH$Uy(0Vm-A#I0Q%Uey0blWMjlFHhxtm89?YF*GOIsxg*GJ-<;A7#PU%Qoag>6I;*8 z&jrTT*xZ`(++T~@gW*b^56o`)mDt7i@!j~`>zxs!6qP5rzZIrp{j>Sdu2(o!8NspO z-s|d2nD7pHiJth)B)h+C59i>iQ~l@1jNVKw~6 zOFjCs1=)B*@ouwK)32c4rzIPUEhk(VNTUpCZ>S6GAe=3~h4*oJD4?6~J($~e{Ig;p zcyotWDJQ~`=R#iGORHe`G7tHVPgt%S+J1L&tK#Qoke?7);N0mE5c$jkRF@9~No@uN zMN4m&a}Iv}dVSP+^IuHFt);!!w~e@vQ`f}&v7zlgs4Q}H9%)HuikGom`S{7GckTgk zxI_wu%T?Euq}1>p$qPib{&=>6f{21kK3&0R0tsrKO^afsQKnI2X zz0P2J)Q(HhOZ-YYsd(SDMrT?kj&z?TccwJmGQ=!#CVx1)gTUcunVblY#wiZlz<1>j z>acPP{NV&XX%TQ{-ynPzCrZ)i8@z`5#vZgBqUu5KhLRX)Bn1f~#LtFuzgnKN+V-sb z$6s^?Qb;Mx?wPJgu?M|d3OUNWr{zDmmf3l;IKo&0MXafBe489Vw+9dDl=!2jth23%%Se)#I?ZM#>zi=7@{evRslTZNWn)SKFZaoz*YgGp z-)JG@;SOkoo(iVjLtOMu`|b+PAk2uSlRvzlbHC9VezAx*Fd&Cs{EDu`C$2}g>A=-M zD+28TE#L?ozn-%qwr!3UT*#j6nPA!Z!3ZNMeJQHrTyy`L=?OI>+XE!_*Fbt#{I*_K zNG<$VIj4fvRXI%Y`wyb(>2tk?B%uM4V zc$H3E_#QPt?<&Iwr#$wEv{S<6y=T59>8b$=1k)lyM~}XinSVDw<@Jrzm~xP5Kd9VSlU}=O{jOlJog=b$*7#e(E=D^^9F1)6z?!8E|V8hfM z%lJtG#T^^+a;R;lvCi!YFR!Z&W?@!3|5vk0VY8-txqzB(mlk~)X~pQzHKBKIAS-S^ paPVeB+717A3;A<@DORaI0#$atH9sLY$#mTgAQPz}O~FZp{{kp3OmF}I literal 0 HcmV?d00001 diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 6cc774b..6d13331 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -1,91 +1,9 @@ -"use strict"; - /** - * This module provides metric data for various data points. - * The data provided is streamed in two modes: real time and aggregate. - * - * The real time data is simply a slice of the data that has been collected - * over time. - * - * The aggregate data is data that has been rolled-up to various time increments. - * - * The strategy for aggregation centers around time indexes. Imagine an array - * of values and each element of that array being a data point. If the data - * is real-time, then each index is just one logical second. When the data - * is aggregated, each data point is the average of the data, for one logical - * grouping of time. - * - * To determine which data elements from the real-time array are to be grouped, - * the difference in time from the start of the process and the moment a data - * point was captured is passed through this formula: - * - * Time Index = Math.floor((Data Point Time - Process Start Time) / Time Units) - * - * All Times are expressed in milliseconds (ms) and are obtained from Date.now(). - * - * Example: Process Start Time 8798172749871 - * Data Point Time 8798172756481 - * Delta 6610 - * Time Units 5000 - * Quotient 1.322 - * Floor 1 - * - * The above example demonstrates that for a 5000ms (5s) aggregate, the time index - * for the data point is one. This formula is applied to all data points, so - * when many data points share the same logical time index, they can be averaged. - * When two or more array elements have a common time index, they form a time band. - * - * To efficiently perform the average, care is taken to reduce the number of - * CPU cycles required to analyze the data. To accomplish this, the program - * calculates the average inline with the originating transaction. - * - * Also, to prevent re-aggregating the data needlessly, each aggregate level - * maintains its position in the base array, thereby keeping track of where it - * left off. This is done using two simple numbers: the last array element - * examined and the start of the current time band. - * - * So that data is still streamed on an event-basis, an aggregate data point is - * only captured and emitted when it is complete. To detect when an aggregate - * is complete, the algorithm traverses the base real-time array of data, beginning - * where it left off for any given aggregate. Walking each element from there - * forward, a simplistic level-break algorithm is implemented. As and when a - * logical time index changes, this indicates that the previous set of data is - * complete. - * - * Once this occurs, the program then averages the data from the starting index - * through the ending index that spans a logical time unit, regardless of aggregate - * level (using the time index formula above). - * - * Image a set of tuples: (physical index, logical time index, value): - * - * [ 0,0,1.3 ], [ 1,0,4.5 ], [ 2,0,3.7], [3,1,4], [4,2,5.6], [5,2,0.3], [6,5,9.1] - * - * elements[0..2] have the same time index and when element[3] is encountered, a - * change in time index is detected. In state variables, the program knows to re-read - * elements[0..2] and perform the average (shown below). State variables update to - * reflect this new end point where aggregation has been completed for that aggregate - * level. - * - * The program continues and at the end of reading the data above, the average - * would be: - * - * [ 3.2, 4, 2.9 ] - * - * The final tuple would not be processed because nothing came after it. But when it - * did get processed, the program would also include 0-averages for logical time - * elements 3 and 4. When a gap is detected between two logical time elements, - * the program replaces them with averages equal to zero. - * - * To ensure no overflows (and therefore NaN) are produced, the average is done - * using the formula on the left-side of this equivalence: - * - * k[0] k[1] k[2] k[n - 1] sum([k0...kn-1]) - * ------ + ------ + ------ + ... + ---------- = ---------------- = avg([k0...kn-1]) - * n n n n n - * - * The sum of memory usage even over a 10s aggregate is enough to produce NaN. + * For detail information on aggregation in this module, see ../../METRIC-AGGREGATION.md */ +"use strict"; + var EventEmitter = require("events").EventEmitter; var _ = require("lodash"); From 20c542fff5dea43a2c1d725b24a3e5fa4869a54c Mon Sep 17 00:00:00 2001 From: "Michael P. Scott" Date: Tue, 8 Aug 2017 10:12:04 -0700 Subject: [PATCH 21/21] docs(#64) correct PR ref --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ebd2a3..761374e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ [v0.1.2]: https://github.com/FormidableLabs/nodejs-dashboard/compare/v0.1.1...v0.1.2 [v0.1.1]: https://github.com/FormidableLabs/nodejs-dashboard/compare/v0.1.0...v0.1.1 -[\#64]: https://github.com/FormidableLabs/nodejs-dashboard/pull/64 +[\#64]: https://github.com/FormidableLabs/nodejs-dashboard/pull/66 [\#63]: https://github.com/FormidableLabs/nodejs-dashboard/pull/63 [\#62]: https://github.com/FormidableLabs/nodejs-dashboard/pull/62 [\#59]: https://github.com/FormidableLabs/nodejs-dashboard/pull/59