diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3488385..495ba52 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,16 +10,16 @@ jobs: fail-fast: false matrix: node-version: + - 16 - 14 - 12 - - 10 os: - ubuntu-latest - macos-latest - windows-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/bench.js b/bench.js index 339a5c9..b404bf1 100644 --- a/bench.js +++ b/bench.js @@ -1,12 +1,14 @@ -'use strict'; /* global after, before, bench, suite */ -const fs = require('fs'); -const rimraf = require('rimraf'); -const globbyMainBranch = require('globby'); -const gs = require('glob-stream'); -const fastGlob = require('fast-glob'); -const globby = require('.'); +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import rimraf from 'rimraf'; +import globbyMainBranch from 'globby'; +import gs from 'glob-stream'; +import fastGlob from 'fast-glob'; +import {globby, globbySync} from './index.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const BENCH_DIR = 'bench'; const runners = [{ @@ -14,58 +16,58 @@ const runners = [{ run: async (patterns, callback) => { await globby(patterns); callback(); - } + }, }, { name: 'globby async (upstream/main)', run: async (patterns, callback) => { await globbyMainBranch(patterns); callback(); - } + }, }, { name: 'globby sync (working directory)', run: patterns => { - globby.sync(patterns); - } + globbySync(patterns); + }, }, { name: 'globby sync (upstream/main)', run: patterns => { globbyMainBranch.sync(patterns); - } + }, }, { name: 'glob-stream', run: (patterns, cb) => { gs(patterns).on('data', () => {}).on('end', cb); - } + }, }, { name: 'fast-glob async', run: async (patterns, callback) => { await fastGlob(patterns); callback(); - } + }, }, { name: 'fast-glob sync', run: patterns => { fastGlob.sync(patterns); - } + }, }]; const benchs = [{ name: 'negative globs (some files inside dir)', patterns: [ 'a/*', - '!a/c*' - ] + '!a/c*', + ], }, { name: 'negative globs (whole dir)', patterns: [ 'a/*', - '!a/**' - ] + '!a/**', + ], }, { name: 'multiple positive globs', patterns: [ 'a/*', - 'b/*' - ] + 'b/*', + ], }]; before(() => { @@ -73,14 +75,15 @@ before(() => { rimraf.sync(BENCH_DIR); fs.mkdirSync(BENCH_DIR); process.chdir(BENCH_DIR); - ['a', 'b'] - .map(directory => `${directory}/`) - .forEach(directory => { - fs.mkdirSync(directory); - for (let i = 0; i < 500; i++) { - fs.writeFileSync(directory + (i < 100 ? 'c' : 'd') + i, ''); - } - }); + const directories = ['a', 'b'] + .map(directory => `${directory}/`); + + for (const directory of directories) { + fs.mkdirSync(directory); + for (let i = 0; i < 500; i++) { + fs.writeFileSync(directory + (i < 100 ? 'c' : 'd') + i, ''); + } + } }); after(() => { @@ -88,8 +91,10 @@ after(() => { rimraf.sync(BENCH_DIR); }); -benchs.forEach(benchmark => { +for (const benchmark of benchs) { suite(benchmark.name, () => { - runners.forEach(runner => bench(runner.name, runner.run.bind(null, benchmark.patterns))); + for (const runner of runners) { + bench(runner.name, runner.run.bind(null, benchmark.patterns)); + } }); -}); +} diff --git a/gitignore.js b/gitignore.js index 2f77baa..03fbefa 100644 --- a/gitignore.js +++ b/gitignore.js @@ -1,16 +1,15 @@ -'use strict'; -const {promisify} = require('util'); -const fs = require('fs'); -const path = require('path'); -const fastGlob = require('fast-glob'); -const gitIgnore = require('ignore'); -const slash = require('slash'); +import {promisify} from 'node:util'; +import fs from 'node:fs'; +import path from 'node:path'; +import fastGlob from 'fast-glob'; +import gitIgnore from 'ignore'; +import slash from 'slash'; const DEFAULT_IGNORE = [ '**/node_modules/**', '**/flow-typed/**', '**/coverage/**', - '**/.git' + '**/.git', ]; const readFileP = promisify(fs.readFile); @@ -38,7 +37,7 @@ const reduceIgnore = files => { for (const file of files) { ignores.add(parseGitIgnore(file.content, { cwd: file.cwd, - fileName: file.filePath + fileName: file.filePath, })); } @@ -58,9 +57,7 @@ const ensureAbsolutePathForCwd = (cwd, p) => { return path.join(cwd, p); }; -const getIsIgnoredPredecate = (ignores, cwd) => { - return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p.path || p)))); -}; +const getIsIgnoredPredicate = (ignores, cwd) => p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p.path || p)))); const getFile = async (file, cwd) => { const filePath = path.join(cwd, file); @@ -69,7 +66,7 @@ const getFile = async (file, cwd) => { return { cwd, filePath, - content + content, }; }; @@ -80,41 +77,40 @@ const getFileSync = (file, cwd) => { return { cwd, filePath, - content + content, }; }; const normalizeOptions = ({ ignore = [], - cwd = slash(process.cwd()) -} = {}) => { - return {ignore, cwd}; -}; + cwd = slash(process.cwd()), +} = {}) => ({ignore, cwd}); -module.exports = async options => { +export const isGitIgnored = async options => { options = normalizeOptions(options); const paths = await fastGlob('**/.gitignore', { ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd + cwd: options.cwd, }); const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); const ignores = reduceIgnore(files); - return getIsIgnoredPredecate(ignores, options.cwd); + return getIsIgnoredPredicate(ignores, options.cwd); }; -module.exports.sync = options => { +export const isGitIgnoredSync = options => { options = normalizeOptions(options); const paths = fastGlob.sync('**/.gitignore', { ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd + cwd: options.cwd, }); const files = paths.map(file => getFileSync(file, options.cwd)); const ignores = reduceIgnore(files); - return getIsIgnoredPredecate(ignores, options.cwd); + return getIsIgnoredPredicate(ignores, options.cwd); }; + diff --git a/gitignore.test.js b/gitignore.test.js index fd25b31..ea7718a 100644 --- a/gitignore.test.js +++ b/gitignore.test.js @@ -1,11 +1,14 @@ -const path = require('path'); -const test = require('ava'); -const slash = require('slash'); -const gitignore = require('./gitignore'); +import path from 'node:path'; +import {fileURLToPath} 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)); test('gitignore', async t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); - const isIgnored = await gitignore({cwd}); + const isIgnored = await isGitIgnored({cwd}); const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file)); const expected = ['bar.js']; t.deepEqual(actual, expected); @@ -13,19 +16,19 @@ test('gitignore', async t => { test('gitignore - mixed path styles', async t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); - const isIgnored = await gitignore({cwd}); + const isIgnored = await isGitIgnored({cwd}); t.true(isIgnored(slash(path.resolve(cwd, 'foo.js')))); }); test('gitignore - os paths', async t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); - const isIgnored = await gitignore({cwd}); + const isIgnored = await isGitIgnored({cwd}); t.true(isIgnored(path.resolve(cwd, 'foo.js'))); }); test('gitignore - sync', t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); - const isIgnored = gitignore.sync({cwd}); + const isIgnored = isGitIgnoredSync({cwd}); const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file)); const expected = ['bar.js']; t.deepEqual(actual, expected); @@ -35,7 +38,7 @@ test('ignore ignored .gitignore', async t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); const ignore = ['**/.gitignore']; - const isIgnored = await gitignore({cwd, ignore}); + 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); @@ -45,7 +48,7 @@ test('ignore ignored .gitignore - sync', t => { const cwd = path.join(__dirname, 'fixtures/gitignore'); const ignore = ['**/.gitignore']; - const isIgnored = gitignore.sync({cwd, ignore}); + 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); @@ -53,7 +56,7 @@ test('ignore ignored .gitignore - sync', t => { test('negative gitignore', async t => { const cwd = path.join(__dirname, 'fixtures/negative'); - const isIgnored = await gitignore({cwd}); + const isIgnored = await isGitIgnored({cwd}); const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file)); const expected = ['foo.js']; t.deepEqual(actual, expected); @@ -61,7 +64,7 @@ test('negative gitignore', async t => { test('negative gitignore - sync', t => { const cwd = path.join(__dirname, 'fixtures/negative'); - const isIgnored = gitignore.sync({cwd}); + const isIgnored = isGitIgnoredSync({cwd}); const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file)); const expected = ['foo.js']; t.deepEqual(actual, expected); @@ -69,13 +72,13 @@ test('negative gitignore - sync', t => { test('multiple negation', async t => { const cwd = path.join(__dirname, 'fixtures/multiple-negation'); - const isIgnored = await gitignore({cwd}); + const isIgnored = await isGitIgnored({cwd}); const actual = [ '!!!unicorn.js', '!!unicorn.js', '!unicorn.js', - 'unicorn.js' + 'unicorn.js', ].filter(file => !isIgnored(file)); const expected = ['!!unicorn.js', '!unicorn.js']; @@ -84,13 +87,13 @@ test('multiple negation', async t => { test('multiple negation - sync', t => { const cwd = path.join(__dirname, 'fixtures/multiple-negation'); - const isIgnored = gitignore.sync({cwd}); + const isIgnored = isGitIgnoredSync({cwd}); const actual = [ '!!!unicorn.js', '!!unicorn.js', '!unicorn.js', - 'unicorn.js' + 'unicorn.js', ].filter(file => !isIgnored(file)); const expected = ['!!unicorn.js', '!unicorn.js']; diff --git a/index.d.ts b/index.d.ts index 2e563fc..2811bbe 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,15 +1,14 @@ import {Options as FastGlobOptions, Entry as FastGlobEntry} from 'fast-glob'; -declare namespace globby { - type ExpandDirectoriesOption = - | boolean - | readonly string[] - | {files?: readonly string[]; extensions?: readonly string[]}; +type ExpandDirectoriesOption = + | boolean + | readonly string[] + | {files?: readonly string[]; extensions?: readonly string[]}; - type Entry = FastGlobEntry; +type GlobbyEntry = FastGlobEntry; - interface GlobbyOptions extends FastGlobOptions { - /** +interface GlobbyOptions extends FastGlobOptions { + /** 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. Note that if you set this option to `false`, you won't get back matched directories unless you set `onlyFiles: false`. @@ -18,7 +17,7 @@ declare namespace globby { @example ``` - import globby = require('globby'); + import {globby} from 'globby'; (async () => { const paths = await globby('images', { @@ -32,56 +31,52 @@ declare namespace globby { //=> ['cat.png', 'unicorn.png', 'cow.jpg', 'rainbow.jpg'] })(); ``` - */ - readonly expandDirectories?: ExpandDirectoriesOption; + */ + readonly expandDirectories?: ExpandDirectoriesOption; - /** + /** Respect ignore patterns in `.gitignore` files that apply to the globbed files. @default false - */ - readonly gitignore?: boolean; - } - - interface GlobTask { - readonly pattern: string; - readonly options: GlobbyOptions; - } + */ + readonly gitignore?: boolean; +} - interface GitignoreOptions { - readonly cwd?: string; - readonly ignore?: readonly string[]; - } +interface GlobTask { + readonly pattern: string; + readonly options: GlobbyOptions; +} - type FilterFunction = (path: string) => boolean; +interface GitignoreOptions { + readonly cwd?: string; + readonly ignore?: readonly string[]; } -interface Gitignore { - /** - @returns A filter function indicating whether a given path is ignored via a `.gitignore` file. - */ - sync: (options?: globby.GitignoreOptions) => globby.FilterFunction; +type GlobbyFilterFunction = (path: string) => boolean; - /** +/** `.gitignore` files matched by the ignore config are not used for the resulting filter function. @returns A filter function indicating whether a given path is ignored via a `.gitignore` file. @example ``` - import {gitignore} from 'globby'; + import {isGitIgnored} from 'globby'; (async () => { - const isIgnored = await gitignore(); + const isIgnored = await isGitIgnored(); console.log(isIgnored('some/file')); })(); ``` - */ - (options?: globby.GitignoreOptions): Promise; -} + */ +declare const isGitIgnored: (options?: GitignoreOptions) => Promise; -declare const globby: { - /** +/** + @returns A filter function indicating whether a given path is ignored via a `.gitignore` file. + */ +declare const isGitIgnoredSync: (options?: GitignoreOptions) => GlobbyFilterFunction; + +/** Find files and directories using glob patterns. Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`. @@ -89,16 +84,16 @@ declare const globby: { @param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns). @param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package. @returns The matching paths. - */ - sync: (( - patterns: string | readonly string[], - options: globby.GlobbyOptions & {objectMode: true} - ) => globby.Entry[]) & (( - patterns: string | readonly string[], - options?: globby.GlobbyOptions - ) => string[]); - - /** + */ +declare const globbySync: (( + patterns: string | readonly string[], + options: GlobbyOptions & {objectMode: true} +) => GlobbyEntry[]) & (( + patterns: string | readonly string[], + options?: GlobbyOptions +) => string[]); + +/** Find files and directories using glob patterns. Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`. @@ -109,33 +104,33 @@ declare const globby: { @example ``` - import globby = require('globby'); + import {globbyStream} from 'globby'; (async () => { - for await (const path of globby.stream('*.tmp')) { + for await (const path of globbyStream('*.tmp')) { console.log(path); } })(); ``` - */ - stream: ( - patterns: string | readonly string[], - options?: globby.GlobbyOptions - ) => NodeJS.ReadableStream; + */ +declare const globbyStream: ( + patterns: string | readonly string[], + options?: GlobbyOptions +) => NodeJS.ReadableStream; - /** +/** Note that you should avoid running the same tasks multiple times as they contain a file system cache. Instead, run this method each time to ensure file system changes are taken into consideration. @param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns). @param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package. @returns An object in the format `{pattern: string, options: object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages. - */ - generateGlobTasks: ( - patterns: string | readonly string[], - options?: globby.GlobbyOptions - ) => globby.GlobTask[]; + */ +declare const generateGlobTasks: ( + patterns: string | readonly string[], + options?: GlobbyOptions +) => GlobTask[]; - /** +/** Note that the options affect the results. This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isdynamicpatternpattern-options). @@ -143,18 +138,17 @@ declare const globby: { @param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns). @param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3). @returns Whether there are any special glob characters in the `patterns`. - */ - hasMagic: ( - patterns: string | readonly string[], - options?: FastGlobOptions - ) => boolean; - - readonly gitignore: Gitignore; + */ +declare const isDynamicPattern: ( + patterns: string | readonly string[], + options?: FastGlobOptions +) => boolean; +declare const globby: { ( patterns: string | readonly string[], - options: globby.GlobbyOptions & {objectMode: true} - ): Promise; + options: GlobbyOptions & {objectMode: true} + ): Promise; /** Find files and directories using glob patterns. @@ -167,7 +161,7 @@ declare const globby: { @example ``` - import globby = require('globby'); + import {globby} from 'globby'; (async () => { const paths = await globby(['*', '!cake']); @@ -179,8 +173,6 @@ declare const globby: { */ ( patterns: string | readonly string[], - options?: globby.GlobbyOptions + options?: GlobbyOptions ): Promise; }; - -export = globby; diff --git a/index.js b/index.js index b2d503b..0c9269f 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,10 @@ -'use strict'; -const fs = require('fs'); -const arrayUnion = require('array-union'); -const merge2 = require('merge2'); -const fastGlob = require('fast-glob'); -const dirGlob = require('dir-glob'); -const gitignore = require('./gitignore'); -const {FilterStream, UniqueStream} = require('./stream-utils'); +import fs from 'node:fs'; +import arrayUnion from 'array-union'; +import merge2 from 'merge2'; +import fastGlob from 'fast-glob'; +import dirGlob from 'dir-glob'; +import {isGitIgnored, isGitIgnoredSync} from './gitignore.js'; +import {FilterStream, UniqueStream} from './stream-utils.js'; const DEFAULT_FILTER = () => false; @@ -36,8 +35,8 @@ const checkCwdOption = (options = {}) => { const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; -const generateGlobTasks = (patterns, taskOptions) => { - patterns = arrayUnion([].concat(patterns)); +export const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([patterns].flat()); assertPatternsInput(patterns); checkCwdOption(taskOptions); @@ -46,7 +45,7 @@ const generateGlobTasks = (patterns, taskOptions) => { taskOptions = { ignore: [], expandDirectories: true, - ...taskOptions + ...taskOptions, }; for (const [index, pattern] of patterns.entries()) { @@ -61,7 +60,7 @@ const generateGlobTasks = (patterns, taskOptions) => { const options = { ...taskOptions, - ignore: taskOptions.ignore.concat(ignore) + ignore: [...taskOptions.ignore, ...ignore], }; globTasks.push({pattern, options}); @@ -79,12 +78,12 @@ const globDirs = (task, fn) => { if (Array.isArray(task.options.expandDirectories)) { options = { ...options, - files: task.options.expandDirectories + files: task.options.expandDirectories, }; } else if (typeof task.options.expandDirectories === 'object') { options = { ...options, - ...task.options.expandDirectories + ...task.options.expandDirectories, }; } @@ -93,11 +92,9 @@ const globDirs = (task, fn) => { const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; -const getFilterSync = options => { - return options && options.gitignore ? - gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; -}; +const getFilterSync = options => options && options.gitignore + ? isGitIgnoredSync({cwd: options.cwd, ignore: options.ignore}) + : DEFAULT_FILTER; const globToTask = task => glob => { const {options} = task; @@ -107,18 +104,16 @@ const globToTask = task => glob => { return { pattern: glob, - options + options, }; }; -module.exports = async (patterns, options) => { +export const globby = async (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); - const getFilter = async () => { - return options && options.gitignore ? - gitignore({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; - }; + const getFilter = async () => options && options.gitignore + ? isGitIgnored({cwd: options.cwd, ignore: options.ignore}) + : DEFAULT_FILTER; const getTasks = async () => { const tasks = await Promise.all(globTasks.map(async task => { @@ -135,7 +130,7 @@ module.exports = async (patterns, options) => { return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); }; -module.exports.sync = (patterns, options) => { +export const globbySync = (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); const tasks = []; @@ -154,7 +149,7 @@ module.exports.sync = (patterns, options) => { return matches.filter(path_ => !filter(path_)); }; -module.exports.stream = (patterns, options) => { +export const globbyStream = (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); const tasks = []; @@ -172,10 +167,10 @@ module.exports.stream = (patterns, options) => { .pipe(uniqueStream); }; -module.exports.generateGlobTasks = generateGlobTasks; - -module.exports.hasMagic = (patterns, options) => [] - .concat(patterns) +export const isDynamicPattern = (patterns, options) => [patterns].flat() .some(pattern => fastGlob.isDynamicPattern(pattern, options)); -module.exports.gitignore = gitignore; +export { + isGitIgnored, + isGitIgnoredSync, +}; diff --git a/index.test-d.ts b/index.test-d.ts index 3681c25..9a86b23 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,14 +1,20 @@ +import {dirname} from 'node:path'; +import {fileURLToPath} from 'node:url'; import {expectType} from 'tsd'; -import globby = require('.'); import { GlobTask, - FilterFunction, - sync as globbySync, - stream as globbyStream, + GlobbyEntry, + GlobbyFilterFunction, + globby, + globbySync, + globbyStream, generateGlobTasks, - hasMagic, - gitignore -} from '.'; + isDynamicPattern, + isGitIgnored, + isGitIgnoredSync, +} from './index.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); // Globby expectType>(globby('*.tmp')); @@ -16,19 +22,19 @@ expectType>(globby(['a.tmp', '*.tmp', '!{c,d,e}.tmp'])); expectType>(globby('*.tmp', {expandDirectories: false})); expectType>( - globby('*.tmp', {expandDirectories: ['a*', 'b*']}) + globby('*.tmp', {expandDirectories: ['a*', 'b*']}), ); expectType>( globby('*.tmp', { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] - } - }) + extensions: ['tmp'], + }, + }), ); expectType>(globby('*.tmp', {gitignore: true})); expectType>(globby('*.tmp', {ignore: ['**/b.tmp']})); -expectType>(globby('*.tmp', {objectMode: true})); +expectType>(globby('*.tmp', {objectMode: true})); // Globby (sync) expectType(globbySync('*.tmp')); @@ -40,13 +46,13 @@ expectType( globbySync('*.tmp', { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] - } - }) + extensions: ['tmp'], + }, + }), ); expectType(globbySync('*.tmp', {gitignore: true})); expectType(globbySync('*.tmp', {ignore: ['**/b.tmp']})); -expectType(globbySync('*.tmp', {objectMode: true})); +expectType(globbySync('*.tmp', {objectMode: true})); // Globby (stream) expectType(globbyStream('*.tmp')); @@ -58,9 +64,9 @@ expectType( globbyStream('*.tmp', { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] - } - }) + extensions: ['tmp'], + }, + }), ); expectType(globbyStream('*.tmp', {gitignore: true})); expectType(globbyStream('*.tmp', {ignore: ['**/b.tmp']})); @@ -82,46 +88,46 @@ expectType(generateGlobTasks(['a.tmp', '*.tmp', '!{c,d,e}.tmp'])); expectType(generateGlobTasks('*.tmp', {expandDirectories: false})); expectType( - generateGlobTasks('*.tmp', {expandDirectories: ['a*', 'b*']}) + generateGlobTasks('*.tmp', {expandDirectories: ['a*', 'b*']}), ); expectType( generateGlobTasks('*.tmp', { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] - } - }) + extensions: ['tmp'], + }, + }), ); expectType(generateGlobTasks('*.tmp', {gitignore: true})); expectType(generateGlobTasks('*.tmp', {ignore: ['**/b.tmp']})); -// HasMagic -expectType(hasMagic('**')); -expectType(hasMagic(['**', 'path1', 'path2'])); -expectType(hasMagic(['**', 'path1', 'path2'], {extglob: false})); +// IsDynamicPattern +expectType(isDynamicPattern('**')); +expectType(isDynamicPattern(['**', 'path1', 'path2'])); +expectType(isDynamicPattern(['**', 'path1', 'path2'], {extglob: false})); -// Gitignore -expectType>(gitignore()); -expectType>( - gitignore({ - cwd: __dirname - }) +// IsGitIgnored +expectType>(isGitIgnored()); +expectType>( + isGitIgnored({ + cwd: __dirname, + }), ); -expectType>( - gitignore({ - ignore: ['**/b.tmp'] - }) +expectType>( + isGitIgnored({ + ignore: ['**/b.tmp'], + }), ); -// Gitignore (sync) -expectType(gitignore.sync()); -expectType( - gitignore.sync({ - cwd: __dirname - }) +// IsGitIgnoredSync +expectType(isGitIgnoredSync()); +expectType( + isGitIgnoredSync({ + cwd: __dirname, + }), ); -expectType( - gitignore.sync({ - ignore: ['**/b.tmp'] - }) +expectType( + isGitIgnoredSync({ + ignore: ['**/b.tmp'], + }), ); diff --git a/package.json b/package.json index 4970eb0..08d691d 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "url": "https://sindresorhus.com" }, "engines": { - "node": ">=10" + "node": ">=12.20" }, + "type": "module", + "exports": "./index.js", "scripts": { "bench": "npm update glob-stream fast-glob && matcha bench.js", "test": "xo && ava && tsd" @@ -57,22 +59,29 @@ "git" ], "dependencies": { - "array-union": "^2.1.0", + "array-union": "^3.0.1", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "fast-glob": "^3.2.7", + "ignore": "^5.1.8", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "devDependencies": { - "ava": "^3.13.0", - "get-stream": "^6.0.0", + "@types/node": "^16.3.3", + "ava": "^3.15.0", + "get-stream": "^6.0.1", "glob-stream": "^6.1.0", "globby": "sindresorhus/globby#main", "matcha": "^0.7.0", "rimraf": "^3.0.2", - "tsd": "^0.13.1", - "xo": "^0.33.1" + "typescript": "^4.3.5", + "tsd": "^0.17.0", + "xo": "^0.42.0" + }, + "tsd": { + "compilerOptions": { + "module": "es2020" + } }, "xo": { "ignores": [ diff --git a/readme.md b/readme.md index b39ae43..e7a508b 100644 --- a/readme.md +++ b/readme.md @@ -27,7 +27,7 @@ $ npm install globby ``` ```js -const globby = require('globby'); +import {globby} from 'globby'; (async () => { const paths = await globby(['*', '!cake']); @@ -65,7 +65,7 @@ Default: `true` 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 below: ```js -const globby = require('globby'); +import {globby} from 'globby'; (async () => { const paths = await globby('images', { @@ -89,33 +89,33 @@ Default: `false` Respect ignore patterns in `.gitignore` files that apply to the globbed files. -### globby.sync(patterns, options?) +### globbySync(patterns, options?) Returns `string[]` of matching paths. -### globby.stream(patterns, options?) +### globbyStream(patterns, options?) Returns a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) of matching paths. 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: ```js -const globby = require('globby'); +import {globbyStream} from 'globby'; (async () => { - for await (const path of globby.stream('*.tmp')) { + for await (const path of globbyStream('*.tmp')) { console.log(path); } })(); ``` -### globby.generateGlobTasks(patterns, options?) +### generateGlobTasks(patterns, options?) Returns an `object[]` in the format `{pattern: string, options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages. Note that you should avoid running the same tasks multiple times as they contain a file system cache. Instead, run this method each time to ensure file system changes are taken into consideration. -### globby.hasMagic(patterns, options?) +### isDynamicPattern(patterns, options?) Returns a `boolean` of whether there are any special glob characters in the `patterns`. @@ -123,26 +123,26 @@ Note that the options affect the results. This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isdynamicpatternpattern-options). -### globby.gitignore(options?) +### isGitIgnored(options?) 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. ```js -const {gitignore} = require('globby'); +import {isGitIgnored} from 'globby'; (async () => { - const isIgnored = await gitignore(); + const isIgnored = await isGitIgnored(); console.log(isIgnored('some/file')); })(); ``` -### globby.gitignore.sync(options?) +### isGitIgnoredSync(options?) Returns a `(path: string) => boolean` indicating whether a given path is ignored via a `.gitignore` file. -Takes the same options as `globby.gitignore`. +Takes the same options as `isGitIgnored`. ## Globbing patterns diff --git a/stream-utils.js b/stream-utils.js index 98aedc8..3e432b7 100644 --- a/stream-utils.js +++ b/stream-utils.js @@ -1,10 +1,9 @@ -'use strict'; -const {Transform} = require('stream'); +import {Transform} from 'node:stream'; class ObjectTransform extends Transform { constructor() { super({ - objectMode: true + objectMode: true, }); } } @@ -40,7 +39,7 @@ class UniqueStream extends ObjectTransform { } } -module.exports = { +export { FilterStream, - UniqueStream + UniqueStream, }; diff --git a/test.js b/test.js index c73fbdb..38ba7d7 100644 --- a/test.js +++ b/test.js @@ -1,10 +1,18 @@ -const fs = require('fs'); -const util = require('util'); -const path = require('path'); -const test = require('ava'); -const getStream = require('get-stream'); -const globby = require('.'); - +import fs from 'node:fs'; +import path from 'node:path'; +import util from 'node:util'; +import {fileURLToPath} from 'node:url'; +import test from 'ava'; +import getStream from 'get-stream'; +import { + isDynamicPattern, + generateGlobTasks, + globby, + globbySync, + globbyStream, +} from './index.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const cwd = process.cwd(); const temporary = 'tmp'; @@ -13,7 +21,7 @@ const fixture = [ 'b.tmp', 'c.tmp', 'd.tmp', - 'e.tmp' + 'e.tmp', ]; test.before(() => { @@ -41,7 +49,7 @@ test('glob - async', async t => { }); test('glob - async - multiple file paths', t => { - t.deepEqual(globby.sync(['a.tmp', 'b.tmp']), ['a.tmp', 'b.tmp']); + t.deepEqual(globbySync(['a.tmp', 'b.tmp']), ['a.tmp', 'b.tmp']); }); test('glob with multiple patterns - async', async t => { @@ -53,21 +61,21 @@ test('respect patterns order - async', async t => { }); test('respect patterns order - sync', t => { - t.deepEqual(globby.sync(['!*.tmp', 'a.tmp']), ['a.tmp']); + t.deepEqual(globbySync(['!*.tmp', 'a.tmp']), ['a.tmp']); }); test('glob - sync', t => { - t.deepEqual(globby.sync('*.tmp'), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); - t.deepEqual(globby.sync(['a.tmp', '*.tmp', '!{c,d,e}.tmp']), ['a.tmp', 'b.tmp']); - t.deepEqual(globby.sync(['!*.tmp', 'a.tmp']), ['a.tmp']); + t.deepEqual(globbySync('*.tmp'), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); + t.deepEqual(globbySync(['a.tmp', '*.tmp', '!{c,d,e}.tmp']), ['a.tmp', 'b.tmp']); + t.deepEqual(globbySync(['!*.tmp', 'a.tmp']), ['a.tmp']); }); test('glob - sync - multiple file paths', t => { - t.deepEqual(globby.sync(['a.tmp', 'b.tmp']), ['a.tmp', 'b.tmp']); + t.deepEqual(globbySync(['a.tmp', 'b.tmp']), ['a.tmp', 'b.tmp']); }); test('return [] for all negative patterns - sync', t => { - t.deepEqual(globby.sync(['!a.tmp', '!b.tmp']), []); + t.deepEqual(globbySync(['!a.tmp', '!b.tmp']), []); }); test('return [] for all negative patterns - async', async t => { @@ -75,12 +83,12 @@ test('return [] for all negative patterns - async', async t => { }); test('glob - stream', async t => { - t.deepEqual((await getStream.array(globby.stream('*.tmp'))).sort(), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); + t.deepEqual((await getStream.array(globbyStream('*.tmp'))).sort(), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); }); test('glob - stream async iterator support', async t => { const results = []; - for await (const path of globby.stream('*.tmp')) { + for await (const path of globbyStream('*.tmp')) { results.push(path); } @@ -88,25 +96,25 @@ test('glob - stream async iterator support', async t => { }); test('glob - stream - multiple file paths', async t => { - t.deepEqual(await getStream.array(globby.stream(['a.tmp', 'b.tmp'])), ['a.tmp', 'b.tmp']); + t.deepEqual(await getStream.array(globbyStream(['a.tmp', 'b.tmp'])), ['a.tmp', 'b.tmp']); }); test('glob with multiple patterns - stream', async t => { - t.deepEqual(await getStream.array(globby.stream(['a.tmp', '*.tmp', '!{c,d,e}.tmp'])), ['a.tmp', 'b.tmp']); + t.deepEqual(await getStream.array(globbyStream(['a.tmp', '*.tmp', '!{c,d,e}.tmp'])), ['a.tmp', 'b.tmp']); }); test('respect patterns order - stream', async t => { - t.deepEqual(await getStream.array(globby.stream(['!*.tmp', 'a.tmp'])), ['a.tmp']); + t.deepEqual(await getStream.array(globbyStream(['!*.tmp', 'a.tmp'])), ['a.tmp']); }); test('return [] for all negative patterns - stream', async t => { - t.deepEqual(await getStream.array(globby.stream(['!a.tmp', '!b.tmp'])), []); + t.deepEqual(await getStream.array(globbyStream(['!a.tmp', '!b.tmp'])), []); }); test('cwd option', t => { process.chdir(temporary); - t.deepEqual(globby.sync('*.tmp', {cwd}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); - t.deepEqual(globby.sync(['a.tmp', '*.tmp', '!{c,d,e}.tmp'], {cwd}), ['a.tmp', 'b.tmp']); + t.deepEqual(globbySync('*.tmp', {cwd}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); + t.deepEqual(globbySync(['a.tmp', '*.tmp', '!{c,d,e}.tmp'], {cwd}), ['a.tmp', 'b.tmp']); process.chdir(cwd); }); @@ -116,81 +124,81 @@ test('don\'t mutate the options object - async', async t => { }); test('don\'t mutate the options object - sync', t => { - globby.sync(['*.tmp', '!b.tmp'], Object.freeze({ignore: Object.freeze([])})); + globbySync(['*.tmp', '!b.tmp'], Object.freeze({ignore: Object.freeze([])})); t.pass(); }); test('don\'t mutate the options object - stream', async t => { - await getStream.array(globby.stream(['*.tmp', '!b.tmp'], Object.freeze({ignore: Object.freeze([])}))); + await getStream.array(globbyStream(['*.tmp', '!b.tmp'], Object.freeze({ignore: Object.freeze([])}))); t.pass(); }); test('expose generateGlobTasks', t => { - const tasks = globby.generateGlobTasks(['*.tmp', '!b.tmp'], {ignore: ['c.tmp']}); + const tasks = generateGlobTasks(['*.tmp', '!b.tmp'], {ignore: ['c.tmp']}); t.is(tasks.length, 1); t.is(tasks[0].pattern, '*.tmp'); t.deepEqual(tasks[0].options.ignore, ['c.tmp', 'b.tmp']); }); -test('expose hasMagic', t => { - t.true(globby.hasMagic('**')); - t.true(globby.hasMagic(['**', 'path1', 'path2'])); - t.false(globby.hasMagic(['path1', 'path2'])); +test('expose isDynamicPattern', t => { + t.true(isDynamicPattern('**')); + t.true(isDynamicPattern(['**', 'path1', 'path2'])); + t.false(isDynamicPattern(['path1', 'path2'])); }); test('expandDirectories option', t => { - t.deepEqual(globby.sync(temporary), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); - t.deepEqual(globby.sync('**', {cwd: temporary}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); - t.deepEqual(globby.sync(temporary, {expandDirectories: ['a*', 'b*']}), ['tmp/a.tmp', 'tmp/b.tmp']); - t.deepEqual(globby.sync(temporary, { + t.deepEqual(globbySync(temporary), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); + t.deepEqual(globbySync('**', {cwd: temporary}), ['a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', 'e.tmp']); + t.deepEqual(globbySync(temporary, {expandDirectories: ['a*', 'b*']}), ['tmp/a.tmp', 'tmp/b.tmp']); + t.deepEqual(globbySync(temporary, { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] - } + extensions: ['tmp'], + }, }), ['tmp/a.tmp', 'tmp/b.tmp']); - t.deepEqual(globby.sync(temporary, { + t.deepEqual(globbySync(temporary, { expandDirectories: { files: ['a', 'b'], - extensions: ['tmp'] + extensions: ['tmp'], }, - ignore: ['**/b.tmp'] + ignore: ['**/b.tmp'], }), ['tmp/a.tmp']); }); test('expandDirectories:true and onlyFiles:true option', t => { - t.deepEqual(globby.sync(temporary, {onlyFiles: true}), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); + t.deepEqual(globbySync(temporary, {onlyFiles: true}), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); }); test.failing('expandDirectories:true and onlyFiles:false option', t => { // Node-glob('tmp/**') => ['tmp', 'tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp'] // Fast-glob('tmp/**') => ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp'] // See https://github.com/mrmlnc/fast-glob/issues/47 - t.deepEqual(globby.sync(temporary, {onlyFiles: false}), ['tmp', 'tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); + t.deepEqual(globbySync(temporary, {onlyFiles: false}), ['tmp', 'tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); }); test('expandDirectories and ignores option', t => { - t.deepEqual(globby.sync('tmp', { - ignore: ['tmp'] + t.deepEqual(globbySync('tmp', { + ignore: ['tmp'], }), []); - t.deepEqual(globby.sync('tmp/**', { + t.deepEqual(globbySync('tmp/**', { expandDirectories: false, - ignore: ['tmp'] + ignore: ['tmp'], }), ['tmp/a.tmp', 'tmp/b.tmp', 'tmp/c.tmp', 'tmp/d.tmp', 'tmp/e.tmp']); }); test.failing('relative paths and ignores option', t => { process.chdir(temporary); - t.deepEqual(globby.sync('../tmp', { + t.deepEqual(globbySync('../tmp', { cwd: process.cwd(), - ignore: ['tmp'] + ignore: ['tmp'], }), []); process.chdir(cwd); }); // Rejected for being an invalid pattern -[ +for (const value of [ {}, [{}], true, @@ -206,8 +214,8 @@ test.failing('relative paths and ignores option', t => { 5, [5], function () {}, - [function () {}] -].forEach(value => { + [function () {}], +]) { const valueString = util.format(value); const message = 'Patterns must be a string or an array of strings'; @@ -218,34 +226,34 @@ test.failing('relative paths and ignores option', t => { test(`throws for invalid patterns input: ${valueString} - sync`, t => { t.throws(() => { - globby.sync(value); + globbySync(value); }, {instanceOf: TypeError}); t.throws(() => { - globby.sync(value); + globbySync(value); }, {message}); }); test(`throws for invalid patterns input: ${valueString} - stream`, t => { t.throws(() => { - globby.stream(value); + globbyStream(value); }, {instanceOf: TypeError}); t.throws(() => { - globby.stream(value); + globbyStream(value); }, {message}); }); test(`generateGlobTasks throws for invalid patterns input: ${valueString}`, t => { t.throws(() => { - globby.generateGlobTasks(value); + generateGlobTasks(value); }, {instanceOf: TypeError}); t.throws(() => { - globby.generateGlobTasks(value); + generateGlobTasks(value); }, {message}); }); -}); +} test('gitignore option defaults to false - async', async t => { const actual = await globby('*', {onlyFiles: false}); @@ -253,12 +261,12 @@ test('gitignore option defaults to false - async', async t => { }); test('gitignore option defaults to false - sync', t => { - const actual = globby.sync('*', {onlyFiles: false}); + const actual = globbySync('*', {onlyFiles: false}); t.true(actual.includes('node_modules')); }); test('gitignore option defaults to false - stream', async t => { - const actual = await getStream.array(globby.stream('*', {onlyFiles: false})); + const actual = await getStream.array(globbyStream('*', {onlyFiles: false})); t.true(actual.includes('node_modules')); }); @@ -268,12 +276,12 @@ test('respects gitignore option true - async', async t => { }); test('respects gitignore option true - sync', t => { - const actual = globby.sync('*', {gitignore: true, onlyFiles: false}); + const actual = globbySync('*', {gitignore: true, onlyFiles: false}); t.false(actual.includes('node_modules')); }); test('respects gitignore option true - stream', async t => { - const actual = await getStream.array(globby.stream('*', {gitignore: true, onlyFiles: false})); + const actual = await getStream.array(globbyStream('*', {gitignore: true, onlyFiles: false})); t.false(actual.includes('node_modules')); }); @@ -283,7 +291,7 @@ test('respects gitignore option false - async', async t => { }); test('respects gitignore option false - sync', t => { - const actual = globby.sync('*', {gitignore: false, onlyFiles: false}); + const actual = globbySync('*', {gitignore: false, onlyFiles: false}); t.true(actual.includes('node_modules')); }); @@ -299,7 +307,7 @@ test('gitignore option with absolute option', async t => { }); test('respects gitignore option false - stream', async t => { - const actual = await getStream.array(globby.stream('*', {gitignore: false, onlyFiles: false})); + const actual = await getStream.array(globbyStream('*', {gitignore: false, onlyFiles: false})); t.true(actual.includes('node_modules')); }); @@ -310,30 +318,30 @@ test('gitingore option and objectMode option - async', async t => { }); test('gitingore option and objectMode option - sync', t => { - const result = globby.sync('fixtures/gitignore/*', {gitignore: true, objectMode: true}); + const result = globbySync('fixtures/gitignore/*', {gitignore: true, objectMode: true}); t.is(result.length, 1); t.truthy(result[0].path); }); test('`{extension: false}` and `expandDirectories.extensions` option', t => { t.deepEqual( - globby.sync('*', { + globbySync('*', { cwd: temporary, extension: false, expandDirectories: { extensions: [ 'md', - 'tmp' - ] - } + 'tmp', + ], + }, }), [ 'a.tmp', 'b.tmp', 'c.tmp', 'd.tmp', - 'e.tmp' - ] + 'e.tmp', + ], ); }); @@ -342,12 +350,12 @@ test('throws when specifying a file as cwd - async', async t => { await t.throwsAsync( globby('.', {cwd: isFile}), - {message: 'The `cwd` option must be a path to a directory'} + {message: 'The `cwd` option must be a path to a directory'}, ); await t.throwsAsync( globby('*', {cwd: isFile}), - {message: 'The `cwd` option must be a path to a directory'} + {message: 'The `cwd` option must be a path to a directory'}, ); }); @@ -355,11 +363,11 @@ test('throws when specifying a file as cwd - sync', t => { const isFile = path.resolve('fixtures/gitignore/bar.js'); t.throws(() => { - globby.sync('.', {cwd: isFile}); + globbySync('.', {cwd: isFile}); }, {message: 'The `cwd` option must be a path to a directory'}); t.throws(() => { - globby.sync('*', {cwd: isFile}); + globbySync('*', {cwd: isFile}); }, {message: 'The `cwd` option must be a path to a directory'}); }); @@ -367,11 +375,11 @@ test('throws when specifying a file as cwd - stream', t => { const isFile = path.resolve('fixtures/gitignore/bar.js'); t.throws(() => { - globby.stream('.', {cwd: isFile}); + globbyStream('.', {cwd: isFile}); }, {message: 'The `cwd` option must be a path to a directory'}); t.throws(() => { - globby.stream('*', {cwd: isFile}); + globbyStream('*', {cwd: isFile}); }, {message: 'The `cwd` option must be a path to a directory'}); }); @@ -381,6 +389,6 @@ test('don\'t throw when specifying a non-existing cwd directory - async', async }); test('don\'t throw when specifying a non-existing cwd directory - sync', t => { - const actual = globby.sync('.', {cwd: '/unknown'}); + const actual = globbySync('.', {cwd: '/unknown'}); t.is(actual.length, 0); });