Skip to content

Commit

Permalink
Convert watcher and watcher_adapter to typescript (broccolijs#434)
Browse files Browse the repository at this point in the history
  • Loading branch information
thoov authored Sep 23, 2019
1 parent 28e8f4d commit b684378
Show file tree
Hide file tree
Showing 11 changed files with 491 additions and 68 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
Expand Down
2 changes: 1 addition & 1 deletion lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface BuildOptions {
dev?: boolean;
}

export default function broccoliCLI(args: string[], ui = new UI()) {
export = function broccoliCLI(args: string[], ui = new UI()) {
// always require a fresh commander, as it keeps state at module scope
delete require.cache[require.resolve('commander')];
const program = require('commander');
Expand Down
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
export = {
get Builder() {
return require('./builder');
},
Expand Down
21 changes: 21 additions & 0 deletions lib/utils/bind-file-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import sane from 'sane';
import SourceNodeWrapper from '../wrappers/source-node';
import TransformNodeWrapper from '../wrappers/transform-node';
import WatcherAdapter from '../watcher_adapter';

const logger = require('heimdalljs-logger')('broccoli:watcherAdapter');

export default function bindFileEvent(
adapter: WatcherAdapter,
watcher: sane.Watcher,
node: TransformNodeWrapper | SourceNodeWrapper,
event: 'change' | 'add' | 'delete'
) {
// @ts-ignores
watcher.on(event, (filepath: string, root: string) => {
logger.debug(event, root + '/' + filepath);
logger.debug(`revise called on node [${node.id}]`);
node.revise();
adapter.emit('change', event, filepath, root);
});
}
60 changes: 46 additions & 14 deletions lib/watcher.js → lib/watcher.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
const path = require('path');
const promiseFinally = require('promise.prototype.finally');
const EventEmitter = require('events').EventEmitter;
const WatcherAdapter = require('./watcher_adapter');
import path from 'path';
import sane from 'sane';
import { EventEmitter } from 'events';
import WatcherAdapter from './watcher_adapter';
import promiseFinally from 'promise.prototype.finally';
import SourceNodeWrapper from './wrappers/source-node';

const logger = require('heimdalljs-logger')('broccoli:watcher');

interface WatcherOptions {
debounce?: number;
watcherAdapter?: WatcherAdapter;
saneOptions?: sane.Options;
}

// This Watcher handles all the Broccoli logic, such as debouncing. The
// WatcherAdapter handles I/O via the sane package, and could be pluggable in
// principle.

module.exports = class Watcher extends EventEmitter {
constructor(builder, watchedNodes, options = {}) {
class Watcher extends EventEmitter {
_changedFiles: string[];
_quitting?: boolean; // is this ever set
_rebuildScheduled: boolean;
_ready: boolean;
_quittingPromise: Promise<void> | null;
_lifetime: {
promise?: Promise<void>,
resolve?: (value: any) => void;
reject?: (error: any) => void;
} | null;

options: WatcherOptions;
currentBuild: null | Promise<void | null>;
watcherAdapter: WatcherAdapter;
builder: any;

constructor(builder: any, watchedNodes: SourceNodeWrapper[], options: WatcherOptions = {}) {
super();
this.options = options;
if (this.options.debounce == null) {
Expand All @@ -32,7 +57,8 @@ module.exports = class Watcher extends EventEmitter {
throw new Error('Watcher.prototype.start() must not be called more than once');
}

let lifetime = (this._lifetime = {});
this._lifetime = {};
let lifetime = this._lifetime;
lifetime.promise = new Promise((resolve, reject) => {
lifetime.resolve = resolve;
lifetime.reject = reject;
Expand All @@ -55,7 +81,7 @@ module.exports = class Watcher extends EventEmitter {
return this._lifetime.promise;
}

_change(event, filePath, root) {
_change(event: 'change', filePath: string, root: string) {
this._changedFiles.push(path.join(root, filePath));
if (!this._ready) {
logger.debug('change', 'ignored: before ready');
Expand Down Expand Up @@ -92,7 +118,7 @@ module.exports = class Watcher extends EventEmitter {
});
}

_build(filePath) {
_build(filePath?: string) {
logger.debug('buildStart');
this.emit('buildStart');

Expand All @@ -111,7 +137,7 @@ module.exports = class Watcher extends EventEmitter {
// currentBuild, their callback will come after our events have
// triggered, because we registered our callback first.
buildPromise.then(
(results = {}) => {
(results: { filePath?: string } = {}) => {
const hrend = process.hrtime(hrstart);
logger.debug('Build execution time: %ds %dms', hrend[0], Math.round(hrend[1] / 1e6));
logger.debug('buildSuccess');
Expand All @@ -125,7 +151,7 @@ module.exports = class Watcher extends EventEmitter {
this._changedFiles = [];
this.emit('buildSuccess', results);
},
err => {
(err: Error) => {
this._changedFiles = [];
logger.debug('buildFailure');
this.emit('buildFailure', err);
Expand All @@ -134,7 +160,7 @@ module.exports = class Watcher extends EventEmitter {
return buildPromise;
}

_error(err) {
_error(err: any) {
if (this._quittingPromise) {
logger.debug('error', 'ignored: already quitting');
return this._quittingPromise;
Expand All @@ -144,7 +170,11 @@ module.exports = class Watcher extends EventEmitter {
this.emit('error', err);
return this._quit()
.catch(() => {})
.then(() => this._lifetime.reject(err));
.then(() => {
if (this._lifetime && typeof this._lifetime.reject === 'function') {
this._lifetime.reject(err)
}
});
}

quit() {
Expand All @@ -155,7 +185,7 @@ module.exports = class Watcher extends EventEmitter {

let quitting = this._quit();

if (this._lifetime) {
if (this._lifetime && typeof this._lifetime.resolve === 'function') {
this._lifetime.resolve(quitting);
return this._lifetime.promise;
} else {
Expand All @@ -181,3 +211,5 @@ module.exports = class Watcher extends EventEmitter {
return this._quittingPromise;
}
};

export = Watcher;
42 changes: 22 additions & 20 deletions lib/watcher_adapter.js → lib/watcher_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import sane from 'sane';
import { EventEmitter } from 'events';
import SourceNode from './wrappers/source-node';
import SourceNodeWrapper from './wrappers/source-node';
import bindFileEvent from './utils/bind-file-event';

const EventEmitter = require('events').EventEmitter;
const sane = require('sane');
const logger = require('heimdalljs-logger')('broccoli:watcherAdapter');

function defaultFilterFunction(name) {
return /^[^.]/.test(name);
interface WatcherAdapterOptions extends sane.Options {
filter?: (name: string) => boolean;
}

function bindFileEvent(adapter, watcher, node, event) {
watcher.on(event, (filepath, root) => {
logger.debug(event, root + '/' + filepath);
logger.debug(`revise called on node [${node.id}]`);
node.revise();
adapter.emit('change', event, filepath, root);
});
function defaultFilterFunction(name: string) {
return /^[^.]/.test(name);
}

module.exports = class WatcherAdapter extends EventEmitter {
constructor(watchedNodes, options) {
class WatcherAdapter extends EventEmitter {
watchers: sane.Watcher[];
watchedNodes: SourceNodeWrapper[];
options: WatcherAdapterOptions;

constructor(watchedNodes: SourceNodeWrapper[], options: sane.Options = {}) {
super();
if (!Array.isArray(watchedNodes)) {
throw new TypeError(
Expand All @@ -34,15 +35,15 @@ module.exports = class WatcherAdapter extends EventEmitter {
}
}
this.watchedNodes = watchedNodes;
this.options = options || {};
this.options = options;
this.options.filter = this.options.filter || defaultFilterFunction;
this.watchers = [];
}

watch() {
let watchers = this.watchedNodes.map(node => {
let watchers = this.watchedNodes.map((node: SourceNodeWrapper) => {
const watchedPath = node.nodeInfo.sourceDirectory;
const watcher = new sane(watchedPath, this.options);
const watcher = sane(watchedPath, this.options);
this.watchers.push(watcher);
bindFileEvent(this, watcher, node, 'change');
bindFileEvent(this, watcher, node, 'add');
Expand All @@ -54,7 +55,7 @@ module.exports = class WatcherAdapter extends EventEmitter {
}).then(() => {
watcher.removeAllListeners('ready');
watcher.removeAllListeners('error');
watcher.on('error', err => {
watcher.on('error', (err: Error) => {
logger.debug('error', err);
this.emit('error', err);
});
Expand All @@ -66,9 +67,10 @@ module.exports = class WatcherAdapter extends EventEmitter {

quit() {
let closing = this.watchers.map(
watcher =>
(watcher: sane.Watcher) =>
new Promise((resolve, reject) =>
watcher.close(err => {
// @ts-ignore
watcher.close((err: any) => {
if (err) reject(err);
else resolve();
})
Expand All @@ -79,4 +81,4 @@ module.exports = class WatcherAdapter extends EventEmitter {
}
};

module.exports.bindFileEvent = bindFileEvent;
export = WatcherAdapter;
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,16 @@
},
"devDependencies": {
"@types/node": "^12.7.1",
"@types/promise.prototype.finally": "^2.0.3",
"@types/rimraf": "^2.0.2",
"@types/sane": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^2.3.0",
"@typescript-eslint/parser": "^2.3.0",
"broccoli-node-api": "^1.7.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"copyfiles": "^2.1.1",
"eslint": "^6.4.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-node": "^6.0.1",
Expand All @@ -78,7 +83,7 @@
"sinon-chai": "^3.1.0",
"symlink-or-copy": "^1.2.0",
"ts-node": "^8.0.3",
"typescript": "^3.3.3333"
"typescript": "^3.6.3"
},
"engines": {
"node": "8.* || >= 10.*"
Expand Down
14 changes: 8 additions & 6 deletions test/server_test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const chai = require('chai');
import chai from 'chai';
import Sinon from 'sinon';
import got from 'got';
import fs from 'fs';
import promiseFinally from 'promise.prototype.finally';
import Watcher from '../lib/watcher';

const expect = chai.expect;
const multidepRequire = require('multidep')('test/multidep.json');
const sinon = require('sinon').createSandbox();
const got = require('got');
const fs = require('fs');
const promiseFinally = require('promise.prototype.finally');
const sinon = Sinon.createSandbox();

const Server = require('../lib/server');
const Watcher = require('../lib/watcher');
const Builder = require('../lib/builder');
const MockUI = require('console-ui/mock');

Expand Down
13 changes: 7 additions & 6 deletions test/watcher_adapter_test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import fs from 'fs';
import chai from 'chai';
import sinonChai from 'sinon-chai';
import Sinon from 'sinon';
import TransformNodeWrapper from '../lib/wrappers/transform-node';
import SourceNodeWrapper from '../lib/wrappers/source-node';
import WatcherAdapter from '../lib/watcher_adapter';
import bindFileEvent from '../lib/utils/bind-file-event';

const WatcherAdapter = require('../lib/watcher_adapter');
const bindFileEvent = WatcherAdapter.bindFileEvent;
const fs = require('fs');
const chai = require('chai');
const expect = chai.expect;
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const sinon = require('sinon').createSandbox();
const sinon = Sinon.createSandbox();

describe('WatcherAdapter', function() {
afterEach(function() {
Expand Down
11 changes: 6 additions & 5 deletions test/watcher_test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import path from 'path';
import chai from 'chai';
import sinonChai from 'sinon-chai';
import Sinon from 'sinon';
import Watcher from '../lib/watcher';
import SourceNodeWrapper from '../lib/wrappers/source-node';

const Watcher = require('../lib/watcher');
const path = require('path');
const chai = require('chai');
const expect = chai.expect;
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const sinon = require('sinon').createSandbox();
const sinon = Sinon.createSandbox();

describe('Watcher', function() {
let watcher;
Expand Down
Loading

0 comments on commit b684378

Please sign in to comment.