mirror of https://github.com/embarklabs/embark.git
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:
parent
6858de4ff5
commit
b49839afdc
|
@ -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",
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
"init": "init",
|
||||
"build": "build",
|
||||
"initiated": "initiated",
|
||||
"built": "built"
|
||||
"built": "built",
|
||||
"webpackDone": "webpackDone"
|
||||
},
|
||||
"blockchain": {
|
||||
"clients": {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
|
@ -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'
|
||||
|
|
|
@ -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)});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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": []
|
||||
}
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue