Skip to content

Commit

Permalink
Support URL as cwd
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Jan 12, 2022
1 parent 79765fb commit 072a774
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 121 deletions.
3 changes: 2 additions & 1 deletion gitignore.js
Expand Up @@ -5,6 +5,7 @@ import path from 'node:path';
import fastGlob from 'fast-glob';
import gitIgnore from 'ignore';
import slash from 'slash';
import {toPath} from 'url-or-path';

const DEFAULT_IGNORE = [
'**/node_modules/**',
Expand Down Expand Up @@ -85,7 +86,7 @@ const getFileSync = (file, cwd) => {
const normalizeOptions = ({
ignore = [],
cwd = slash(process.cwd()),
} = {}) => ({ignore, cwd});
} = {}) => ({ignore, cwd: toPath(cwd)});

export const isGitIgnored = async options => {
options = normalizeOptions(options);
Expand Down
141 changes: 80 additions & 61 deletions gitignore.test.js
@@ -1,101 +1,120 @@
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {fileURLToPath, pathToFileURL} from 'node:url';
import test from 'ava';
import slash from 'slash';
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';

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

test('gitignore', async t => {
const cwd = path.join(__dirname, 'fixtures/gitignore');
const isIgnored = await isGitIgnored({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['bar.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['bar.js'];
t.deepEqual(actual, expected);
}
});

test('gitignore - mixed path styles', async t => {
const cwd = path.join(__dirname, 'fixtures/gitignore');
const isIgnored = await isGitIgnored({cwd});
t.true(isIgnored(slash(path.resolve(cwd, 'foo.js'))));
const directory = path.join(__dirname, 'fixtures/gitignore');
for (const cwd of getCwdValues(directory)) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
t.true(isIgnored(slash(path.resolve(directory, 'foo.js'))));
}
});

test('gitignore - os paths', async t => {
const cwd = path.join(__dirname, 'fixtures/gitignore');
const isIgnored = await isGitIgnored({cwd});
t.true(isIgnored(path.resolve(cwd, 'foo.js')));
const directory = path.join(__dirname, 'fixtures/gitignore');
for (const cwd of getCwdValues(directory)) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
t.true(isIgnored(path.resolve(directory, 'foo.js')));
}
});

test('gitignore - sync', t => {
const cwd = path.join(__dirname, 'fixtures/gitignore');
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['bar.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['bar.js'];
t.deepEqual(actual, expected);
}
});

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

const isIgnored = await isGitIgnored({cwd, ignore});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js', 'bar.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd, ignore});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js', 'bar.js'];
t.deepEqual(actual, expected);
}
});

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

const isIgnored = isGitIgnoredSync({cwd, ignore});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js', 'bar.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/gitignore'))) {
const isIgnored = isGitIgnoredSync({cwd, ignore});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js', 'bar.js'];
t.deepEqual(actual, expected);
}
});

test('negative gitignore', async t => {
const cwd = path.join(__dirname, 'fixtures/negative');
const isIgnored = await isGitIgnored({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/negative'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js'];
t.deepEqual(actual, expected);
}
});

test('negative gitignore - sync', t => {
const cwd = path.join(__dirname, 'fixtures/negative');
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/negative'))) {
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
const expected = ['foo.js'];
t.deepEqual(actual, expected);
}
});

test('multiple negation', async t => {
const cwd = path.join(__dirname, 'fixtures/multiple-negation');
const isIgnored = await isGitIgnored({cwd});

const actual = [
'!!!unicorn.js',
'!!unicorn.js',
'!unicorn.js',
'unicorn.js',
].filter(file => !isIgnored(file));

const expected = ['!!unicorn.js', '!unicorn.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/multiple-negation'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});

const actual = [
'!!!unicorn.js',
'!!unicorn.js',
'!unicorn.js',
'unicorn.js',
].filter(file => !isIgnored(file));

const expected = ['!!unicorn.js', '!unicorn.js'];
t.deepEqual(actual, expected);
}
});

test('multiple negation - sync', t => {
const cwd = path.join(__dirname, 'fixtures/multiple-negation');
const isIgnored = isGitIgnoredSync({cwd});

const actual = [
'!!!unicorn.js',
'!!unicorn.js',
'!unicorn.js',
'unicorn.js',
].filter(file => !isIgnored(file));

const expected = ['!!unicorn.js', '!unicorn.js'];
t.deepEqual(actual, expected);
for (const cwd of getCwdValues(path.join(__dirname, 'fixtures/multiple-negation'))) {
const isIgnored = isGitIgnoredSync({cwd});

const actual = [
'!!!unicorn.js',
'!!unicorn.js',
'!unicorn.js',
'unicorn.js',
].filter(file => !isIgnored(file));

const expected = ['!!unicorn.js', '!unicorn.js'];
t.deepEqual(actual, expected);
}
});
22 changes: 19 additions & 3 deletions index.d.ts
Expand Up @@ -12,7 +12,9 @@ export type ExpandDirectoriesOption =
| readonly string[]
| {files?: readonly string[]; extensions?: readonly string[]};

export interface Options extends FastGlobOptions {
type FastGlobOptionsWithoutCwd = Omit<FastGlobOptions, 'cwd'>;

export interface Options extends FastGlobOptionsWithoutCwd {
/**
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.
Expand Down Expand Up @@ -43,10 +45,17 @@ export interface Options extends FastGlobOptions {
@default false
*/
readonly gitignore?: boolean;

/**
The current working directory in which to search.
@default process.cwd()
*/
readonly cwd?: URL | string;
}

export interface GitignoreOptions {
readonly cwd?: string;
readonly cwd?: URL | string;
readonly ignore?: readonly string[];
}

Expand Down Expand Up @@ -144,7 +153,14 @@ This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isd
*/
export function isDynamicPattern(
patterns: string | readonly string[],
options?: FastGlobOptions
options?: FastGlobOptionsWithoutCwd & {
/**
The current working directory in which to search.
@default process.cwd()
*/
readonly cwd?: URL | string;
}
): boolean;

/**
Expand Down
25 changes: 23 additions & 2 deletions index.js
Expand Up @@ -3,6 +3,7 @@ import arrayUnion from 'array-union';
import merge2 from 'merge2';
import fastGlob from 'fast-glob';
import dirGlob from 'dir-glob';
import {toPath} from 'url-or-path';
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';
import {FilterStream, UniqueStream} from './stream-utils.js';

Expand Down Expand Up @@ -61,6 +62,7 @@ export const generateGlobTasks = (patterns, taskOptions) => {
const options = {
...taskOptions,
ignore: [...taskOptions.ignore, ...ignore],
cwd: taskOptions.cwd ? toPath(taskOptions.cwd) : taskOptions.cwd,
};

globTasks.push({pattern, options});
Expand Down Expand Up @@ -98,6 +100,11 @@ const getFilterSync = options => options && options.gitignore

const globToTask = task => async glob => {
const {options} = task;

if (options.cwd) {
options.cwd = toPath(options.cwd);
}

if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) {
options.ignore = await dirGlob(options.ignore);
}
Expand All @@ -110,6 +117,11 @@ const globToTask = task => async glob => {

const globToTaskSync = task => glob => {
const {options} = task;

if (options.cwd) {
options.cwd = toPath(options.cwd);
}

if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) {
options.ignore = dirGlob.sync(options.ignore);
}
Expand Down Expand Up @@ -179,8 +191,17 @@ export const globbyStream = (patterns, options) => {
.pipe(uniqueStream);
};

export const isDynamicPattern = (patterns, options) => [patterns].flat()
.some(pattern => fastGlob.isDynamicPattern(pattern, options));
export const isDynamicPattern = (patterns, options = {}) => {
if (options.cwd) {
options = {
...options,
cwd: toPath(options.cwd),
};
}

return [patterns].flat()
.some(pattern => fastGlob.isDynamicPattern(pattern, options));
};

export {
isGitIgnored,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -64,7 +64,8 @@
"fast-glob": "^3.2.7",
"ignore": "^5.1.9",
"merge2": "^1.4.1",
"slash": "^4.0.0"
"slash": "^4.0.0",
"url-or-path": "^1.0.2"
},
"devDependencies": {
"@types/node": "^16.11.11",
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Expand Up @@ -11,6 +11,7 @@ Based on [`fast-glob`](https://github.com/mrmlnc/fast-glob) but adds a bunch of
- Negated patterns: `['foo*', '!foobar']`
- Expands directories: `foo``foo/**/*`
- Supports `.gitignore`
- Supports `URL` as `cwd`

## Install

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

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

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

```js
import {isGitIgnored} from 'globby';
Expand Down

0 comments on commit 072a774

Please sign in to comment.