Skip to content

Commit 2c06ae5

Browse files
committedNov 9, 2023
Require Node.js 18
1 parent 76c70ab commit 2c06ae5

12 files changed

+130
-128
lines changed
 

‎.github/funding.yml

-4
This file was deleted.

‎.github/workflows/main.yml

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
node-version:
13+
- 20
1314
- 18
14-
- 16
15-
- 14
1615
os:
1716
- ubuntu-latest
1817
- macos-latest
1918
- windows-latest
2019
steps:
21-
- uses: actions/checkout@v3
22-
- uses: actions/setup-node@v3
20+
- uses: actions/checkout@v4
21+
- uses: actions/setup-node@v4
2322
with:
2423
node-version: ${{ matrix.node-version }}
2524
- run: npm install

‎bench.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import fs from 'node:fs';
33
import path from 'node:path';
44
import {fileURLToPath} from 'node:url';
55
import Benchmark from 'benchmark';
6-
import rimraf from 'rimraf';
76
import * as globbyMainBranch from '@globby/main-branch';
87
import gs from 'glob-stream';
98
import fastGlob from 'fast-glob';
@@ -83,7 +82,7 @@ const benchs = [
8382

8483
const before = () => {
8584
process.chdir(__dirname);
86-
rimraf.sync(BENCH_DIR);
85+
fs.rmdirSync(BENCH_DIR, {recursive: true});
8786
fs.mkdirSync(BENCH_DIR);
8887
process.chdir(BENCH_DIR);
8988

@@ -100,7 +99,7 @@ const before = () => {
10099

101100
const after = () => {
102101
process.chdir(__dirname);
103-
rimraf.sync(BENCH_DIR);
102+
fs.rmdirSync(BENCH_DIR, {recursive: true});
104103
};
105104

106105
const suites = [];

‎ignore.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import process from 'node:process';
22
import fs from 'node:fs';
3+
import fsPromises from 'node:fs/promises';
34
import path from 'node:path';
45
import fastGlob from 'fast-glob';
56
import gitIgnore from 'ignore';
67
import slash from 'slash';
7-
import {toPath, isNegativePattern} from './utilities.js';
8+
import {toPath} from 'unicorn-magic';
9+
import {isNegativePattern} from './utilities.js';
810

911
const ignoreFilesGlobOptions = {
1012
ignore: [
@@ -57,7 +59,7 @@ const getIsIgnoredPredicate = (files, cwd) => {
5759
};
5860

5961
const normalizeOptions = (options = {}) => ({
60-
cwd: toPath(options.cwd) || process.cwd(),
62+
cwd: toPath(options.cwd) ?? process.cwd(),
6163
suppressErrors: Boolean(options.suppressErrors),
6264
deep: typeof options.deep === 'number' ? options.deep : Number.POSITIVE_INFINITY,
6365
});
@@ -70,7 +72,7 @@ export const isIgnoredByIgnoreFiles = async (patterns, options) => {
7072
const files = await Promise.all(
7173
paths.map(async filePath => ({
7274
filePath,
73-
content: await fs.promises.readFile(filePath, 'utf8'),
75+
content: await fsPromises.readFile(filePath, 'utf8'),
7476
})),
7577
);
7678

‎index.d.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import {Options as FastGlobOptions, Entry} from 'fast-glob';
1+
import {type Options as FastGlobOptions, type Entry} from 'fast-glob';
22

33
export type GlobEntry = Entry;
44

5-
export interface GlobTask {
5+
export type GlobTask = {
66
readonly patterns: string[];
77
readonly options: Options;
8-
}
8+
};
99

1010
export type ExpandDirectoriesOption =
1111
| boolean
@@ -14,7 +14,7 @@ export type ExpandDirectoriesOption =
1414

1515
type FastGlobOptionsWithoutCwd = Omit<FastGlobOptions, 'cwd'>;
1616

17-
export interface Options extends FastGlobOptionsWithoutCwd {
17+
export type Options = {
1818
/**
1919
If set to `true`, `globby` will automatically glob directories for you. If you define an `Array` it will only glob files that matches the patterns inside the `Array`. You can also define an `Object` with `files` and `extensions` like in the example below.
2020
@@ -61,11 +61,11 @@ export interface Options extends FastGlobOptionsWithoutCwd {
6161
@default process.cwd()
6262
*/
6363
readonly cwd?: URL | string;
64-
}
64+
} & FastGlobOptionsWithoutCwd;
6565

66-
export interface GitignoreOptions {
66+
export type GitignoreOptions = {
6767
readonly cwd?: URL | string;
68-
}
68+
};
6969

7070
export type GlobbyFilterFunction = (path: URL | string) => boolean;
7171

‎index.js

+64-31
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,67 @@
1+
import process from 'node:process';
12
import fs from 'node:fs';
23
import nodePath from 'node:path';
3-
import merge2 from 'merge2';
4+
import mergeStreams from '@sindresorhus/merge-streams';
45
import fastGlob from 'fast-glob';
5-
import dirGlob from 'dir-glob';
6+
import {isDirectory, isDirectorySync} from 'path-type';
7+
import {toPath} from 'unicorn-magic';
68
import {
79
GITIGNORE_FILES_PATTERN,
810
isIgnoredByIgnoreFiles,
911
isIgnoredByIgnoreFilesSync,
1012
} from './ignore.js';
11-
import {FilterStream, toPath, isNegativePattern} from './utilities.js';
13+
import {isNegativePattern} from './utilities.js';
1214

1315
const assertPatternsInput = patterns => {
1416
if (patterns.some(pattern => typeof pattern !== 'string')) {
1517
throw new TypeError('Patterns must be a string or an array of strings');
1618
}
1719
};
1820

21+
const normalizePathForDirectoryGlob = (filePath, cwd) => {
22+
const path = isNegativePattern(filePath) ? filePath.slice(1) : filePath;
23+
return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path);
24+
};
25+
26+
const getDirectoryGlob = ({directoryPath, files, extensions}) => {
27+
const extensionGlob = extensions?.length > 0 ? `.${extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]}` : '';
28+
return files
29+
? files.map(file => nodePath.posix.join(directoryPath, `**/${nodePath.extname(file) ? file : `${file}${extensionGlob}`}`))
30+
: [nodePath.posix.join(directoryPath, `**${extensionGlob ? `/${extensionGlob}` : ''}`)];
31+
};
32+
33+
const directoryToGlob = async (directoryPaths, {
34+
cwd = process.cwd(),
35+
files,
36+
extensions,
37+
} = {}) => {
38+
const globs = await Promise.all(directoryPaths.map(async directoryPath =>
39+
(await isDirectory(normalizePathForDirectoryGlob(directoryPath, cwd))) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath),
40+
);
41+
42+
return globs.flat();
43+
};
44+
45+
const directoryToGlobSync = (directoryPaths, {
46+
cwd = process.cwd(),
47+
files,
48+
extensions,
49+
} = {}) => directoryPaths.flatMap(directoryPath => isDirectorySync(normalizePathForDirectoryGlob(directoryPath, cwd)) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath);
50+
1951
const toPatternsArray = patterns => {
2052
patterns = [...new Set([patterns].flat())];
2153
assertPatternsInput(patterns);
2254
return patterns;
2355
};
2456

25-
const checkCwdOption = options => {
26-
if (!options.cwd) {
57+
const checkCwdOption = cwd => {
58+
if (!cwd) {
2759
return;
2860
}
2961

3062
let stat;
3163
try {
32-
stat = fs.statSync(options.cwd);
64+
stat = fs.statSync(cwd);
3365
} catch {
3466
return;
3567
}
@@ -42,20 +74,18 @@ const checkCwdOption = options => {
4274
const normalizeOptions = (options = {}) => {
4375
options = {
4476
...options,
45-
ignore: options.ignore || [],
46-
expandDirectories: options.expandDirectories === undefined
47-
? true
48-
: options.expandDirectories,
77+
ignore: options.ignore ?? [],
78+
expandDirectories: options.expandDirectories ?? true,
4979
cwd: toPath(options.cwd),
5080
};
5181

52-
checkCwdOption(options);
82+
checkCwdOption(options.cwd);
5383

5484
return options;
5585
};
5686

57-
const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
58-
const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
87+
const normalizeArguments = function_ => async (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
88+
const normalizeArgumentsSync = function_ => (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
5989

6090
const getIgnoreFilesPatterns = options => {
6191
const {ignoreFiles, gitignore} = options;
@@ -86,16 +116,19 @@ const createFilterFunction = isIgnored => {
86116
const seen = new Set();
87117

88118
return fastGlobResult => {
89-
const path = fastGlobResult.path || fastGlobResult;
90-
const pathKey = nodePath.normalize(path);
91-
const seenOrIgnored = seen.has(pathKey) || (isIgnored && isIgnored(path));
119+
const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
120+
121+
if (seen.has(pathKey) || (isIgnored && isIgnored(pathKey))) {
122+
return false;
123+
}
124+
92125
seen.add(pathKey);
93-
return !seenOrIgnored;
126+
127+
return true;
94128
};
95129
};
96130

97131
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
98-
const unionFastGlobStreams = (streams, filter) => merge2(streams).pipe(new FilterStream(fastGlobResult => filter(fastGlobResult)));
99132

100133
const convertNegativePatterns = (patterns, options) => {
101134
const tasks = [];
@@ -133,7 +166,7 @@ const convertNegativePatterns = (patterns, options) => {
133166
return tasks;
134167
};
135168

136-
const getDirGlobOptions = (options, cwd) => ({
169+
const normalizeExpandDirectoriesOption = (options, cwd) => ({
137170
...(cwd ? {cwd} : {}),
138171
...(Array.isArray(options) ? {files: options} : options),
139172
});
@@ -147,8 +180,7 @@ const generateTasks = async (patterns, options) => {
147180
return globTasks;
148181
}
149182

150-
const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
151-
const ignoreExpandOptions = cwd ? {cwd} : undefined;
183+
const directoryToGlobOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
152184

153185
return Promise.all(
154186
globTasks.map(async task => {
@@ -158,8 +190,8 @@ const generateTasks = async (patterns, options) => {
158190
patterns,
159191
options.ignore,
160192
] = await Promise.all([
161-
dirGlob(patterns, patternExpandOptions),
162-
dirGlob(options.ignore, ignoreExpandOptions),
193+
directoryToGlob(patterns, directoryToGlobOptions),
194+
directoryToGlob(options.ignore, {cwd}),
163195
]);
164196

165197
return {patterns, options};
@@ -169,20 +201,18 @@ const generateTasks = async (patterns, options) => {
169201

170202
const generateTasksSync = (patterns, options) => {
171203
const globTasks = convertNegativePatterns(patterns, options);
172-
173204
const {cwd, expandDirectories} = options;
174205

175206
if (!expandDirectories) {
176207
return globTasks;
177208
}
178209

179-
const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
180-
const ignoreExpandOptions = cwd ? {cwd} : undefined;
210+
const directoryToGlobSyncOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
181211

182212
return globTasks.map(task => {
183213
let {patterns, options} = task;
184-
patterns = dirGlob.sync(patterns, patternExpandOptions);
185-
options.ignore = dirGlob.sync(options.ignore, ignoreExpandOptions);
214+
patterns = directoryToGlobSync(patterns, directoryToGlobSyncOptions);
215+
options.ignore = directoryToGlobSync(options.ignore, {cwd});
186216
return {patterns, options};
187217
});
188218
};
@@ -195,25 +225,28 @@ export const globby = normalizeArguments(async (patterns, options) => {
195225
generateTasks(patterns, options),
196226
getFilter(options),
197227
]);
198-
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
199228

229+
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
200230
return unionFastGlobResults(results, filter);
201231
});
202232

203233
export const globbySync = normalizeArgumentsSync((patterns, options) => {
204234
const tasks = generateTasksSync(patterns, options);
205235
const filter = getFilterSync(options);
206236
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
207-
208237
return unionFastGlobResults(results, filter);
209238
});
210239

211240
export const globbyStream = normalizeArgumentsSync((patterns, options) => {
212241
const tasks = generateTasksSync(patterns, options);
213242
const filter = getFilterSync(options);
214243
const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
244+
const stream = mergeStreams(streams).filter(fastGlobResult => filter(fastGlobResult));
245+
246+
// TODO: Make it return a web stream at some point.
247+
// return Readable.toWeb(stream);
215248

216-
return unionFastGlobStreams(streams, filter);
249+
return stream;
217250
});
218251

219252
export const isDynamicPattern = normalizeArgumentsSync(

‎index.test-d.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import {Buffer} from 'node:buffer';
2-
import {URL} from 'node:url';
1+
import {type Buffer} from 'node:buffer';
32
import {expectType} from 'tsd';
43
import {
5-
GlobTask,
6-
GlobEntry,
7-
GlobbyFilterFunction,
4+
type GlobTask,
5+
type GlobEntry,
6+
type GlobbyFilterFunction,
87
globby,
98
globbySync,
109
globbyStream,

‎package.json

+16-18
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
"url": "https://sindresorhus.com"
1212
},
1313
"type": "module",
14-
"exports": "./index.js",
14+
"exports": {
15+
"types": "./index.d.ts",
16+
"default": "./index.js"
17+
},
18+
"sideEffects": false,
1519
"engines": {
16-
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
20+
"node": ">=18"
1721
},
1822
"scripts": {
1923
"bench": "npm update @globby/main-branch glob-stream fast-glob && node bench.js",
@@ -59,33 +63,27 @@
5963
"git"
6064
],
6165
"dependencies": {
62-
"dir-glob": "^3.0.1",
63-
"fast-glob": "^3.3.0",
66+
"@sindresorhus/merge-streams": "^1.0.0",
67+
"fast-glob": "^3.3.2",
6468
"ignore": "^5.2.4",
65-
"merge2": "^1.4.1",
66-
"slash": "^4.0.0"
69+
"path-type": "^5.0.0",
70+
"slash": "^5.1.0",
71+
"unicorn-magic": "^0.1.0"
6772
},
6873
"devDependencies": {
6974
"@globby/main-branch": "sindresorhus/globby#main",
70-
"@types/node": "^20.3.3",
75+
"@types/node": "^20.9.0",
7176
"ava": "^5.3.1",
7277
"benchmark": "2.1.4",
7378
"glob-stream": "^8.0.0",
74-
"rimraf": "^5.0.1",
75-
"tempy": "^3.0.0",
76-
"tsd": "^0.28.1",
77-
"typescript": "^5.1.6",
78-
"xo": "^0.54.2"
79+
"tempy": "^3.1.0",
80+
"tsd": "^0.29.0",
81+
"xo": "^0.56.0"
7982
},
8083
"xo": {
8184
"ignores": [
8285
"fixtures"
83-
],
84-
"rules": {
85-
"@typescript-eslint/consistent-type-definitions": "off",
86-
"n/prefer-global/url": "off",
87-
"@typescript-eslint/consistent-type-imports": "off"
88-
}
86+
]
8987
},
9088
"ava": {
9189
"files": [

‎readme.md

+15-25
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Based on [`fast-glob`](https://github.com/mrmlnc/fast-glob) but adds a bunch of
1515

1616
## Install
1717

18-
```
19-
$ npm install globby
18+
```sh
19+
npm install globby
2020
```
2121

2222
## Usage
@@ -66,17 +66,15 @@ If set to `true`, `globby` will automatically glob directories for you. If you d
6666
```js
6767
import {globby} from 'globby';
6868

69-
(async () => {
70-
const paths = await globby('images', {
71-
expandDirectories: {
72-
files: ['cat', 'unicorn', '*.jpg'],
73-
extensions: ['png']
74-
}
75-
});
76-
77-
console.log(paths);
78-
//=> ['cat.png', 'unicorn.png', 'cow.jpg', 'rainbow.jpg']
79-
})();
69+
const paths = await globby('images', {
70+
expandDirectories: {
71+
files: ['cat', 'unicorn', '*.jpg'],
72+
extensions: ['png']
73+
}
74+
});
75+
76+
console.log(paths);
77+
//=> ['cat.png', 'unicorn.png', 'cow.jpg', 'rainbow.jpg']
8078
```
8179

8280
Note that if you set this option to `false`, you won't get back matched directories unless you set `onlyFiles: false`.
@@ -105,16 +103,14 @@ Returns `string[]` of matching paths.
105103

106104
Returns a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) of matching paths.
107105

108-
Since Node.js 10, [readable streams are iterable](https://nodejs.org/api/stream.html#stream_readable_symbol_asynciterator), so you can loop over glob matches in a [`for await...of` loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) like this:
106+
For example, loop over glob matches in a [`for await...of` loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) like this:
109107

110108
```js
111109
import {globbyStream} from 'globby';
112110

113-
(async () => {
114-
for await (const path of globbyStream('*.tmp')) {
115-
console.log(path);
116-
}
117-
})();
111+
for await (const path of globbyStream('*.tmp')) {
112+
console.log(path);
113+
}
118114
```
119115

120116
### generateGlobTasks(patterns, options?)
@@ -169,12 +165,6 @@ Just a quick overview.
169165

170166
[Various patterns and expected matches.](https://github.com/sindresorhus/multimatch/blob/main/test/test.js)
171167

172-
## globby for enterprise
173-
174-
Available as part of the Tidelift Subscription.
175-
176-
The maintainers of globby and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-globby?utm_source=npm-globby&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
177-
178168
## Related
179169

180170
- [multimatch](https://github.com/sindresorhus/multimatch) - Match against a list instead of the filesystem

‎tests/generate-glob-tasks.js

+12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import util from 'node:util';
22
import process from 'node:process';
33
import path from 'node:path';
4+
import fs from 'node:fs';
45
import test from 'ava';
6+
import {temporaryDirectory} from 'tempy';
57
import {
68
generateGlobTasks,
79
generateGlobTasksSync,
@@ -220,3 +222,13 @@ test('random patterns', async t => {
220222
);
221223
}
222224
});
225+
226+
// Test for https://github.com/sindresorhus/globby/issues/147
227+
test.failing('expandDirectories should work with globstar prefix', async t => {
228+
const cwd = temporaryDirectory();
229+
const filePath = path.join(cwd, 'a', 'b');
230+
fs.mkdirSync(filePath, {recursive: true});
231+
const tasks = await runGenerateGlobTasks(t, ['**/b'], {cwd});
232+
t.is(tasks.length, 1);
233+
t.deepEqual(tasks[0].patterns, ['**/b/**']);
234+
});

‎tests/globby.js

+2-12
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,12 @@ const stabilizeResult = result => result
3939

4040
return fastGlobResult;
4141
})
42-
.sort((a, b) => (a.path || a).localeCompare(b.path || b));
43-
44-
const streamToArray = async stream => {
45-
const result = [];
46-
for await (const chunk of stream) {
47-
result.push(chunk);
48-
}
49-
50-
return result;
51-
};
42+
.sort((a, b) => (a.path ?? a).localeCompare(b.path ?? b));
5243

5344
const runGlobby = async (t, patterns, options) => {
5445
const syncResult = globbySync(patterns, options);
5546
const promiseResult = await globby(patterns, options);
56-
// TODO: Use `stream.toArray()` when targeting Node.js 16.
57-
const streamResult = await streamToArray(globbyStream(patterns, options));
47+
const streamResult = await globbyStream(patterns, options).toArray();
5848

5949
const result = stabilizeResult(promiseResult);
6050
t.deepEqual(

‎utilities.js

-16
Original file line numberDiff line numberDiff line change
@@ -1,17 +1 @@
1-
import {fileURLToPath} from 'node:url';
2-
import {Transform} from 'node:stream';
3-
4-
export const toPath = urlOrPath => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
5-
6-
export class FilterStream extends Transform {
7-
constructor(filter) {
8-
super({
9-
objectMode: true,
10-
transform(data, encoding, callback) {
11-
callback(undefined, filter(data) ? data : undefined);
12-
},
13-
});
14-
}
15-
}
16-
171
export const isNegativePattern = pattern => pattern[0] === '!';

0 commit comments

Comments
 (0)
Please sign in to comment.