diff --git a/bin/watchman-processor b/bin/watchman-processor index 02aeb7c..d346317 100755 --- a/bin/watchman-processor +++ b/bin/watchman-processor @@ -1,8 +1,23 @@ #!/usr/bin/env node process.title = 'watchman-processor'; -var watchman = require('../index'); + +var index = require('../index'); +var watchman = index.processor; +var terminal = index.terminal; +var WatchmanProcessorEvent = index.WatchmanProcessorEvent; + if (watchman && typeof watchman.start === 'function') { + watchman.emitter.on(WatchmanProcessorEvent.Error, function (params) { + terminal.error(params.err); + }); + watchman.emitter.on(WatchmanProcessorEvent.Debug, function (params) { + terminal.debug(params.msg); + }); + watchman.emitter.on(WatchmanProcessorEvent.SetState, function (params) { + terminal.setState(params.configEntry, params.state, params.statusMessage); + }); + watchman.emitter.on(WatchmanProcessorEvent.Render, terminal.render.bind(terminal)); watchman.start() } process.on('SIGINT', function() { diff --git a/rollup.config.js b/rollup.config.js index 9b607e0..eadc9d4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,6 +12,7 @@ module.exports = { external: [ 'chai', 'child_process', + 'events', 'fb-watchman', 'fs', 'inversify', @@ -26,4 +27,4 @@ module.exports = { typescript: require('typescript') }), ] -}; \ No newline at end of file +}; diff --git a/src/WatchmanProcessor.ts b/src/WatchmanProcessor.ts index 0eebe49..ffff4f9 100644 --- a/src/WatchmanProcessor.ts +++ b/src/WatchmanProcessor.ts @@ -1,8 +1,10 @@ +import { EventEmitter } from 'events'; import { Client, SubscriptionResponse, SubscriptionResponseFile } from 'fb-watchman'; import { inject, injectable } from 'inversify'; import { resolve as resolvePath } from 'path'; -import { Config, SubConfig, Sync, Terminal, WatchmanExpression, WatchmanProcessor } from '../interfaces'; +import { Config, SubConfig, Sync, WatchmanExpression, WatchmanProcessor } from '../interfaces'; import { Bindings } from './ioc.bindings'; +import { WatchmanProcessorEvent } from './WatchmanProcessorEvent'; @injectable() export class WatchmanProcessorImpl implements WatchmanProcessor { @@ -11,16 +13,16 @@ export class WatchmanProcessorImpl implements WatchmanProcessor { private config: Config, @inject(Bindings.WatchmanClient) private client: Client, - @inject(Bindings.Terminal) - private terminal: Terminal, + @inject(Bindings.Emitter) + private emitter: EventEmitter, @inject(Bindings.Sync) private sync: Sync, ) { } public start(): void { - const { client, terminal } = this; + const { client, emitter } = this; - terminal.debug('watchman: initialize'); + emitter.emit(WatchmanProcessorEvent.Debug, {msg: 'watchman: initialize'}); const onCapabilityCheck = this.onCapabilityCheck.bind(this); client.capabilityCheck({}, onCapabilityCheck); } @@ -46,12 +48,12 @@ export class WatchmanProcessorImpl implements WatchmanProcessor { } private onCapabilityCheck(error?: string | Error): void { - const terminal = this.terminal; + const emitter = this.emitter; if (error) { - terminal.error(error); + emitter.emit(WatchmanProcessorEvent.Error, {err: error }); return; } - terminal.render(); + emitter.emit(WatchmanProcessorEvent.Render); const client = this.client; const onSubscription = this.onSubscription.bind(this); @@ -68,8 +70,8 @@ export class WatchmanProcessorImpl implements WatchmanProcessor { promises.push(this.subscribe(resolvePath(sub.source), name, expression)); } - const render = terminal.render.bind(terminal); - const errHandler = terminal.error.bind(terminal); + const render = emitter.emit.bind(emitter, WatchmanProcessorEvent.Render); + const errHandler = emitter.emit.bind(emitter, WatchmanProcessorEvent.Error); Promise.all(promises).then(render).catch(errHandler); // subscription is fired regardless of which subscriber fired it @@ -82,25 +84,27 @@ export class WatchmanProcessorImpl implements WatchmanProcessor { const files = resp.files; const subConfig = config.subscriptions[subscription]; - this.syncFiles(subConfig, files); + this.syncFiles(subConfig, files, subscription); } - private syncFiles(subConfig: SubConfig, files: SubscriptionResponseFile[]): void { - const {sync, terminal } = this; - terminal.setState(subConfig, 'running'); + private syncFiles(subConfig: SubConfig, files: SubscriptionResponseFile[], subscription: string): void { + const {sync, emitter } = this; + emitter.emit(WatchmanProcessorEvent.SetState, {subscription, configEntry: subConfig, state: 'running' }); const fileNames = (files || []).map(file => file.name); sync.syncFiles(subConfig, fileNames) .then(() => { - terminal.setState(subConfig, 'good'); + emitter.emit(WatchmanProcessorEvent.SetState, {subscription, configEntry: subConfig, state: 'good' }); }) .catch(err => { - terminal.setState(subConfig, 'error', err); + emitter.emit( + WatchmanProcessorEvent.SetState, + {subscription, configEntry: subConfig, state: WatchmanProcessorEvent.Error, statusMessage: err}); }); } private subscribe(folder: string, name: string, expression: WatchmanExpression): Promise { - const terminal = this.terminal; + const emitter = this.emitter; const client = this.client; const sub = { expression, @@ -108,30 +112,30 @@ export class WatchmanProcessorImpl implements WatchmanProcessor { relative_root: '', }; - terminal.debug(`subscribe: ${name}`); + emitter.emit(WatchmanProcessorEvent.Debug, {msg: `subscribe: ${name}` }); return new Promise((resolve, reject) => { client.command(['subscribe', folder, name, sub], (error: string) => { - error ? reject('failed to start: ' + error) : resolve(); + error ? reject({err: 'failed to start: ' + error, subscription: name}) : resolve(); }); }); } private unsubscribe(folder: string, name: string): Promise { - const terminal = this.terminal; + const emitter = this.emitter; const client = this.client; - terminal.debug(`unsubscribe: ${name}`); + emitter.emit(WatchmanProcessorEvent.Debug, {msg: `unsubscribe: ${name}` }); return new Promise(resolve => { client.command(['unsubscribe', folder, name], resolve); }); } private shutdown(): Promise { - const terminal = this.terminal; + const emitter = this.emitter; const client = this.client; - terminal.debug(`watchman: shutdown`); + emitter.emit(WatchmanProcessorEvent.Debug, {msg: `watchman: shutdown` }); return new Promise(resolve => { client.command(['shutdown-server'], resolve); }); diff --git a/src/WatchmanProcessorEvent.ts b/src/WatchmanProcessorEvent.ts new file mode 100644 index 0000000..122433b --- /dev/null +++ b/src/WatchmanProcessorEvent.ts @@ -0,0 +1,8 @@ +enum WatchmanProcessorEvent { + Debug = 'debug', + Error = 'error', + Render = 'render', + SetState = 'setState', +} + +export {WatchmanProcessorEvent}; diff --git a/src/index.ts b/src/index.ts index c90f571..87778bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,14 @@ import 'reflect-metadata'; -import { Cli, ConfigManager, WatchmanProcessor } from '../interfaces'; +import { Cli, ConfigManager, Terminal, WatchmanProcessor } from '../interfaces'; import { Bindings } from './ioc.bindings'; import { container } from './ioc.config'; +import { WatchmanProcessorEvent } from './WatchmanProcessorEvent'; const cli = container.get(Bindings.Cli); const configManager = container.get(Bindings.ConfigManager); const watchmanProcessor = container.get(Bindings.WatchmanProcessor); +const terminal = container.get(Bindings.Terminal); const args = cli.getArguments(); let processor = watchmanProcessor; @@ -28,4 +30,9 @@ if (args.init) { } } -export default processor; +export default { + WatchmanProcessorEvent, + config: configManager.getConfig(), + processor, + terminal, +}; diff --git a/src/ioc.bindings.ts b/src/ioc.bindings.ts index 52f9ab0..c2e09bd 100644 --- a/src/ioc.bindings.ts +++ b/src/ioc.bindings.ts @@ -2,6 +2,7 @@ export const Bindings = { Cli: Symbol('Cli'), Config: Symbol('Config'), ConfigManager: Symbol('ConfigManager'), + Emitter: Symbol('Emitter'), Process: Symbol('Process'), Require: Symbol('require'), Spawn: Symbol('spawn'), diff --git a/src/ioc.config.ts b/src/ioc.config.ts index 724b39b..c93aba8 100644 --- a/src/ioc.config.ts +++ b/src/ioc.config.ts @@ -1,4 +1,5 @@ import { spawn } from 'child_process'; +import { EventEmitter } from 'events'; import { Client } from 'fb-watchman'; import { Container } from 'inversify'; import * as interfaces from '../interfaces'; @@ -16,6 +17,7 @@ container.bind(Bindings.Process).toConstantValue(process); container.bind(Bindings.Spawn).toConstantValue(spawn); container.bind(Bindings.Require).toConstantValue(require); container.bind(Bindings.WatchmanClient).toConstantValue(new Client()); +container.bind(Bindings.Emitter).toConstantValue(new EventEmitter()); // setup the main classes container.bind(Bindings.Cli).to(CliImpl); diff --git a/test/WatchmanProcessor-test.ts b/test/WatchmanProcessor-test.ts index 1390d51..5bfb81e 100644 --- a/test/WatchmanProcessor-test.ts +++ b/test/WatchmanProcessor-test.ts @@ -1,15 +1,16 @@ import * as chai from 'chai'; +import { EventEmitter } from 'events'; import { Client } from 'fb-watchman'; import 'reflect-metadata'; import * as sinon from 'sinon'; import 'ts-helpers'; import { Config } from '../interfaces'; import { SyncImpl as Sync } from '../src/Sync'; -import { TerminalImpl as Terminal } from '../src/Terminal'; import { WatchmanProcessorImpl as Watchman } from '../src/WatchmanProcessor'; +import { WatchmanProcessorEvent } from '../src/WatchmanProcessorEvent'; -const mockTerminal = sinon.mock(Terminal); -const terminal: Terminal = mockTerminal as any; +const mockEventEmitter = sinon.mock(EventEmitter); +const emitter = mockEventEmitter as any; const mockSync = { end: sinon.stub(), syncFiles: sinon.stub(), @@ -38,10 +39,7 @@ describe('Watchman', () => { beforeEach(() => { // mock all the defaults before - terminal.render = sinon.stub(); - terminal.error = sinon.stub(); - terminal.debug = sinon.stub(); - terminal.setState = sinon.stub(); + emitter.emit = sinon.spy(); mockSync.syncFiles = sinon.stub().returns(new Promise(resolve => resolve())); mockWatchmanClient.capabilityCheck = sinon.stub().callsArg(1); @@ -50,34 +48,45 @@ describe('Watchman', () => { }); it('should start watchman', () => { - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.start(); chai.assert.isObject(watchman, 'watchman is an object'); }); it('should log errors from watchman.capabilityCheck', () => { - mockWatchmanClient.capabilityCheck.callsArgWith(1, 'error'); + mockWatchmanClient.capabilityCheck.callsArgWith(1, WatchmanProcessorEvent.Error); - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.start(); + chai.assert.deepEqual( + emitter.emit.getCall(0).args, + [WatchmanProcessorEvent.Debug, { msg: 'watchman: initialize' }]); + chai.assert.deepEqual(emitter.emit.getCall(1).args, [WatchmanProcessorEvent.Error, { err: 'error' }]); chai.assert.isObject(watchman, 'watchman is an object'); }); it('should log errors from watchman.command', () => { mockWatchmanClient.command.callsArgWith(1, 'error'); - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.start(); + chai.assert.deepEqual( + emitter.emit.getCall(0).args, + [WatchmanProcessorEvent.Debug, { msg: 'watchman: initialize' }]); + chai.assert.deepEqual(emitter.emit.getCall(1).args, [WatchmanProcessorEvent.Render]); + chai.assert.deepEqual(emitter.emit.getCall(2).args, [WatchmanProcessorEvent.Debug, { msg: 'subscribe: example1' }]); + chai.assert.deepEqual(emitter.emit.getCall(3).args, [WatchmanProcessorEvent.SetState, + { configEntry: config.subscriptions.example1, state: 'running', subscription: 'example1' }]); chai.assert.isObject(watchman, 'watchman is an object'); }); it('should log errors from sync.syncFiles', () => { mockSync.syncFiles.returns(new Promise(() => { throw new Error('error'); })); - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.start(); chai.assert.isObject(watchman, 'watchman is an object'); @@ -85,7 +94,7 @@ describe('Watchman', () => { it('should attempt to sync files', () => { mockWatchmanClient.on = sinon.stub().callsArgWith(1, {subscription: 'example1'}); - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.start(); chai.assert.isObject(watchman, 'watchman is an object'); @@ -93,14 +102,18 @@ describe('Watchman', () => { it('should end', () => { mockWatchmanClient.end = sinon.stub(); - const watchman = new Watchman(config, watchmanClient, terminal, sync); + const watchman = new Watchman(config, watchmanClient, emitter, sync); watchman.end(); + + chai.assert.deepEqual( + emitter.emit.getCall(0).args, + [WatchmanProcessorEvent.Debug, { msg: 'unsubscribe: example1' }]); }); it('should end and shutdown', () => { mockWatchmanClient.end = sinon.stub(); const newConfig = { controlWatchman: true, subscriptions: {} } as any; - const watchman = new Watchman(newConfig, watchmanClient, terminal, sync); + const watchman = new Watchman(newConfig, watchmanClient, emitter, sync); watchman.end(); });