embark/lib/modules/watcher/index.js

208 lines
6.3 KiB
JavaScript

let chokidar = require('chokidar');
let path = require('path');
let fs = require('../../core/fs.js');
const DAPP_PIPELINE_CONFIG_FILE = 'pipeline.js';
const DAPP_WEBPACK_CONFIG_FILE = 'webpack.config.js';
const DAPP_BABEL_LOADER_OVERRIDES_CONFIG_FILE = 'babel-loader-overrides.js';
// TODO: this should be receiving the config object not re-reading the
// embark.json file
class Watcher {
constructor(embark) {
this.logger = embark.logger;
this.events = embark.events;
this.fileWatchers = [];
this.events.setCommandHandler('watcher:start', () => this.start());
this.events.setCommandHandler('watcher:stop', () => this.stop());
this.events.setCommandHandler('watcher:restart', () => this.restart());
}
start() {
let self = this;
// TODO: should come from the config object instead of reading the file
// directly
let embarkConfig = fs.readJSONSync("embark.json");
this.watchAssets(embarkConfig, function () {
self.logger.trace('ready to watch asset changes');
});
this.watchContracts(embarkConfig, function () {
self.logger.trace('ready to watch contract changes');
});
this.watchContractConfig(embarkConfig, function () {
self.logger.trace('ready to watch contract config changes');
});
this.watchPipelineConfig(embarkConfig, function () {
self.logger.trace('ready to watch pipeline config changes');
});
this.watchWebserverConfig(embarkConfig, function () {
self.logger.trace('ready to watch webserver config changes');
});
this.logger.info(__("ready to watch file changes"));
}
restart() {
this.stop();
this.start();
}
stop() {
this.fileWatchers.forEach(fileWatcher => {
if (fileWatcher.shouldClose) return;
if (fileWatcher.isReady) fileWatcher.close();
fileWatcher.shouldClose = true;
});
}
watchAssets(embarkConfig, callback) {
let self = this;
let appConfig = embarkConfig.app;
let filesToWatch = [];
for (let targetFile in appConfig) {
let files = appConfig[targetFile];
let fileGlob = files;
// workaround for imports issue
// so embark reacts to changes made in imported js files
// chokidar glob patterns only work with front-slashes
if (!Array.isArray(files)) {
fileGlob = path.join(path.dirname(files), '**', '*.*').replace(/\\/g, '/');
} else if (files.length === 1) {
fileGlob = path.join(path.dirname(files[0]), '**', '*.*').replace(/\\/g, '/');
}
filesToWatch.push(fileGlob);
}
filesToWatch = Array.from(new Set(filesToWatch));
this.watchFiles(
filesToWatch,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'asset', path);
self.events.emit('file-event', 'asset', path);
},
function () {
callback();
}
);
}
watchContracts(embarkConfig, callback) {
let self = this;
this.watchFiles(
[embarkConfig.contracts],
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'contract', path);
self.events.emit('file-event', 'contract', path);
},
function () {
callback();
}
);
}
watchWebserverConfig(embarkConfig, callback) {
let self = this;
let webserverConfig;
if (typeof embarkConfig.config === 'object') {
if (!embarkConfig.config.webserver) {
return;
}
webserverConfig = embarkConfig.config.webserver;
} else {
let contractsFolder = embarkConfig.config.replace(/\\/g, '/');
if (contractsFolder.charAt(contractsFolder.length - 1) !== '/') {
contractsFolder += '/';
}
webserverConfig = [`${contractsFolder}**/webserver.json`, `${contractsFolder}**/webserver.js`];
}
this.watchFiles(webserverConfig,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('webserver:config:change', 'config', path);
},
function () {
callback();
}
);
}
watchContractConfig(embarkConfig, callback) {
let self = this;
let contractConfig;
if (typeof embarkConfig.config === 'object' || embarkConfig.config.contracts) {
contractConfig = embarkConfig.config.contracts;
} else {
let contractsFolder = embarkConfig.config.replace(/\\/g, '/');
if (contractsFolder.charAt(contractsFolder.length - 1) !== '/') {
contractsFolder += '/';
}
contractConfig = [`${contractsFolder}**/contracts.json`, `${contractsFolder}**/contracts.js`];
}
this.watchFiles(contractConfig,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'config', path);
self.events.emit('file-event', 'config', path);
},
function () {
callback();
}
);
}
watchPipelineConfig(embarkConfig, callback) {
let filesToWatch = [
fs.dappPath('', DAPP_WEBPACK_CONFIG_FILE),
fs.dappPath('', DAPP_BABEL_LOADER_OVERRIDES_CONFIG_FILE)
];
if (typeof embarkConfig.config === 'object' && embarkConfig.config.pipeline) {
filesToWatch.push(embarkConfig.config.pipeline);
} else if (typeof embarkConfig.config === 'string') {
filesToWatch.push(fs.dappPath(embarkConfig.config, DAPP_PIPELINE_CONFIG_FILE));
}
this.watchFiles(filesToWatch, (eventName, path) => {
this.logger.info(`${eventName}: ${path}`);
this.events.emit('file-' + eventName, 'config', path);
this.events.emit('file-event', 'config', path);
}, callback);
}
watchFiles(files, changeCallback, doneCallback) {
this.logger.trace('watchFiles');
this.logger.trace(files);
let configWatcher = chokidar.watch(files, {
ignored: /[\/\\]\.|tmp_/, persistent: true, ignoreInitial: true, followSymlinks: true
});
this.fileWatchers.push(configWatcher);
configWatcher
.on('add', path => changeCallback('add', path))
.on('change', path => changeCallback('change', path))
.on('unlink', path => changeCallback('remove', path))
.once('ready', () => {
configWatcher.isReady = true;
if (configWatcher.shouldClose) configWatcher.close();
doneCallback();
});
}
}
module.exports = Watcher;