Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support URL as cwd #201

Merged
merged 9 commits into from Jan 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion gitignore.js
Expand Up @@ -4,6 +4,7 @@ import path from 'node:path';
import fastGlob from 'fast-glob';
import gitIgnore from 'ignore';
import slash from 'slash';
import toPath from './to-path.js';

const DEFAULT_IGNORE = [
'**/node_modules/**',
Expand Down Expand Up @@ -82,7 +83,7 @@ const getFileSync = (file, cwd) => {
const normalizeOptions = ({
ignore = [],
cwd = slash(process.cwd()),
} = {}) => ({ignore: [...DEFAULT_IGNORE, ...ignore], cwd});
} = {}) => ({ignore: [...DEFAULT_IGNORE, ...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), pathToFileURL(cwd).href];

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @sindresorhus

URL interface belongs to lib.dom.d.ts, so now we force TS users to inject compilerOptions.lib: ['dom'] to resolve URL as global. It breaks TS builds and formally this is a breaking change.

[build:es6   ] ../../node_modules/globby/index.d.ts(54,17): error TS2304: Cannot find name 'URL'.
[build:es6   ] ../../node_modules/globby/index.d.ts(58,17): error TS2304: Cannot find name 'URL'.
[build:es6   ] ../../node_modules/globby/index.d.ts(162,18): error TS2304: Cannot find name 'URL'.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise it should be explicitly imported in index.d.ts. It does not belong to global by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sindresorhus
I really don't want to fix hundreds of our globby@^12-dependant projects all night )). Could you plz roll the patch #206?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for breaking your projects, but I know nothing about the typing, I can't help.

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
19 changes: 14 additions & 5 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 './to-path.js';
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';
import {FilterStream, UniqueStream} from './stream-utils.js';

Expand All @@ -16,7 +17,7 @@ const assertPatternsInput = patterns => {
}
};

const checkCwdOption = (options = {}) => {
const checkCwdOption = options => {
if (!options.cwd) {
return;
}
Expand All @@ -35,19 +36,21 @@ const checkCwdOption = (options = {}) => {

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

export const generateGlobTasks = (patterns, taskOptions) => {
export const generateGlobTasks = (patterns, taskOptions = {}) => {
patterns = arrayUnion([patterns].flat());
assertPatternsInput(patterns);
checkCwdOption(taskOptions);

const globTasks = [];

taskOptions = {
ignore: [],
expandDirectories: true,
...taskOptions,
cwd: toPath(taskOptions.cwd),
};

checkCwdOption(taskOptions);

for (const [index, pattern] of patterns.entries()) {
if (isNegative(pattern)) {
continue;
Expand Down Expand Up @@ -179,8 +182,14 @@ 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 = {}) => {
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 @@ -23,7 +23,8 @@
"index.js",
"index.d.ts",
"gitignore.js",
"stream-utils.js"
"stream-utils.js",
"to-path.js"
],
"keywords": [
"all",
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