feat(@embark/core): improve long running webpack UI

The webpack process took quite a while to run, and there were no updates in the console while running.

This PR adds a spinner (when there is no dashboard) and status updates every 5 seconds. When there is a dashboard, the updates are added to a new line.

After (with dashboard):
![with dashboard](https://i.imgur.com/zVJH5U4.png)

After (`—nodashboard`):
![no dashboard](http://g.recordit.co/2zRNLt51jU.gif)

Convert LongRunningProcessTimer to TypeScript

PR feedback and consistency changes

Changed the constructor signature to accept an options object instead of individual optional parameters, for readability.

Changed library_manager to use the spinner when not using the dashboard, for consistency’s sake. Additionally increased the update time for the library manager from 750ms to 1s.

Fix lint errors

Added `"variable-name": ["allow-leading-underscore”]` to `tslint.json` due to a lack of the ability to prefix backing variables with underscore. This is an [ongoing discussion](https://github.com/palantir/tslint/issues/1489), and something the community thinks should be implemented, as it the preferred way to use a property with backing variable in TypeScript.
This commit is contained in:
emizzle 2018-11-30 14:11:18 +11:00 committed by Iuri Matias
parent 6858de4ff5
commit b49839afdc
12 changed files with 275 additions and 167 deletions

View File

@ -77,6 +77,7 @@
"@babel/preset-react": "7.0.0",
"@babel/preset-typescript": "7.1.0",
"@babel/runtime-corejs2": "7.1.2",
"@types/pretty-ms": "3.2.0",
"ajv": "6.5.5",
"ascii-table": "0.0.9",
"async": "2.6.1",
@ -144,6 +145,7 @@
"os-locale": "2.1.0",
"parse-json": "4.0.0",
"pkg-up": "2.0.0",
"pretty-ms": "4.0.0",
"promptly": "2.2.0",
"propose": "0.0.5",
"pump": "3.0.0",

View File

@ -28,7 +28,8 @@
"init": "init",
"build": "build",
"initiated": "initiated",
"built": "built"
"built": "built",
"webpackDone": "webpackDone"
},
"blockchain": {
"clients": {

View File

@ -136,7 +136,8 @@ class Engine {
pipelineService(_options) {
const self = this;
this.registerModule('pipeline', {
webpackConfigName: this.webpackConfigName
webpackConfigName: this.webpackConfigName,
useDashboard: this.useDashboard
});
this.events.on('code-generator-ready', function (modifiedAssets) {
self.events.request('code', function (abi, contractsJSON) {
@ -286,7 +287,7 @@ class Engine {
}
libraryManagerService(_options) {
this.registerModule('library_manager');
this.registerModule('library_manager', {useDashboard: this.useDashboard});
}
codeCoverageService(_options) {

View File

@ -2,11 +2,12 @@ var Npm = require('./npm.js');
class LibraryManager {
constructor(embark) {
constructor(embark, {useDashboard}) {
this.embark = embark;
this.config = embark.config;
this.contractsConfig = this.config.contractsConfig;
this.storageConfig = this.config.storageConfig;
this.useDashboard = useDashboard;
this.determineVersions();
@ -75,7 +76,7 @@ class LibraryManager {
}
listenToCommandsToGetLibrary() {
let npm = new Npm({logger: this.embark.logger});
let npm = new Npm({logger: this.embark.logger, useDashboard: this.useDashboard});
this.embark.events.setCommandHandler('version:getPackageLocation', (libName, version, cb) => {
npm.getPackageVersion(libName, version, cb);
});

View File

@ -1,7 +1,7 @@
const fs = require('../../core/fs.js');
const PluginManager = require('live-plugin-manager-git-fix').PluginManager;
require('colors');
const NpmTimer = require('./npmTimer.js');
import LongRunningProcessTimer from '../../utils/longRunningProcessTimer.js';
class Npm {
@ -10,13 +10,14 @@ class Npm {
this._packageName = options.packageName;
this._version = options.version;
this._installing = {};
this._useDashboard = options.useDashboard;
}
static getPackagePath(packageName, version){
static getPackagePath(packageName, version) {
return './.embark/versions/' + packageName + '/' + version + '/' + packageName;
}
_isInstalling(packageName, version){
_isInstalling(packageName, version) {
return typeof this._installing[packageName + version] !== 'undefined';
}
@ -31,21 +32,33 @@ class Npm {
const pluginManager = new PluginManager({pluginsPath: './.embark/versions/' + packageName + '/' + version + '/'});
// check if we're already installing this package
if(this._isInstalling(packageName, version)){
if (this._isInstalling(packageName, version)) {
this._installing[packageName + version].push(callback);
}else{
} else {
this._installing[packageName + version] = [callback];
const timer = new NpmTimer({logger: this._logger, packageName: packageName, version: version});
const timer = new LongRunningProcessTimer(
this._logger,
packageName,
version,
'Downloading and installing {{packageName}} {{version}}...',
'Still downloading and installing {{packageName}} {{version}}... ({{duration}})',
'Finished downloading and installing {{packageName}} {{version}} in {{duration}}',
{
showSpinner: !this._useDashboard,
interval: this._useDashboard ? 2000 : 1000,
longRunningThreshold: 10000
}
);
timer.start();
// do the package download/install
pluginManager.install(packageName, version).then((result) => {
timer.end();
this._installing[packageName + version].forEach((cb) => {
cb(null, result.location);
});
delete this._installing[packageName + version];
timer.end();
this._installing[packageName + version].forEach((cb) => {
cb(null, result.location);
});
delete this._installing[packageName + version];
}).catch(err => {
this._installing[packageName + version].forEach((cb) => {
cb(err);

View File

@ -1,95 +0,0 @@
const {PerformanceObserver, performance} = require('perf_hooks');
require('colors');
const utils = require('../../utils/utils.js');
const i18n = require('../../core/i18n/i18n.js');
i18n.setOrDetectLocale('en');
class NpmTimer{
constructor(options){
this._logger = (options.logger && typeof options.logger.info === 'function') ? options.logger : console;
this._packageName = options.packageName;
this._version = options.version;
this._showSpinner = options.showSpinner || false;
this._spinnerStyle = options.spinnerStyle || 'dots';
this._interval = options.interval || 750;
// define mark and measurement names
this._startMark = 'downloadStart' + this._packageName + this._version;
this._ongoingMark = 'downloadOngoingMark' + this._packageName + this._version;
this._downloadOngoing = 'downloadOngoing' + this._packageName + this._version;
this._endMark = 'downloadEnd' + this._packageName + this._version;
this._downloadComplete = 'downloadComplete' + this._packageName + this._version;
this.observer.observe({entryTypes: ['measure']});
}
get observer(){
if(typeof this._observer === 'undefined'){
this._observer = new PerformanceObserver((items) => {
let entry;
let strDuration;
// find any download ongoing measurements we've made
entry = utils.last(items.getEntries().filter(entry => entry.name === this._downloadOngoing));
if(entry){
// ongoing performance mark
strDuration = __('Downloading and installing {{packageName}} {{version}}... ({{duration}}ms elapsed)', {packageName: this._packageName, version: this._version, duration: entry.duration});
if(this._spinner) this._spinner.text = strDuration;
}
else{
// otherwise, find our download complete measurement
entry = utils.last(items.getEntries().filter(entry => entry.name === this._downloadComplete));
if(entry){
strDuration = __('Finished downloading and installing {{packageName}} {{version}} in {{duration}}ms', {packageName: this._packageName, version: this._version, duration: entry.duration});
performance.clearMarks();
if(this._spinner) this._spinner.succeed(strDuration);
}
}
// log our measurement and make it red if it has taken too long
if(!this._showSpinner && entry && strDuration){
if(entry.duration > 4000){
strDuration = strDuration.red;
}
this._logger.info(strDuration);
}
});
}
return this._observer;
}
start(){
let self = this;
const strDownloadStart = __("Downloading and installing {{packageName}} {{version}}...", {packageName: this._packageName, version: this._version});
if(this._showSpinner){
const ora = require('ora');
this._spinner = ora({
spinner: this._spinnerStyle,
text: strDownloadStart
}).start();
}else{
this._logger.info(strDownloadStart);
}
// mark our start time
performance.mark(this._startMark);
// function that continually updates the console to show user that we're downloading a library
this._intOngoingDownload = setInterval(
function(){
performance.mark(self._ongoingMark);
performance.measure(self._downloadOngoing, self._startMark, self._ongoingMark);
}, this._interval);
}
end(){
// stop updating console for ongoing download
clearInterval(this._intOngoingDownload);
performance.mark(this._endMark);
performance.measure(this._downloadComplete, this._startMark, this._endMark);
}
}
module.exports = NpmTimer;

View File

@ -5,12 +5,13 @@ const utils = require('../../utils/utils.js');
const ProcessLauncher = require('../../core/processes/processLauncher');
const constants = require('../../constants');
const WebpackConfigReader = require('../pipeline/webpackConfigReader');
import LongRunningProcessTimer from '../../utils/longRunningProcessTimer';
class Pipeline {
constructor(embark, options) {
this.embark = embark;
this.env = embark.config.env;
this.buildDir = embark.config.buildDir;
this.buildDir = embark.config.buildDir;
this.contractsFiles = embark.config.contractsFiles;
this.assetFiles = embark.config.assetFiles;
this.events = embark.events;
@ -20,6 +21,7 @@ class Pipeline {
this.pipelinePlugins = this.plugins.getPluginsFor('pipeline');
this.pipelineConfig = embark.config.pipelineConfig;
this.isFirstBuild = true;
this.useDashboard = options.useDashboard;
this.events.setCommandHandler('pipeline:build', (options, callback) => this.build(options, callback));
this.events.setCommandHandler('pipeline:build:contracts', callback => this.buildContracts(callback));
@ -69,7 +71,7 @@ class Pipeline {
return res.send({error: error.message});
}
fs.writeFileSync(req.body.path, req.body.content, { encoding: 'utf8'});
fs.writeFileSync(req.body.path, req.body.content, {encoding: 'utf8'});
const name = path.basename(req.body.path);
res.send({name, path: req.body.path, content: req.body.content});
}
@ -104,7 +106,8 @@ class Pipeline {
dirname: dir,
path: path.join(dir, name),
isHidden: name.indexOf('.') === 0,
children: utils.fileTreeSort(walk(path.join(dir, name), filelist))};
children: utils.fileTreeSort(walk(path.join(dir, name), filelist))
};
}
return {
name,
@ -165,7 +168,7 @@ class Pipeline {
self.events.request('contracts:list', (_err, contracts) => {
// ensure the .embark/contracts directory exists (create if not exists)
fs.mkdirp(fs.dappPath(".embark/contracts", ''), err => {
if(err) return next(err);
if (err) return next(err);
// Create a file .embark/contracts/index.js that requires all contract files
// Used to enable alternate import syntax:
@ -182,7 +185,7 @@ class Pipeline {
// add the contract to the exports list to support alternate import syntax
importsHelperFile.write(`"${contract.className}": require('./${contract.className}').default`);
if(idx < contracts.length - 1) importsHelperFile.write(',\n'); // add a comma if we have more contracts to add
if (idx < contracts.length - 1) importsHelperFile.write(',\n'); // add a comma if we have more contracts to add
});
}, () => {
importsHelperFile.write('\n}'); // close the module.exports = {}
@ -191,16 +194,16 @@ class Pipeline {
});
});
},
function shouldRunWebpack(next){
function shouldRunWebpack(next) {
// assuming we got here because an asset was changed, let's check our webpack config
// to see if the changed asset requires webpack to run
if(!(modifiedAssets && modifiedAssets.length)) return next(null, false);
if (!(modifiedAssets && modifiedAssets.length)) return next(null, false);
const configReader = new WebpackConfigReader({webpackConfigName: self.webpackConfigName});
return configReader.readConfig((err, config) => {
if(err) return next(err);
if (err) return next(err);
if (typeof config !== 'object' || config === null) {
return next(__('bad webpack config, the resolved config was null or not an object'));
return next(__('Pipeline: bad webpack config, the resolved config was null or not an object'));
}
const shouldRun = modifiedAssets.some(modifiedAsset => config.module.rules.some(rule => rule.test.test(modifiedAsset)));
@ -208,15 +211,31 @@ class Pipeline {
});
},
function runWebpack(shouldNotRun, next) {
if(shouldNotRun) return next();
self.logger.info(__(`running webpack with '${self.webpackConfigName}' config...`));
if (shouldNotRun) return next();
const assets = Object.keys(self.assetFiles).filter(key => key.match(/\.js$/));
if (!assets || !assets.length) {
return next();
}
assets.forEach(key => {
self.logger.info(__("writing file") + " " + (utils.joinPath(self.buildDir, key)).bold.dim);
});
let strAssets = '';
if (!self.useDashboard) {
assets.forEach(key => {
strAssets += ('\n ' + (utils.joinPath(self.buildDir, key)).bold.dim);
});
}
const timer = new LongRunningProcessTimer(
self.logger,
'webpack',
'0',
`${'Pipeline:'.cyan} Bundling dapp using '${self.webpackConfigName}' config...${strAssets}`,
`${'Pipeline:'.cyan} Still bundling dapp using '${self.webpackConfigName}' config... ({{duration}})${strAssets}`,
`${'Pipeline:'.cyan} Finished bundling dapp in {{duration}}${strAssets}`,
{
showSpinner: !self.useDashboard,
interval: self.useDashboard ? 5000 : 1000,
longRunningThreshold: 15000
}
);
timer.start();
let built = false;
const webpackProcess = new ProcessLauncher({
embark: self.embark,
@ -247,6 +266,9 @@ class Pipeline {
webpackProcess.kill();
return next(msg.error);
});
webpackProcess.once('result', constants.pipeline.webpackDone, () => {
timer.end();
});
},
function assetFileWrite(next) {
async.eachOf(
@ -261,7 +283,7 @@ class Pipeline {
const isDir = targetFile.slice(-1) === '/' || targetFile.slice(-1) === '\\' || targetFile.indexOf('.') === -1;
// if it's not a directory
if (!isDir) {
self.logger.info(__("writing file") + " " + (utils.joinPath(self.buildDir, targetFile)).bold.dim);
self.logger.info('Pipeline: '.cyan + __("writing file") + " " + (utils.joinPath(self.buildDir, targetFile)).bold.dim);
}
async.map(
files,
@ -273,10 +295,10 @@ class Pipeline {
},
function (err, contentFiles) {
if (err) {
self.logger.error(__('errors found while generating') + ' ' + targetFile);
self.logger.error('Pipeline: '.cyan + __('errors found while generating') + ' ' + targetFile);
}
let dir = targetFile.split('/').slice(0, -1).join('/');
self.logger.trace("creating dir " + utils.joinPath(self.buildDir, dir));
self.logger.trace(`${'Pipeline:'.cyan} creating dir ` + utils.joinPath(self.buildDir, dir));
fs.mkdirpSync(utils.joinPath(self.buildDir, dir));
// if it's a directory
@ -289,7 +311,7 @@ class Pipeline {
async.each(contentFiles, function (file, eachCb) {
let filename = file.filename.replace(file.basedir + '/', '');
self.logger.info("writing file " + (utils.joinPath(self.buildDir, targetDir, filename)).bold.dim);
self.logger.info(`${'Pipeline:'.cyan} writing file ` + (utils.joinPath(self.buildDir, targetDir, filename)).bold.dim);
fs.copy(file.path, utils.joinPath(self.buildDir, targetDir, filename), {overwrite: true}, eachCb);
}, cb);
@ -314,7 +336,7 @@ class Pipeline {
next
);
},
function removePlaceholderPage(next){
function removePlaceholderPage(next) {
let placeholderFile = utils.joinPath(self.buildDir, placeholderPage);
fs.access(utils.joinPath(self.buildDir, placeholderPage), (err) => {
if (err) return next(); // index-temp doesn't exist, do nothing
@ -337,7 +359,7 @@ class Pipeline {
self.events.request('contracts:list', next);
},
function writeContractsJSON(contracts, next) {
async.each(contracts,(contract, eachCb) => {
async.each(contracts, (contract, eachCb) => {
fs.writeJson(fs.dappPath(
self.buildDir,
'contracts', contract.className + '.json'

View File

@ -48,38 +48,42 @@ class WebpackProcess extends ProcessWrapper {
}
if (typeof config !== 'object' || config === null) {
return callback('bad webpack config, the resolved config was null or not an object');
return callback('Pipeline: '.cyan + 'bad webpack config, the resolved config was null or not an object');
}
webpack(config).run(async (err, stats) => {
if (err) {
return callback(errorMessage(err));
}
if (!config.stats || config.stats === 'none') {
return callback();
}
try {
this._log('info', 'writing file ' + ('.embark/stats.report').bold.dim);
await writeFile(
fs.dappPath('.embark/stats.report'),
stats.toString(config.stats)
);
this._log('info', 'writing file ' + ('.embark/stats.json').bold.dim);
await writeFile(
fs.dappPath('.embark/stats.json'),
JSON.stringify(stats.toJson(config.stats))
);
if (stats.hasErrors()) {
const errors = stats.toJson(config.stats).errors.join('\n');
return callback(errors);
}
callback();
} catch (e) {
return callback(errorMessage(e));
}
callback(null, config, stats);
});
});
}
async writeStats(config, stats, callback){
if (!config.stats || config.stats === 'none') {
return callback();
}
try {
this._log('info', 'Pipeline: '.cyan + 'writing file ' + ('.embark/stats.report').bold.dim);
await writeFile(
fs.dappPath('.embark/stats.report'),
stats.toString(config.stats)
);
this._log('info', 'Pipeline: '.cyan + 'writing file ' + ('.embark/stats.json').bold.dim);
await writeFile(
fs.dappPath('.embark/stats.json'),
JSON.stringify(stats.toJson(config.stats))
);
if (stats.hasErrors()) {
const errors = stats.toJson(config.stats).errors.join('\n');
return callback(errors);
}
callback();
} catch (e) {
return callback(errorMessage(e));
}
}
}
process.on('message', (msg) => {
@ -89,8 +93,11 @@ process.on('message', (msg) => {
}
if (msg.action === constants.pipeline.build) {
return webpackProcess.build(msg.assets, msg.importsList, (err) => {
process.send({result: constants.pipeline.built, error: err});
return webpackProcess.build(msg.assets, msg.importsList, (err, config, stats) => {
process.send({result: constants.pipeline.webpackDone, error: err});
webpackProcess.writeStats(config, stats, (errWriteStats) => {
process.send({result: constants.pipeline.built, error: (err || errWriteStats)});
});
});
}
});

View File

@ -5,11 +5,11 @@ const constants = require('../../constants');
const Utils = require('../../utils/utils');
const ProcessWrapper = require('../../core/processes/processWrapper');
const PluginManager = require('live-plugin-manager-git-fix').PluginManager;
const NpmTimer = require('../library_manager/npmTimer');
import LongRunningProcessTimer from '../../utils/longRunningProcessTimer';
class SolcProcess extends ProcessWrapper {
constructor(options){
constructor(options) {
super({pingParent: false});
this._logger = options.logger;
this._showSpinner = options.showSpinner === true;
@ -33,19 +33,27 @@ class SolcProcess extends ProcessWrapper {
return {error: 'File not found'};
}
installAndLoadCompiler(solcVersion, packagePath){
installAndLoadCompiler(solcVersion, packagePath) {
let self = this;
return new Promise((resolve, reject) => {
let manager = new PluginManager({pluginsPath: packagePath});
let timer;
if (!fs.existsSync(packagePath)) {
timer = new NpmTimer({logger: self._logger, packageName: 'solc', version: solcVersion, showSpinner: self._showSpinner});
timer = new LongRunningProcessTimer(
self._logger,
'solc',
solcVersion,
'Downloading and installing {{packageName}} {{version}}...',
'Still downloading and installing {{packageName}} {{version}}... ({{duration}})',
'Finished downloading and installing {{packageName}} {{version}} in {{duration}}',
{ showSpinner: self._showSpinner }
);
}
if(timer) timer.start();
if (timer) timer.start();
manager.install('solc', solcVersion).then(() => {
self.solc = manager.require('solc');
if(timer) timer.end();
if (timer) timer.end();
resolve();
}).catch(reject);

View File

@ -0,0 +1,130 @@
import { red } from "colors";
import { performance, PerformanceObserver } from "perf_hooks";
import prettyMs from "pretty-ms";
import { Logger } from "../../../src/typings/logger";
const utils = require("./utils.js");
const ora = require("ora");
export interface LongRunningProcessTimerOptions {
showSpinner?: boolean;
spinnerStyle?: string;
interval?: number;
longRunningThreshold?: number;
}
export default class LongRunningProcessTimer {
private startMark: string;
private ongoingMark: string;
private downloadOngoing: string;
private endMark: string;
private downloadComplete: string;
private spinner!: any;
private intOngoingDownload!: number;
// backing variables
private _observer!: PerformanceObserver;
// default options
private static readonly DEFAULT_OPTIONS: LongRunningProcessTimerOptions = {
interval: 750,
longRunningThreshold: 4000,
showSpinner: false,
spinnerStyle: "dots",
};
constructor(
private logger: Logger,
private packageName: string,
private version: string,
private processStartingMsg: string,
private processOngoingMsg: string,
private processFinishedMsg: string,
private options: LongRunningProcessTimerOptions = LongRunningProcessTimer.DEFAULT_OPTIONS,
) {
this.options = utils.recursiveMerge(LongRunningProcessTimer.DEFAULT_OPTIONS, this.options);
// define mark and measurement names
this.startMark = "downloadStart" + this.packageName + this.version;
this.ongoingMark = "downloadOngoingMark" + this.packageName + this.version;
this.downloadOngoing = "downloadOngoing" + this.packageName + this.version;
this.endMark = "downloadEnd" + this.packageName + this.version;
this.downloadComplete = "downloadComplete" + this.packageName + this.version;
this.observer.observe({ entryTypes: ["measure"] });
}
get observer() {
if (typeof this._observer === "undefined") {
this._observer = new PerformanceObserver((items) => {
let entry;
let strDuration;
// find any download ongoing measurements we"ve made
entry = utils.last(items.getEntries().filter((thisEntry) => thisEntry.name === this.downloadOngoing));
if (entry) {
// ongoing performance mark
// TODO: add i18n
strDuration = this.processOngoingMsg.replace("{{packageName}}", this.packageName).replace("{{version}}", this.version).replace("{{duration}}", prettyMs(entry.duration));
if (this.spinner) {
this.spinner.text = strDuration;
}
} else {
// otherwise, find our download complete measurement
entry = utils.last(items.getEntries().filter((thisEntry) => thisEntry.name === this.downloadComplete));
if (entry) {
// TODO: add i18n
strDuration = this.processFinishedMsg.replace("{{packageName}}", this.packageName).replace("{{version}}", this.version).replace("{{duration}}", prettyMs(entry.duration));
performance.clearMarks();
if (this.spinner) {
this.spinner.succeed(strDuration);
}
}
}
// log our measurement and make it red if it has taken too long
if (!this.options.showSpinner && entry && strDuration && this.options && this.options.longRunningThreshold) {
if (entry.duration > this.options.longRunningThreshold) {
strDuration = strDuration.red;
}
this.logger.info(strDuration);
}
});
}
return this._observer;
}
public start() {
// TODO: add i18n
const strDownloadStart = this.processStartingMsg.replace("{{packageName}}", this.packageName).replace("{{version}}", this.version);
if (this.options.showSpinner) {
this.spinner = ora({
spinner: this.options.spinnerStyle,
text: strDownloadStart,
}).start();
} else {
this.logger.info(strDownloadStart);
}
// mark our start time
performance.mark(this.startMark);
// function that continually updates the console to show user that we"re downloading a library
this.intOngoingDownload = setInterval(
() => {
performance.mark(this.ongoingMark);
performance.measure(this.downloadOngoing, this.startMark, this.ongoingMark);
}, this.options.interval);
}
public end() {
// stop updating console for ongoing download
clearInterval(this.intOngoingDownload);
performance.mark(this.endMark);
performance.measure(this.downloadComplete, this.startMark, this.endMark);
}
}
module.exports = LongRunningProcessTimer;

View File

@ -10,7 +10,8 @@
"member-ordering": [false],
"no-var-requires": false,
"no-empty": false,
"no-console": false
"no-console": false,
"variable-name": ["allow-leading-underscore"]
},
"rulesDirectory": []
}

View File

@ -877,6 +877,11 @@
resolved "https://registry.yarnpkg.com/@types/os-locale/-/os-locale-2.1.0.tgz#0ded736612a79e900fa76f02c6ad566d046ad17a"
integrity sha512-1FI8uCzD//cYu6eDMrik91BevmE8xQLeV5Br2+dDjiluhTM5vtxTDXSaVY20rRV8V/XT6l+A1H12g5+hBtSQZg==
"@types/pretty-ms@3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@types/pretty-ms/-/pretty-ms-3.2.0.tgz#cdd35f7edac2310bbe2af86f5244625db7a101b1"
integrity sha512-jF8PYR5Nm2w148Icj+Xf4CcVO+YrrpVyRSZPmSG67W2H3WrAAET7jP22txHpk6rnwPGJ8asW7syiyQTPcWWPAQ==
"@types/semver@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
@ -10989,6 +10994,11 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
parse-ms@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.0.0.tgz#7b3640295100caf3fa0100ccceb56635b62f9d62"
integrity sha512-AddiXFSLLCqj+tCRJ9MrUtHZB4DWojO3tk0NVZ+g5MaMQHF2+p2ktqxuoXyPFLljz/aUK0Nfhd/uGWnhXVXEyA==
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@ -11619,6 +11629,13 @@ pretty-format@^20.0.3:
ansi-regex "^2.1.1"
ansi-styles "^3.0.0"
pretty-ms@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-4.0.0.tgz#31baf41b94fd02227098aaa03bd62608eb0d6e92"
integrity sha512-qG66ahoLCwpLXD09ZPHSCbUWYTqdosB7SMP4OffgTgL2PBKXMuUsrk5Bwg8q4qPkjTXsKBMr+YK3Ltd/6F9s/Q==
dependencies:
parse-ms "^2.0.0"
private@^0.1.6, private@^0.1.7, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"