Skip to content

Commit 73c0aca

Browse files
authoredJan 15, 2022
Support URL as cwd (#201)
1 parent de4082b commit 73c0aca

8 files changed

+216
-124
lines changed
 

‎gitignore.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from 'node:path';
44
import fastGlob from 'fast-glob';
55
import gitIgnore from 'ignore';
66
import slash from 'slash';
7+
import toPath from './to-path.js';
78

89
const DEFAULT_IGNORE = [
910
'**/node_modules/**',
@@ -82,7 +83,7 @@ const getFileSync = (file, cwd) => {
8283
const normalizeOptions = ({
8384
ignore = [],
8485
cwd = slash(process.cwd()),
85-
} = {}) => ({ignore: [...DEFAULT_IGNORE, ...ignore], cwd});
86+
} = {}) => ({ignore: [...DEFAULT_IGNORE, ...ignore], cwd: toPath(cwd)});
8687

8788
export const isGitIgnored = async options => {
8889
options = normalizeOptions(options);

‎gitignore.test.js

+80-61
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,120 @@
11
import path from 'node:path';
2-
import {fileURLToPath} from 'node:url';
2+
import {fileURLToPath, pathToFileURL} from 'node:url';
33
import test from 'ava';
44
import slash from 'slash';
55
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';
66

77
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const getCwdValues = cwd => [cwd, pathToFileURL(cwd), pathToFileURL(cwd).href];
89

910
test('gitignore', async t => {
10-
const cwd = path.join(__dirname, 'fixtures/gitignore');
11-
const isIgnored = await isGitIgnored({cwd});
12-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
13-
const expected = ['bar.js'];
14-
t.deepEqual(actual, expected);
11+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
12+
// eslint-disable-next-line no-await-in-loop
13+
const isIgnored = await isGitIgnored({cwd});
14+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
15+
const expected = ['bar.js'];
16+
t.deepEqual(actual, expected);
17+
}
1518
});
1619

1720
test('gitignore - mixed path styles', async t => {
18-
const cwd = path.join(__dirname, 'fixtures/gitignore');
19-
const isIgnored = await isGitIgnored({cwd});
20-
t.true(isIgnored(slash(path.resolve(cwd, 'foo.js'))));
21+
const directory = path.join(__dirname, 'fixtures/gitignore');
22+
for (const cwd of getCwdValues(directory)) {
23+
// eslint-disable-next-line no-await-in-loop
24+
const isIgnored = await isGitIgnored({cwd});
25+
t.true(isIgnored(slash(path.resolve(directory, 'foo.js'))));
26+
}
2127
});
2228

2329
test('gitignore - os paths', async t => {
24-
const cwd = path.join(__dirname, 'fixtures/gitignore');
25-
const isIgnored = await isGitIgnored({cwd});
26-
t.true(isIgnored(path.resolve(cwd, 'foo.js')));
30+
const directory = path.join(__dirname, 'fixtures/gitignore');
31+
for (const cwd of getCwdValues(directory)) {
32+
// eslint-disable-next-line no-await-in-loop
33+
const isIgnored = await isGitIgnored({cwd});
34+
t.true(isIgnored(path.resolve(directory, 'foo.js')));
35+
}
2736
});
2837

2938
test('gitignore - sync', t => {
30-
const cwd = path.join(__dirname, 'fixtures/gitignore');
31-
const isIgnored = isGitIgnoredSync({cwd});
32-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
33-
const expected = ['bar.js'];
34-
t.deepEqual(actual, expected);
39+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
40+
const isIgnored = isGitIgnoredSync({cwd});
41+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
42+
const expected = ['bar.js'];
43+
t.deepEqual(actual, expected);
44+
}
3545
});
3646

3747
test('ignore ignored .gitignore', async t => {
38-
const cwd = path.join(__dirname, 'fixtures/gitignore');
3948
const ignore = ['**/.gitignore'];
4049

41-
const isIgnored = await isGitIgnored({cwd, ignore});
42-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
43-
const expected = ['foo.js', 'bar.js'];
44-
t.deepEqual(actual, expected);
50+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
51+
// eslint-disable-next-line no-await-in-loop
52+
const isIgnored = await isGitIgnored({cwd, ignore});
53+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
54+
const expected = ['foo.js', 'bar.js'];
55+
t.deepEqual(actual, expected);
56+
}
4557
});
4658

4759
test('ignore ignored .gitignore - sync', t => {
48-
const cwd = path.join(__dirname, 'fixtures/gitignore');
4960
const ignore = ['**/.gitignore'];
5061

51-
const isIgnored = isGitIgnoredSync({cwd, ignore});
52-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
53-
const expected = ['foo.js', 'bar.js'];
54-
t.deepEqual(actual, expected);
62+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
63+
const isIgnored = isGitIgnoredSync({cwd, ignore});
64+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
65+
const expected = ['foo.js', 'bar.js'];
66+
t.deepEqual(actual, expected);
67+
}
5568
});
5669

5770
test('negative gitignore', async t => {
58-
const cwd = path.join(__dirname, 'fixtures/negative');
59-
const isIgnored = await isGitIgnored({cwd});
60-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
61-
const expected = ['foo.js'];
62-
t.deepEqual(actual, expected);
71+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/negative'))) {
72+
// eslint-disable-next-line no-await-in-loop
73+
const isIgnored = await isGitIgnored({cwd});
74+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
75+
const expected = ['foo.js'];
76+
t.deepEqual(actual, expected);
77+
}
6378
});
6479

6580
test('negative gitignore - sync', t => {
66-
const cwd = path.join(__dirname, 'fixtures/negative');
67-
const isIgnored = isGitIgnoredSync({cwd});
68-
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
69-
const expected = ['foo.js'];
70-
t.deepEqual(actual, expected);
81+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/negative'))) {
82+
const isIgnored = isGitIgnoredSync({cwd});
83+
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
84+
const expected = ['foo.js'];
85+
t.deepEqual(actual, expected);
86+
}
7187
});
7288

7389
test('multiple negation', async t => {
74-
const cwd = path.join(__dirname, 'fixtures/multiple-negation');
75-
const isIgnored = await isGitIgnored({cwd});
76-
77-
const actual = [
78-
'!!!unicorn.js',
79-
'!!unicorn.js',
80-
'!unicorn.js',
81-
'unicorn.js',
82-
].filter(file => !isIgnored(file));
83-
84-
const expected = ['!!unicorn.js', '!unicorn.js'];
85-
t.deepEqual(actual, expected);
90+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/multiple-negation'))) {
91+
// eslint-disable-next-line no-await-in-loop
92+
const isIgnored = await isGitIgnored({cwd});
93+
94+
const actual = [
95+
'!!!unicorn.js',
96+
'!!unicorn.js',
97+
'!unicorn.js',
98+
'unicorn.js',
99+
].filter(file => !isIgnored(file));
100+
101+
const expected = ['!!unicorn.js', '!unicorn.js'];
102+
t.deepEqual(actual, expected);
103+
}
86104
});
87105

88106
test('multiple negation - sync', t => {
89-
const cwd = path.join(__dirname, 'fixtures/multiple-negation');
90-
const isIgnored = isGitIgnoredSync({cwd});
91-
92-
const actual = [
93-
'!!!unicorn.js',
94-
'!!unicorn.js',
95-
'!unicorn.js',
96-
'unicorn.js',
97-
].filter(file => !isIgnored(file));
98-
99-
const expected = ['!!unicorn.js', '!unicorn.js'];
100-
t.deepEqual(actual, expected);
107+
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/multiple-negation'))) {
108+
const isIgnored = isGitIgnoredSync({cwd});
109+
110+
const actual = [
111+
'!!!unicorn.js',
112+
'!!unicorn.js',
113+
'!unicorn.js',
114+
'unicorn.js',
115+
].filter(file => !isIgnored(file));
116+
117+
const expected = ['!!unicorn.js', '!unicorn.js'];
118+
t.deepEqual(actual, expected);
119+
}
101120
});

‎index.d.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export type ExpandDirectoriesOption =
1212
| readonly string[]
1313
| {files?: readonly string[]; extensions?: readonly string[]};
1414

15-
export interface Options extends FastGlobOptions {
15+
type FastGlobOptionsWithoutCwd = Omit<FastGlobOptions, 'cwd'>;
16+
17+
export interface Options extends FastGlobOptionsWithoutCwd {
1618
/**
1719
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.
1820
@@ -43,10 +45,17 @@ export interface Options extends FastGlobOptions {
4345
@default false
4446
*/
4547
readonly gitignore?: boolean;
48+
49+
/**
50+
The current working directory in which to search.
51+
52+
@default process.cwd()
53+
*/
54+
readonly cwd?: URL | string;
4655
}
4756

4857
export interface GitignoreOptions {
49-
readonly cwd?: string;
58+
readonly cwd?: URL | string;
5059
readonly ignore?: readonly string[];
5160
}
5261

@@ -144,7 +153,14 @@ This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isd
144153
*/
145154
export function isDynamicPattern(
146155
patterns: string | readonly string[],
147-
options?: FastGlobOptions
156+
options?: FastGlobOptionsWithoutCwd & {
157+
/**
158+
The current working directory in which to search.
159+
160+
@default process.cwd()
161+
*/
162+
readonly cwd?: URL | string;
163+
}
148164
): boolean;
149165

150166
/**

‎index.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import arrayUnion from 'array-union';
33
import merge2 from 'merge2';
44
import fastGlob from 'fast-glob';
55
import dirGlob from 'dir-glob';
6+
import toPath from './to-path.js';
67
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';
78
import {FilterStream, UniqueStream} from './stream-utils.js';
89

@@ -16,7 +17,7 @@ const assertPatternsInput = patterns => {
1617
}
1718
};
1819

19-
const checkCwdOption = (options = {}) => {
20+
const checkCwdOption = options => {
2021
if (!options.cwd) {
2122
return;
2223
}
@@ -35,19 +36,21 @@ const checkCwdOption = (options = {}) => {
3536

3637
const getPathString = p => p.stats instanceof fs.Stats ? p.path : p;
3738

38-
export const generateGlobTasks = (patterns, taskOptions) => {
39+
export const generateGlobTasks = (patterns, taskOptions = {}) => {
3940
patterns = arrayUnion([patterns].flat());
4041
assertPatternsInput(patterns);
41-
checkCwdOption(taskOptions);
4242

4343
const globTasks = [];
4444

4545
taskOptions = {
4646
ignore: [],
4747
expandDirectories: true,
4848
...taskOptions,
49+
cwd: toPath(taskOptions.cwd),
4950
};
5051

52+
checkCwdOption(taskOptions);
53+
5154
for (const [index, pattern] of patterns.entries()) {
5255
if (isNegative(pattern)) {
5356
continue;
@@ -179,8 +182,14 @@ export const globbyStream = (patterns, options) => {
179182
.pipe(uniqueStream);
180183
};
181184

182-
export const isDynamicPattern = (patterns, options) => [patterns].flat()
183-
.some(pattern => fastGlob.isDynamicPattern(pattern, options));
185+
export const isDynamicPattern = (patterns, options = {}) => {
186+
options = {
187+
...options,
188+
cwd: toPath(options.cwd),
189+
};
190+
191+
return [patterns].flat().some(pattern => fastGlob.isDynamicPattern(pattern, options));
192+
};
184193

185194
export {
186195
isGitIgnored,

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"index.js",
2424
"index.d.ts",
2525
"gitignore.js",
26-
"stream-utils.js"
26+
"stream-utils.js",
27+
"to-path.js"
2728
],
2829
"keywords": [
2930
"all",

‎readme.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Based on [`fast-glob`](https://github.com/mrmlnc/fast-glob) but adds a bunch of
1111
- Negated patterns: `['foo*', '!foobar']`
1212
- Expands directories: `foo``foo/**/*`
1313
- Supports `.gitignore`
14+
- Supports `URL` as `cwd`
1415

1516
## Install
1617

@@ -125,7 +126,7 @@ This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isd
125126

126127
Returns a `Promise<(path: string) => boolean>` indicating whether a given path is ignored via a `.gitignore` file.
127128

128-
Takes `cwd?: string` and `ignore?: string[]` as options. `.gitignore` files matched by the ignore config are not used for the resulting filter function.
129+
Takes `cwd?: URL | string` and `ignore?: string[]` as options. `.gitignore` files matched by the ignore config are not used for the resulting filter function.
129130

130131
```js
131132
import {isGitIgnored} from 'globby';

‎test.js

+82-52
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import process from 'node:process';
22
import fs from 'node:fs';
33
import path from 'node:path';
44
import util from 'node:util';
5-
import {fileURLToPath} from 'node:url';
5+
import {fileURLToPath, pathToFileURL} from 'node:url';
66
import test from 'ava';
77
import getStream from 'get-stream';
88
import {
@@ -25,6 +25,8 @@ const fixture = [
2525
'e.tmp',
2626
];
2727

28+
const getCwdValues = cwd => [cwd, pathToFileURL(cwd), pathToFileURL(cwd).href];
29+
2830
test.before(() => {
2931
if (!fs.existsSync(temporary)) {
3032
fs.mkdirSync(temporary);
@@ -116,8 +118,11 @@ test('return [] for all negative patterns - stream', async t => {
116118

117119
test('cwd option', t => {
118120
process.chdir(temporary);
119-
t.deepEqual(globbySync('*.tmp', {cwd}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']);
120-
t.deepEqual(globbySync(['a.tmp', '*.tmp', '!{c,d,e}.tmp'], {cwd}), ['a.tmp', 'b.tmp']);
121+
for (const cwdDirectory of getCwdValues(cwd)) {
122+
t.deepEqual(globbySync('*.tmp', {cwd: cwdDirectory}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']);
123+
t.deepEqual(globbySync(['a.tmp', '*.tmp', '!{c,d,e}.tmp'], {cwd: cwdDirectory}), ['a.tmp', 'b.tmp']);
124+
}
125+
121126
process.chdir(cwd);
122127
});
123128

@@ -148,11 +153,18 @@ test('expose isDynamicPattern', t => {
148153
t.true(isDynamicPattern('**'));
149154
t.true(isDynamicPattern(['**', 'path1', 'path2']));
150155
t.false(isDynamicPattern(['path1', 'path2']));
156+
157+
for (const cwdDirectory of getCwdValues(cwd)) {
158+
t.true(isDynamicPattern('**', {cwd: cwdDirectory}));
159+
}
151160
});
152161

153162
test('expandDirectories option', t => {
154163
t.deepEqual(globbySync(temporary), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']);
155-
t.deepEqual(globbySync('**', {cwd: temporary}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']);
164+
for (const temporaryDirectory of getCwdValues(temporary)) {
165+
t.deepEqual(globbySync('**', {cwd: temporaryDirectory}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']);
166+
}
167+
156168
t.deepEqual(globbySync(temporary, {expandDirectories: ['a*', 'b*']}), ['tmp/a.tmp', 'tmp/b.tmp']);
157169
t.deepEqual(globbySync(temporary, {
158170
expandDirectories: {
@@ -193,10 +205,13 @@ test('expandDirectories and ignores option', t => {
193205

194206
test.failing('relative paths and ignores option', t => {
195207
process.chdir(temporary);
196-
t.deepEqual(globbySync('../tmp', {
197-
cwd: process.cwd(),
198-
ignore: ['tmp'],
199-
}), []);
208+
for (const cwd of getCwdValues(process.cwd())) {
209+
t.deepEqual(globbySync('../tmp', {
210+
cwd,
211+
ignore: ['tmp'],
212+
}), []);
213+
}
214+
200215
process.chdir(cwd);
201216
});
202217

@@ -327,71 +342,86 @@ test('gitingore option and objectMode option - sync', t => {
327342
});
328343

329344
test('`{extension: false}` and `expandDirectories.extensions` option', t => {
330-
t.deepEqual(
331-
globbySync('*', {
332-
cwd: temporary,
333-
extension: false,
334-
expandDirectories: {
335-
extensions: [
336-
'md',
337-
'tmp',
338-
],
339-
},
340-
}),
341-
[
342-
'a.tmp',
343-
'b.tmp',
344-
'c.tmp',
345-
'd.tmp',
346-
'e.tmp',
347-
],
348-
);
345+
for (const temporaryDirectory of getCwdValues(temporary)) {
346+
t.deepEqual(
347+
globbySync('*', {
348+
cwd: temporaryDirectory,
349+
extension: false,
350+
expandDirectories: {
351+
extensions: [
352+
'md',
353+
'tmp',
354+
],
355+
},
356+
}),
357+
[
358+
'a.tmp',
359+
'b.tmp',
360+
'c.tmp',
361+
'd.tmp',
362+
'e.tmp',
363+
],
364+
);
365+
}
349366
});
350367

351368
test('throws when specifying a file as cwd - async', async t => {
352369
const isFile = path.resolve('fixtures/gitignore/bar.js');
353370

354-
await t.throwsAsync(
355-
globby('.', {cwd: isFile}),
356-
{message: 'The `cwd` option must be a path to a directory'},
357-
);
358-
359-
await t.throwsAsync(
360-
globby('*', {cwd: isFile}),
361-
{message: 'The `cwd` option must be a path to a directory'},
362-
);
371+
for (const file of getCwdValues(isFile)) {
372+
// eslint-disable-next-line no-await-in-loop
373+
await t.throwsAsync(
374+
globby('.', {cwd: file}),
375+
{message: 'The `cwd` option must be a path to a directory'},
376+
);
377+
378+
// eslint-disable-next-line no-await-in-loop
379+
await t.throwsAsync(
380+
globby('*', {cwd: file}),
381+
{message: 'The `cwd` option must be a path to a directory'},
382+
);
383+
}
363384
});
364385

365386
test('throws when specifying a file as cwd - sync', t => {
366387
const isFile = path.resolve('fixtures/gitignore/bar.js');
367388

368-
t.throws(() => {
369-
globbySync('.', {cwd: isFile});
370-
}, {message: 'The `cwd` option must be a path to a directory'});
389+
for (const file of getCwdValues(isFile)) {
390+
t.throws(() => {
391+
globbySync('.', {cwd: file});
392+
}, {message: 'The `cwd` option must be a path to a directory'});
371393

372-
t.throws(() => {
373-
globbySync('*', {cwd: isFile});
374-
}, {message: 'The `cwd` option must be a path to a directory'});
394+
t.throws(() => {
395+
globbySync('*', {cwd: file});
396+
}, {message: 'The `cwd` option must be a path to a directory'});
397+
}
375398
});
376399

377400
test('throws when specifying a file as cwd - stream', t => {
378401
const isFile = path.resolve('fixtures/gitignore/bar.js');
379402

380-
t.throws(() => {
381-
globbyStream('.', {cwd: isFile});
382-
}, {message: 'The `cwd` option must be a path to a directory'});
403+
for (const file of getCwdValues(isFile)) {
404+
t.throws(() => {
405+
globbyStream('.', {cwd: file});
406+
}, {message: 'The `cwd` option must be a path to a directory'});
383407

384-
t.throws(() => {
385-
globbyStream('*', {cwd: isFile});
386-
}, {message: 'The `cwd` option must be a path to a directory'});
408+
t.throws(() => {
409+
globbyStream('*', {cwd: file});
410+
}, {message: 'The `cwd` option must be a path to a directory'});
411+
}
387412
});
388413

389414
test('don\'t throw when specifying a non-existing cwd directory - async', async t => {
390-
const actual = await globby('.', {cwd: '/unknown'});
391-
t.is(actual.length, 0);
415+
for (const cwd of getCwdValues('/unknown')) {
416+
// eslint-disable-next-line no-await-in-loop
417+
const actual = await globby('.', {cwd});
418+
t.is(actual.length, 0);
419+
}
392420
});
393421

394422
test('don\'t throw when specifying a non-existing cwd directory - sync', t => {
395-
const actual = globbySync('.', {cwd: '/unknown'});
396-
t.is(actual.length, 0);
423+
for (const cwd of getCwdValues('/unknown')) {
424+
const actual = globbySync('.', {cwd});
425+
t.is(actual.length, 0);
426+
}
397427
});

‎to-path.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {fileURLToPath} from 'node:url';
2+
3+
const toPath = urlOrPath => {
4+
if (!urlOrPath) {
5+
return urlOrPath;
6+
}
7+
8+
if (urlOrPath instanceof URL) {
9+
urlOrPath = urlOrPath.href;
10+
}
11+
12+
return urlOrPath.startsWith('file://') ? fileURLToPath(urlOrPath) : urlOrPath;
13+
};
14+
15+
export default toPath;

0 commit comments

Comments
 (0)
Please sign in to comment.