Skip to content

Commit

Permalink
fix: Only traverse directories that can match the glob base (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
phated authored Apr 7, 2024
1 parent 6dfc6bf commit 8e74e21
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:

- name: Run tests
run: npm test
env:
TEMP: ${{ runner.temp }}

- name: Coveralls
uses: coverallsapp/github-action@v1.1.2
Expand Down
57 changes: 46 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function walkdir() {

var ee = new EventEmitter();

var queue = fastq(readdir, 1);
var queue = fastq(process, 1);
queue.drain = function () {
ee.emit('end');
};
Expand All @@ -52,20 +52,39 @@ function walkdir() {
ee.end = function () {
queue.kill();
};
ee.walk = function (filepath) {
queue.push(filepath);
};
ee.walk = walk;
ee.exists = exists;

function isDefined(value) {
return typeof value !== 'undefined';
}

function queuePush(value) {
queue.push(value);
function walk(path) {
queue.push({ action: 'walk', path: path });
}

function exists(path) {
queue.push({ action: 'exists', path: path });
}

function readdir(filepath, cb) {
fs.readdir(filepath, readdirOpts, onReaddir);
function process(data, cb) {
if (data.action === 'walk') {
fs.readdir(data.path, readdirOpts, onReaddir);
} else {
fs.stat(data.path, onStat);
}

function onStat(err, stat) {
if (err) {
// Ignore errors but also don't emit the path
return cb();
}

// `stat` has `isDirectory()` which is what we use from Dirent
ee.emit('path', data.path, stat);

cb();
}

function onReaddir(err, dirents) {
if (err) {
Expand All @@ -77,14 +96,14 @@ function walkdir() {
return cb(err);
}

dirs.filter(isDefined).forEach(queuePush);
dirs.filter(isDefined).forEach(walk);

cb();
});
}

function processDirents(dirent, key, cb) {
var nextpath = path.join(filepath, dirent.name);
var nextpath = path.join(data.path, dirent.name);
ee.emit('path', nextpath, dirent);

if (dirent.isDirectory()) {
Expand Down Expand Up @@ -138,6 +157,10 @@ function validateGlobs(globs) {
}
}

function isPositiveGlob(glob) {
return !isNegatedGlob(glob).negated;
}

function validateOptions(opts) {
if (typeof opts.cwd !== 'string') {
throw new Error('The `cwd` option must be a string');
Expand Down Expand Up @@ -251,7 +274,19 @@ function globStream(globs, opt) {
walker.on('path', onPath);
walker.once('end', onEnd);
walker.once('error', onError);
walker.walk(ourOpt.cwd);
ourGlobs.forEach(function (glob) {
if (isGlob(glob)) {
// We only want to walk the glob-parent directories of any positive glob
// to reduce the amount of files have to check.
if (isPositiveGlob(glob)) {
var base = globParent(glob);
walker.walk(base);
}
} else {
// If the strig is not a glob, we just check for the existence of it.
walker.exists(glob);
}
});

function read(cb) {
walker.resume();
Expand Down
57 changes: 56 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var sinon = require('sinon');

// Need to wrap this to cause walker to emit an error
var fs = require('fs');
var os = require('os');

var globStream = require('../');

Expand Down Expand Up @@ -370,6 +371,11 @@ function suite(moduleName) {
});

it('emits all objects (unordered) when given multiple absolute paths and no cwd', function (done) {
var testFile = path.join(os.tmpdir(), "glob-stream-test.txt");
fs.writeFileSync(testFile, "test");

var tmp = deWindows(os.tmpdir());

var expected = [
{
cwd: cwd,
Expand All @@ -387,24 +393,73 @@ function suite(moduleName) {
base: dir + '/fixtures/whatsgoingon',
path: dir + '/fixtures/whatsgoingon/test.js',
},
{
cwd: cwd,
base: tmp,
path: tmp + '/glob-stream-test.txt',
}
];

var paths = [
dir + '/fixtures/whatsgoingon/hey/isaidhey/whatsgoingon/test.txt',
dir + '/fixtures/test.coffee',
dir + '/fixtures/whatsgoingon/test.js',
tmp + '/glob-stream-*.txt',
];

console.log(paths);

function assert(pathObjs) {
expect(pathObjs.length).toEqual(3);
fs.unlinkSync(testFile, "test");
expect(pathObjs.length).toEqual(4);
expect(pathObjs).toContainEqual(expected[0]);
expect(pathObjs).toContainEqual(expected[1]);
expect(pathObjs).toContainEqual(expected[2]);
expect(pathObjs).toContainEqual(expected[3]);
}

stream.pipeline([globStream(paths), concat(assert)], done);
});

it('resolves globs when process.chdir() changes the cwd', function (done) {
var prevCwd = process.cwd();
process.chdir('test/fixtures/stuff');

var expected = {
cwd: dir + '/fixtures/stuff',
base: dir + '/fixtures',
path: dir + '/fixtures/test.coffee',
};

function assert(pathObjs) {
process.chdir(prevCwd);

expect(pathObjs.length).toEqual(1);
expect(pathObjs[0]).toEqual(expected);
}

stream.pipeline([globStream(['../*.coffee']), concat(assert)], done);
});

// https://github.com/gulpjs/glob-stream/issues/129
it('does not take a long time when when checking a singular glob in the project root', function (done) {
// Extremely short timeout to ensure we aren't traversing node_modules
this.timeout(20);

var expected = {
cwd: cwd,
base: cwd,
path: cwd + '/package.json',
};

function assert(pathObjs) {
expect(pathObjs.length).toEqual(1);
expect(pathObjs[0]).toEqual(expected);
}

stream.pipeline([globStream(['./package.json']), concat(assert)], done);
});

it('removes duplicate objects from the stream using default (path) filter', function (done) {
var expected = {
cwd: dir,
Expand Down

0 comments on commit 8e74e21

Please sign in to comment.