Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

sane

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sane - npm Package Compare versions

Comparing version 0.3.3 to 0.4.0

144

index.js
var fs = require('fs');
var path = require('path');
var watch = require('watch');
var walker = require('walker');

@@ -13,3 +14,3 @@ var minimatch = require('minimatch');

* @param {Array<string>} files
* @param {Array<string>} globs
* @param {Array<string>} glob
* @return {Watcher}

@@ -52,9 +53,20 @@ * @public

this.stopWatching = this.stopWatching.bind(this);
this.watchdir(dir);
recReaddir(
dir,
this.watchdir,
this.register,
this.emit.bind(this, 'ready')
);
this.filter = this.filter.bind(this);
if (opts.poll) {
this.polling = true;
watch.createMonitor(
dir,
{ interval: opts.interval || DEFAULT_DELAY , filter: this.filter },
this.initPoller.bind(this)
);
} else {
this.watchdir(dir);
recReaddir(
dir,
this.watchdir,
this.register,
this.emit.bind(this, 'ready')
);
}
}

@@ -65,2 +77,26 @@

/**
* Checks a file relative path against the globs array.
*
* @param {string} relativePath
* @return {boolean}
* @private
*/
Watcher.prototype.isFileIncluded = function(relativePath) {
var globs = this.globs;
var matched;
if (globs.length) {
for (var i = 0; i < globs.length; i++) {
if (minimatch(relativePath, globs[i])) {
matched = true;
break;
}
}
} else {
matched = true;
}
return matched;
};
/**
* Register files that matches our globs to know what to type of event to

@@ -84,15 +120,5 @@ * emit in the future.

var relativePath = path.relative(this.root, filepath);
var globs = this.globs;
var matched;
if (globs.length) {
for (var i = 0; i < globs.length; i++) {
if (minimatch(relativePath, globs[i])) {
matched = true;
break;
}
}
} else {
matched = true;
if (!this.isFileIncluded(relativePath)) {
return false;
}
if (!matched) return false;

@@ -170,12 +196,15 @@ var dir = path.dirname(filepath);

/**
* Stop watching a dir.s
* In polling mode stop watching files and directories, in normal mode, stop
* watching files.
*
* @param {string} dir
* @param {string} filepath
* @private
*/
Watcher.prototype.stopWatching = function(dir) {
if (this.watched[dir]) {
this.watched[dir].close();
this.watched[dir] = null;
Watcher.prototype.stopWatching = function(filepath) {
if (this.polling) {
fs.unwatchFile(filepath);
} else if (this.watched[filepath]) {
this.watched[filepath].close();
this.watched[filepath] = null;
}

@@ -216,3 +245,3 @@ };

this.watchdir(fullPath);
this.emitEvent('add', relativePath);
this.emitEvent(ADD_EVENT, relativePath);
} else {

@@ -225,9 +254,9 @@ var registered = this.registered(fullPath);

if (registered) {
this.emitEvent('delete', relativePath);
this.emitEvent(DELETE_EVENT, relativePath);
}
} else if (registered) {
this.emitEvent('change', relativePath);
this.emitEvent(CHANGE_EVENT, relativePath);
} else {
if (this.register(fullPath)) {
this.emitEvent('add', relativePath);
this.emitEvent(ADD_EVENT, relativePath);
}

@@ -252,6 +281,58 @@ }

this.emit(type, file);
}.bind(this), 100);
}.bind(this), DEFAULT_DELAY);
};
/**
* Initiate the polling file watcher with the event emitter passed from
* `watch.watchTree`.
*
* @param {EventEmitter} monitor
* @public
*/
Watcher.prototype.initPoller = function(monitor) {
this.watched = monitor.files;
monitor.on('changed', this.pollerEmit.bind(this, CHANGE_EVENT));
monitor.on('removed', this.pollerEmit.bind(this, DELETE_EVENT));
monitor.on('created', this.pollerEmit.bind(this, ADD_EVENT));
// 1 second wait because mtime is second-based.
setTimeout(this.emit.bind(this, 'ready'), 1000);
};
/**
* Transform and emit an event comming from the poller.
*
* @param {EventEmitter} monitor
* @public
*/
Watcher.prototype.pollerEmit = function(type, file) {
file = path.relative(this.root, file);
this.emit(type, file);
};
/**
* Given a fullpath of a file or directory check if we need to watch it.
*
* @param {string} filepath
* @param {object} stat
* @public
*/
Watcher.prototype.filter = function(filepath, stat) {
return stat.isDirectory() || this.isFileIncluded(
path.relative(this.root, filepath)
);
};
/**
* Constants
*/
var DEFAULT_DELAY = 100;
var CHANGE_EVENT = 'change';
var DELETE_EVENT = 'delete';
var ADD_EVENT = 'add';
/**
* Traverse a directory recursively calling `callback` on every directory.

@@ -271,1 +352,2 @@ *

}
{
"name": "sane",
"version": "0.3.3",
"description": "Sane aims to be fast, small, and reliable file system watcher. No bells and whistles, just change events.",
"version": "0.4.0",
"description": "Sane aims to be fast, small, and reliable file system watcher.",
"main": "index.js",

@@ -21,3 +21,4 @@ "scripts": {

"walker": "~1.0.5",
"minimatch": "~0.2.14"
"minimatch": "~0.2.14",
"watch": "~0.10.0"
},

@@ -24,0 +25,0 @@ "devDependencies": {

@@ -17,6 +17,6 @@ sane

### sane(dir, globs)
### sane(dir, globs, options)
Watches a directory and all it's descendant directorys for changes, deletions, and additions on files and directories.
Shortcut for `new sane.Watcher(files, {globs: globs})`.
Shortcut for `new sane.Watcher(files, {globs: globs, options})`.

@@ -33,2 +33,4 @@ ```js

For `options` see `sane.Watcher`.
### sane.Watcher(dir, options)

@@ -40,2 +42,4 @@

* `glob`: a single string glob pattern or an array of them.
* `poll`: puts the watcher in polling mode. Under the hood that means `fs.watchFile`.
* `interval`: indicates how often the files should be polled. (passed to `fs.watchFile`)

@@ -42,0 +46,0 @@ For the glob pattern documentation, see [minimatch](https://github.com/isaacs/minimatch).

@@ -12,137 +12,119 @@ var os = require('os');

before(function() {
rimraf.sync(testdir);
try {
fs.mkdirSync(testdir);
} catch (e) {}
for (var i = 0; i < 10; i++) {
fs.writeFileSync(jo(testdir, 'file_' + i), 'test_' + i);
var subdir = jo(testdir, 'sub_' + i);
try {
fs.mkdirSync(subdir);
describe('sane in polling mode', harness.bind(this, true));
describe('sand in normal mode', harness.bind(this, false));
function harness(isPolling) {
before(function() {
rimraf.sync(testdir);
try {
fs.mkdirSync(testdir);
} catch (e) {}
for (var j = 0; j < 10; j++) {
fs.writeFileSync(jo(subdir, 'file_' + j), 'test_' + j);
for (var i = 0; i < 10; i++) {
fs.writeFileSync(jo(testdir, 'file_' + i), 'test_' + i);
var subdir = jo(testdir, 'sub_' + i);
try {
fs.mkdirSync(subdir);
} catch (e) {}
for (var j = 0; j < 10; j++) {
fs.writeFileSync(jo(subdir, 'file_' + j), 'test_' + j);
}
}
}
})
})
describe('sane(file)', function() {
beforeEach(function () {
this.watcher = sane(testdir);
});
describe('sane(file)', function() {
beforeEach(function () {
this.watcher = new sane.Watcher(testdir, { poll: isPolling });
});
afterEach(function() {
this.watcher.close();
});
it('emits a ready event', function(done) {
this.watcher.on('ready', done);
});
it('change emits event', function(done) {
var testfile = jo(testdir, 'file_1');
this.watcher.on('change', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
afterEach(function() {
this.watcher.close();
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
it('emits change events for subdir files', function(done) {
var testfile = jo(testdir, 'sub_1', 'file_1');
this.watcher.on('change', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
it('emits a ready event', function(done) {
this.watcher.on('ready', done);
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
it('adding a file will trigger a change', function(done) {
var testfile = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000));
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
it('change emits event', function(done) {
var testfile = jo(testdir, 'file_1');
this.watcher.on('change', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
it('removing a file will emit delete event', function(done) {
var testfile = jo(testdir, 'file_9');
this.watcher.on('delete', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
it('emits change events for subdir files', function(done) {
var testfile = jo(testdir, 'sub_1', 'file_1');
this.watcher.on('change', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
this.watcher.on('ready', function() {
fs.unlink(testfile, 'wow');
});
});
it('removing a dir will emit delete event', function(done) {
var subdir = jo(testdir, 'sub_9');
this.watcher.on('delete', function(filepath) {
// Ignore delete events for files in the dir.
if (path.dirname(filepath) === path.relative(testdir, subdir)) {
return;
}
assert.equal(filepath, path.relative(testdir, subdir));
done();
it('adding a file will trigger a change', function(done) {
var testfile = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000));
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.writeFileSync(testfile, 'wow');
});
});
this.watcher.on('ready', function() {
rimraf.sync(subdir);
});
});
it('adding a dir will emit an add event', function(done) {
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000));
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, subdir));
done();
it('removing a file will emit delete event', function(done) {
var testfile = jo(testdir, 'file_9');
this.watcher.on('delete', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.unlink(testfile, 'wow');
});
});
this.watcher.on('ready', function() {
fs.mkdirSync(subdir);
});
});
it('adding in a new subdir will trigger an add event', function(done) {
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000));
var testfile = jo(subdir, 'file_x' + Math.floor(Math.random() * 10000));
var i = 0;
this.watcher.on('add', function(filepath) {
if (++i === 1) {
it('removing a dir will emit delete event', function(done) {
var subdir = jo(testdir, 'sub_9');
this.watcher.on('delete', function(filepath) {
// Ignore delete events for files in the dir.
if (path.dirname(filepath) === path.relative(testdir, subdir)) {
return;
}
assert.equal(filepath, path.relative(testdir, subdir));
} else {
assert.equal(filepath, path.relative(testdir, testfile));
done();
}
});
this.watcher.on('ready', function() {
fs.mkdirSync(subdir);
defer(function() {
fs.writeFileSync(testfile, 'wow');
});
this.watcher.on('ready', function() {
rimraf.sync(subdir);
});
});
});
it('closes watchers when dirs are deleted', function(done) {
var subdir = jo(testdir, 'sub_1');
var testfile = jo(subdir, 'file_1');
var i = 0;
this.watcher.on('add', function(filepath) {
if (++i === 1) {
it('adding a dir will emit an add event', function(done) {
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000));
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, subdir));
} else {
assert.equal(filepath, path.relative(testdir, testfile));
done();
}
});
this.watcher.on('ready', function() {
fs.mkdirSync(subdir);
});
});
this.watcher.on('ready', function() {
rimraf.sync(subdir);
defer(function() {
it('adding in a new subdir will trigger an add event', function(done) {
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000));
var testfile = jo(subdir, 'file_x' + Math.floor(Math.random() * 10000));
var i = 0;
this.watcher.on('add', function(filepath) {
if (++i === 1) {
assert.equal(filepath, path.relative(testdir, subdir));
} else {
assert.equal(filepath, path.relative(testdir, testfile));
done();
}
});
this.watcher.on('ready', function() {
fs.mkdirSync(subdir);

@@ -154,46 +136,72 @@ defer(function() {

});
});
it('should be ok to remove and then add the same file', function(done) {
var testfile = jo(testdir, 'sub_8', 'file_1');
var i = 0;
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
it('closes watchers when dirs are deleted', function(done) {
var subdir = jo(testdir, 'sub_1');
var testfile = jo(subdir, 'file_1');
var i = 0;
this.watcher.on('add', function(filepath) {
if (++i === 1) {
assert.equal(filepath, path.relative(testdir, subdir));
} else {
assert.equal(filepath, path.relative(testdir, testfile));
done();
}
});
this.watcher.on('ready', function() {
rimraf.sync(subdir);
defer(function() {
fs.mkdirSync(subdir);
defer(function() {
fs.writeFileSync(testfile, 'wow');
});
});
});
});
this.watcher.on('delete', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.unlink(testfile);
defer(function() {
fs.writeFileSync(testfile, 'wow');
it('should be ok to remove and then add the same file', function(done) {
var testfile = jo(testdir, 'sub_8', 'file_1');
var i = 0;
this.watcher.on('add', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
});
this.watcher.on('delete', function(filepath) {
assert.equal(filepath, path.relative(testdir, testfile));
done();
});
this.watcher.on('ready', function() {
fs.unlink(testfile);
defer(function() {
fs.writeFileSync(testfile, 'wow');
});
});
});
});
});
describe('sane(file, glob)', function() {
beforeEach(function () {
this.watcher = sane(testdir, ['**/file_1', '**/file_2']);
});
describe('sane(file, glob)', function() {
beforeEach(function () {
this.watcher = new sane.Watcher(
testdir,
{ glob: ['**/file_1', '**/file_2'], poll: isPolling }
);
});
afterEach(function() {
this.watcher.close();
});
afterEach(function() {
this.watcher.close();
});
it('ignore files according to glob', function (done) {
var i = 0;
this.watcher.on('change', function(filepath) {
assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2');
if (++i == 2) done();
it('ignore files according to glob', function (done) {
var i = 0;
this.watcher.on('change', function(filepath) {
assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2');
if (++i == 2) done();
});
this.watcher.on('ready', function() {
fs.writeFileSync(jo(testdir, 'file_1'), 'wow');
fs.writeFileSync(jo(testdir, 'file_9'), 'wow');
fs.writeFileSync(jo(testdir, 'file_3'), 'wow');
fs.writeFileSync(jo(testdir, 'file_2'), 'wow');
});
});
this.watcher.on('ready', function() {
fs.writeFileSync(jo(testdir, 'file_1'), 'wow');
fs.writeFileSync(jo(testdir, 'file_9'), 'wow');
fs.writeFileSync(jo(testdir, 'file_3'), 'wow');
fs.writeFileSync(jo(testdir, 'file_2'), 'wow');
});
});
});
}

@@ -200,0 +208,0 @@ function defer(fn) {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc