Comparing version 1.0.0-beta.1 to 1.0.0-beta.2
# master | ||
# 1.0.0-beta.2 | ||
* Add `watcher.watch()` method. `Watcher` no longer automatically starts | ||
watching; instead, you must call this method explicitly. It returns a promise | ||
that is fulfilled if you later call `watcher.quit()`, or rejected if watching | ||
one of the source directories fails. | ||
- `server` will call `watcher.watch()` for you. | ||
- In contrast, `getMiddleware` expects a watcher that is already watching. | ||
# 1.0.0-beta.1 | ||
@@ -4,0 +14,0 @@ |
@@ -11,2 +11,4 @@ var path = require('path') | ||
// You must call watcher.watch() before you call `getMiddleware` | ||
// | ||
// Supported options: | ||
@@ -13,0 +15,0 @@ // autoIndex (default: true) - set to false to disable directory listings |
@@ -14,3 +14,3 @@ var Watcher = require('./watcher') | ||
server.watcher = options.watcher || new Watcher(builder, {verbose: true}) | ||
server.watcher = options.watcher || new Watcher(builder) | ||
@@ -21,14 +21,21 @@ server.app = connect().use(middleware(server.watcher)) | ||
server.watcher.watch() | ||
.catch(function(err) { | ||
console.log(err && err.stack || err) | ||
}) | ||
.finally(function() { | ||
builder.cleanup() | ||
server.http.close() | ||
}) | ||
.catch(function(err) { | ||
console.log('Cleanup error:') | ||
console.log(err && err.stack || err) | ||
}) | ||
.finally(function() { | ||
process.exit(1) | ||
}) | ||
// We register these so the 'exit' handler removing temp dirs is called | ||
function cleanupAndExit() { | ||
return server.watcher.quit() | ||
.then(function() { | ||
builder.cleanup() | ||
}) | ||
.catch(function(err) { | ||
console.error('Cleanup error:') | ||
console.error(err && err.stack ? err.stack : err) | ||
}).finally(function() { | ||
process.exit(1) | ||
}) | ||
} | ||
@@ -46,3 +53,3 @@ | ||
console.log('Built with error:') | ||
console.error(err.message) | ||
console.log(err.message) | ||
if (!err.broccoliPayload || !err.broccoliPayload.location.file) { | ||
@@ -49,0 +56,0 @@ console.log('') |
@@ -15,4 +15,3 @@ 'use strict' | ||
this.initialBuildStarted = false | ||
this.check() | ||
this.watchDeferred = null | ||
} | ||
@@ -22,2 +21,11 @@ | ||
Watcher.prototype.watch = function() { | ||
var self = this | ||
if (this.watchDeferred != null) throw new Error('watcher.watch() must only be called once') | ||
this.watchDeferred = RSVP.defer() | ||
self.check() | ||
return this.watchDeferred.promise | ||
} | ||
Watcher.prototype.detectChanges = function () { | ||
@@ -41,8 +49,10 @@ var changedPaths = [] | ||
this.timeoutID = null | ||
// .check can be scheduled via setTimeout or via .then, so we cannot just | ||
// rely on clearTimeout | ||
if (this.quitting) return | ||
// .check can be scheduled via setTimeout or via .then, so we cannot | ||
// just rely on clearTimeout for quitting | ||
if (this.quitting) { | ||
return | ||
} | ||
try { | ||
var interval = this.options.interval || 100 | ||
var changedPaths = this.detectChanges() | ||
@@ -62,2 +72,3 @@ | ||
self.trigger('error', err) | ||
// Do not rethrow | ||
}) | ||
@@ -68,15 +79,14 @@ .then(function() { | ||
}, function(err) { | ||
// Errors here are due to errors in change/error event handlers | ||
console.error('An unexpected error occurred. Watcher quitting.') | ||
console.error(err.stack) | ||
// Rethrow in case a debugging tool wants to catch it | ||
throw err | ||
// A 'change' or 'error' event handler threw an error | ||
self.watchDeferred.reject(err) | ||
}) | ||
} else { | ||
// Schedule next check in 100 milliseconds | ||
var interval = this.options.interval || 100 | ||
this.timeoutID = setTimeout(this.check.bind(this), interval) | ||
} | ||
} catch (err) { | ||
console.error('Uncaught error in Broccoli file watcher:') | ||
console.error(err.stack) | ||
console.error('Watcher quitting') // do not schedule check with setTimeout | ||
// An error occurred in this.detectChanges(); this is usually because one | ||
// of the watched source directories is missing | ||
this.watchDeferred.reject(err) | ||
} | ||
@@ -87,2 +97,4 @@ } | ||
Watcher.prototype.quit = function() { | ||
var self = this | ||
this.quitting = true | ||
@@ -93,3 +105,13 @@ if (this.timeoutID) { | ||
} | ||
return this.currentBuild.catch(function() { /* always fulfill, never reject */ }) | ||
return RSVP.resolve(this.currentBuild) | ||
.catch(function(err) { | ||
// Ignore build errors to stop them from being propagated to | ||
// RSVP.on('error') | ||
}) | ||
.finally(function() { | ||
// It might have been rejected in the meantime, in which case this has | ||
// no effect | ||
self.watchDeferred.resolve() | ||
}) | ||
} |
{ | ||
"name": "broccoli", | ||
"description": "Fast client-side asset builder", | ||
"version": "1.0.0-beta.1", | ||
"version": "1.0.0-beta.2", | ||
"author": "Jo Liss <joliss42@gmail.com>", | ||
@@ -22,3 +22,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"broccoli-kitchen-sink-helpers": "^0.2.9", | ||
"broccoli-kitchen-sink-helpers": "^0.3.0", | ||
"broccoli-slow-trees": "^2.0.0", | ||
@@ -25,0 +25,0 @@ "broccoli-source": "^1.1.0", |
@@ -17,9 +17,17 @@ 'use strict' | ||
// Pass in the Watcher class you wish to test, as well as broccoli.Builder and | ||
// a sleepDuration in milliseconds. Your Watcher class should reliably pick up | ||
// changes within `sleepDuration` milliseconds. | ||
// Parameters: | ||
// | ||
// Watcher: The Watcher class you wish to test | ||
// Builder: The broccoli.Builder class | ||
// sleepDuration: Your Watcher class should reliably pick up | ||
// changes to the file system within `sleepDuration` milliseconds. | ||
// tmpBaseDir: A watcher_test.tmp directory will be created and deleted | ||
// underneath this directory; e.g. if you pass 'test', the directory | ||
// will be test/watcher_test.tmp | ||
// | ||
// Requires mocha for describe/it syntax. | ||
module.exports = function(Watcher, Builder, sleepDuration) { | ||
module.exports = function(Watcher, Builder, sleepDuration, tmpBaseDir) { | ||
var tmpDir = tmpBaseDir + '/watcher_test.tmp' | ||
function sleep() { | ||
@@ -32,7 +40,7 @@ return new RSVP.Promise(function(resolve, reject) { | ||
describe('Watcher', function() { | ||
var builder, buildSpy, watcher | ||
var builder, buildSpy, watcher, watchPromise | ||
beforeEach(function() { | ||
rimraf.sync('test/tmp') | ||
fs.mkdirSync('test/tmp') | ||
rimraf.sync(tmpDir) | ||
fs.mkdirSync(tmpDir) | ||
}) | ||
@@ -44,8 +52,7 @@ | ||
if (watcher) { | ||
var promise = watcher.quit() | ||
watcher = null | ||
return promise.catch(function(err) {}) | ||
return watcher.quit() | ||
} | ||
}) | ||
.then(function() { | ||
watcher = null | ||
if (builder) { | ||
@@ -56,3 +63,3 @@ builder.cleanup() | ||
buildSpy = null | ||
rimraf.sync('test/tmp') | ||
rimraf.sync(tmpDir) | ||
}) | ||
@@ -62,9 +69,9 @@ }) | ||
function makeNodeWithTwoWatchedDirectories() { | ||
fs.mkdirSync('test/tmp/1') | ||
fs.mkdirSync('test/tmp/1/subdir') | ||
fs.writeFileSync('test/tmp/1/subdir/foo', 'x') | ||
fs.mkdirSync('test/tmp/2') | ||
fs.mkdirSync(tmpDir + '/1') | ||
fs.mkdirSync(tmpDir + '/1/subdir') | ||
fs.writeFileSync(tmpDir + '/1/subdir/foo', 'x') | ||
fs.mkdirSync(tmpDir + '/2') | ||
return new plugins.NoopPlugin([ | ||
new WatchedDir('test/tmp/1'), | ||
new WatchedDir('test/tmp/2') | ||
new WatchedDir(tmpDir + '/1'), | ||
new WatchedDir(tmpDir + '/2') | ||
]) | ||
@@ -77,2 +84,3 @@ } | ||
watcher = new Watcher(builder) | ||
watchPromise = watcher.watch() | ||
} | ||
@@ -105,3 +113,3 @@ | ||
return expect(triggersRebuild(function() { | ||
fs.writeFileSync('test/tmp/1/subdir/bar', 'hello') | ||
fs.writeFileSync(tmpDir + '/1/subdir/bar', 'hello') | ||
})).to.be.eventually.true | ||
@@ -113,3 +121,3 @@ }) | ||
return expect(triggersRebuild(function() { | ||
fs.unlinkSync('test/tmp/1/subdir/foo') | ||
fs.unlinkSync(tmpDir + '/1/subdir/foo') | ||
})).to.be.eventually.true | ||
@@ -121,3 +129,3 @@ }) | ||
return expect(triggersRebuild(function() { | ||
fs.writeFileSync('test/tmp/1/subdir/foo', 'y') | ||
fs.writeFileSync(tmpDir + '/1/subdir/foo', 'y') | ||
})).to.be.eventually.true | ||
@@ -129,3 +137,3 @@ }) | ||
return expect(triggersRebuild(function() { | ||
fs.mkdirSync('test/tmp/1/another-subdir') | ||
fs.mkdirSync(tmpDir + '/1/another-subdir') | ||
})).to.be.eventually.true | ||
@@ -135,4 +143,4 @@ }) | ||
describe('Watcher.currentBuild', function() { | ||
it ('is fulfilled when the build succeeds', function() { | ||
describe('watcher.currentBuild', function() { | ||
it('is fulfilled when the build succeeds', function() { | ||
setUpBuilderAndWatcher(new plugins.NoopPlugin) | ||
@@ -142,3 +150,3 @@ return expect(watcher.currentBuild).to.be.fulfilled | ||
it ('is rejected when the build fails', function() { | ||
it('is rejected when the build fails', function() { | ||
setUpBuilderAndWatcher(new plugins.FailingPlugin(new Error('fail me'))) | ||
@@ -204,12 +212,31 @@ return expect(watcher.currentBuild).to.be.rejected | ||
describe('watcher.watch() promise', function() { | ||
it('is fulfilled when watcher.quit() is called', function() { | ||
setUpBuilderAndWatcher(new plugins.FailingPlugin) // even if build fails | ||
watcher.quit() | ||
return expect(watchPromise).to.be.fulfilled | ||
}) | ||
// We could relax this in the future and turn missing source directories | ||
// into transient build errors, by always watching the parent | ||
// directories | ||
it('is rejected when a watched source directory does not exist', function() { | ||
setUpBuilderAndWatcher(new WatchedDir('doesnotexist')) | ||
return expect(watchPromise).to.be.rejected | ||
}) | ||
}) | ||
describe('watcher.quit()', function() { | ||
it('stops watching', function() { | ||
it('if no build is in progress, just stops watching and fulfills watch() promise', function() { | ||
setUpBuilderAndWatcher(makeNodeWithTwoWatchedDirectories()) | ||
return watcher.currentBuild | ||
.then(function() { | ||
expect(buildSpy).to.have.been.calledOnce | ||
return watcher.quit() | ||
var quitPromise = watcher.quit() | ||
// Must be a promise (that presumably fulfills immediately), even | ||
// though no build is in progress | ||
expect(quitPromise.then).to.be.a('function') | ||
return quitPromise | ||
}) | ||
.then(function() { | ||
fs.writeFileSync('test/tmp/1/subdir/bar', 'hello') | ||
fs.writeFileSync(tmpDir + '/1/subdir/bar', 'hello') | ||
}) | ||
@@ -222,4 +249,4 @@ .then(sleep) | ||
it('returns a promise until the current rebuild has finished', function() { | ||
var node = new plugins.AsyncPlugin | ||
it('if a build is in progress, returns a promise until it finishes, then stops watching', function() { | ||
var node = new plugins.AsyncPlugin([makeNodeWithTwoWatchedDirectories()]) | ||
setUpBuilderAndWatcher(node) | ||
@@ -232,2 +259,3 @@ | ||
.then(function() { | ||
// Quit while node is being built | ||
quitPromise = watcher.quit().then(function() { | ||
@@ -239,2 +267,3 @@ quitPromiseHasBeenFulfilled = true | ||
.then(function() { | ||
// .quit() promise should not be fulfilled until build has finished | ||
expect(quitPromiseHasBeenFulfilled).to.be.false | ||
@@ -244,2 +273,11 @@ node.finishBuild() | ||
}) | ||
.then(function() { | ||
fs.writeFileSync(tmpDir + '/1/subdir/bar', 'hello') | ||
}) | ||
.then(sleep) | ||
.then(function() { | ||
// No further rebuilds should happen, even if files change | ||
expect(buildSpy).to.have.been.calledOnce | ||
}) | ||
}) | ||
@@ -246,0 +284,0 @@ |
@@ -13,2 +13,2 @@ 'use strict' | ||
require('./watcher_test_suite')(FastWatcher, Builder, 15) | ||
require('./watcher_test_suite')(FastWatcher, Builder, 15, 'test') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
111719
1764
1
+ Addedbroccoli-kitchen-sink-helpers@0.3.1(transitive)
- Removedbroccoli-kitchen-sink-helpers@0.2.9(transitive)