From 95d4390b5be7038b9d4af106ed8d2ad80738196e Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Sun, 30 Sep 2018 14:50:45 -0400 Subject: [PATCH 01/16] Add support for filename matcher function --- index.js | 9 +++--- package.json | 2 ++ readme.md | 12 ++++++-- test.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 8e83819..797b99a 100644 --- a/index.js +++ b/index.js @@ -10,9 +10,10 @@ module.exports = (filename, opts = {}) => { return new Promise(resolve => { (function find(dir) { - locatePath(filenames, {cwd: dir}).then(file => { + const locating = typeof filename === 'function' ? Promise.resolve(filename(dir)) : locatePath(filenames, {cwd: dir}); + locating.then(file => { if (file) { - resolve(path.join(dir, file)); + resolve(path.resolve(dir, file)); } else if (dir === root) { resolve(null); } else { @@ -31,10 +32,10 @@ module.exports.sync = (filename, opts = {}) => { // eslint-disable-next-line no-constant-condition while (true) { - const file = locatePath.sync(filenames, {cwd: dir}); + const file = typeof filename === 'function' ? filename(dir) : locatePath.sync(filenames, {cwd: dir}); if (file) { - return path.join(dir, file); + return path.resolve(dir, file); } if (dir === root) { diff --git a/package.json b/package.json index a708f57..036221d 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,8 @@ }, "devDependencies": { "ava": "*", + "is-path-inside": "^2.0.0", + "pify": "^4.0.0", "tempy": "^0.2.1", "xo": "*" } diff --git a/readme.md b/readme.md index 118d8ae..08bc587 100644 --- a/readme.md +++ b/readme.md @@ -47,6 +47,12 @@ const findUp = require('find-up'); console.log(await findUp(['rainbow.png', 'unicorn.png'])); //=> '/Users/sindresorhus/unicorn.png' + + console.log(await findUp(dir => Promise.resolve('unicorn.png'))); + //=> '/Users/sindresorhus/unicorn.png' + + console.log(await findUp(dir => dir)); + //=> '/Users/sindresorhus' })(); ``` @@ -71,9 +77,11 @@ Returns the first filepath found (by respecting the order) or `null`. #### filename -Type: `string` +Type: `string` `Function` + +Filename of the file to find, or a custom matcher function to be called with each directory until it returns a filepath to stop the search or the root directory has been reached and nothing was found. When using a matcher function, if you want to check whether a file exists, use [`fs.access()`](https://nodejs.org/api/fs.html#fs_fs_access_path_mode_callback) - this is done automatically when `filename` is a string. -Filename of the file to find. +When using async mode, `filename` may optionally be an `async` function or return a `Promise` for the filepath. #### options diff --git a/test.js b/test.js index e6971aa..028bd7b 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,8 @@ import fs from 'fs'; import path from 'path'; import test from 'ava'; +import isPathInside from 'is-path-inside'; +import pify from 'pify'; import tempy from 'tempy'; import m from '.'; @@ -244,3 +246,87 @@ test('sync (not found, custom cwd)', t => { t.is(filePath, null); }); + +test('async (custom function)', async t => { + const cwd = process.cwd(); + + t.is(await m(dir => { + t.is(dir, cwd); + return dir; + }), cwd); + + t.is(await m(() => { + return '.'; + }), cwd); + + t.is(await m(() => { + return Promise.resolve('foo.txt'); + }), path.join(cwd, 'foo.txt')); + + t.is(await m(() => { + return '..'; + }), path.join(cwd, '..')); + + t.is(await m(dir => { + return (dir !== cwd) && dir; + }), path.join(cwd, '..')); + + t.is(await m(dir => { + return (dir !== cwd) && 'foo.txt'; + }), path.join(cwd, '..', 'foo.txt')); + + const {root} = path.parse(cwd); + const visited = new Set(); + t.is(await m(async dir => { + t.is(typeof dir, 'string'); + const stat = await pify(fs.stat)(dir); + t.true(stat.isDirectory()); + t.true((dir === cwd) || isPathInside(cwd, dir)); + t.false(visited.has(dir)); + visited.add(dir); + }), null); + t.true(visited.has(cwd)); + t.true(visited.has(root)); +}); + +test('sync (custom function)', t => { + const cwd = process.cwd(); + + t.is(m.sync(dir => { + t.is(dir, cwd); + return dir; + }), cwd); + + t.is(m.sync(() => { + return '.'; + }), cwd); + + t.is(m.sync(() => { + return 'foo.txt'; + }), path.join(cwd, 'foo.txt')); + + t.is(m.sync(() => { + return '..'; + }), path.join(cwd, '..')); + + t.is(m.sync(dir => { + return (dir !== cwd) && dir; + }), path.join(cwd, '..')); + + t.is(m.sync(dir => { + return (dir !== cwd) && 'foo.txt'; + }), path.join(cwd, '..', 'foo.txt')); + + const {root} = path.parse(cwd); + const visited = new Set(); + t.is(m.sync(dir => { + t.is(typeof dir, 'string'); + const stat = fs.statSync(dir); + t.true(stat.isDirectory()); + t.true((dir === cwd) || isPathInside(cwd, dir)); + t.false(visited.has(dir)); + visited.add(dir); + }), null); + t.true(visited.has(cwd)); + t.true(visited.has(root)); +}); From 8b80cf185464433fbcbe487f21f3d3660eb60d56 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 28 Dec 2018 18:35:58 -0500 Subject: [PATCH 02/16] Improve documentation --- readme.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 08bc587..25f3b96 100644 --- a/readme.md +++ b/readme.md @@ -39,6 +39,8 @@ $ npm install find-up `example.js` ```js +const fs = require('fs'); +const path = require('path'); const findUp = require('find-up'); (async () => { @@ -48,10 +50,17 @@ const findUp = require('find-up'); console.log(await findUp(['rainbow.png', 'unicorn.png'])); //=> '/Users/sindresorhus/unicorn.png' - console.log(await findUp(dir => Promise.resolve('unicorn.png'))); - //=> '/Users/sindresorhus/unicorn.png' - - console.log(await findUp(dir => dir)); + console.log(await findUp(dir => { + return fs.existsSync(path.join(dir, 'unicorn.png')) && 'foo'; + })); + //=> '/Users/sindresorhus/foo' + + console.log(await findUp(async dir => { + const children = await fs.promises.readdir(dir); + if (children.some(fileName => fileName.endsWith('.png'))) { + return dir; + } + })); //=> '/Users/sindresorhus' })(); ``` @@ -60,6 +69,7 @@ const findUp = require('find-up'); ## API ### findUp(filename, [options]) +### findUp(matcher, [options]) Returns a `Promise` for either the filepath or `null` if it couldn't be found. @@ -68,20 +78,27 @@ Returns a `Promise` for either the filepath or `null` if it couldn't be found. Returns a `Promise` for either the first filepath found (by respecting the order) or `null` if none could be found. ### findUp.sync(filename, [options]) +### findUp.sync(matcher, [options]) Returns a filepath or `null`. ### findUp.sync([filenameA, filenameB], [options]) -Returns the first filepath found (by respecting the order) or `null`. +Returns the first filepath found (by respecting the order) or `null` if none could be found. #### filename -Type: `string` `Function` +Type: `string` + +Filename of the file to find. + +#### matcher + +Type: `Function` -Filename of the file to find, or a custom matcher function to be called with each directory until it returns a filepath to stop the search or the root directory has been reached and nothing was found. When using a matcher function, if you want to check whether a file exists, use [`fs.access()`](https://nodejs.org/api/fs.html#fs_fs_access_path_mode_callback) - this is done automatically when `filename` is a string. +A function that will be called with each directory until it returns a filepath to stop the search or the root directory has been reached and nothing was found. Useful if you want to match files with a certain pattern, set of permissions, or other advanced use cases. -When using async mode, `filename` may optionally be an `async` function or return a `Promise` for the filepath. +When using async mode, `matcher` may optionally be an `async` function or return a `Promise` for the filepath. #### options From e8072bb0da53c37a50f96af5274db3c75bd8b58e Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 28 Dec 2018 19:33:15 -0500 Subject: [PATCH 03/16] Seperate not found tests --- test.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test.js b/test.js index 028bd7b..084b440 100644 --- a/test.js +++ b/test.js @@ -247,7 +247,7 @@ test('sync (not found, custom cwd)', t => { t.is(filePath, null); }); -test('async (custom function)', async t => { +test('async (matcher function)', async t => { const cwd = process.cwd(); t.is(await m(dir => { @@ -259,8 +259,8 @@ test('async (custom function)', async t => { return '.'; }), cwd); - t.is(await m(() => { - return Promise.resolve('foo.txt'); + t.is(await m(async () => { + return 'foo.txt'; }), path.join(cwd, 'foo.txt')); t.is(await m(() => { @@ -274,7 +274,10 @@ test('async (custom function)', async t => { t.is(await m(dir => { return (dir !== cwd) && 'foo.txt'; }), path.join(cwd, '..', 'foo.txt')); +}); +test('async (not found, matcher function)', async t => { + const cwd = process.cwd(); const {root} = path.parse(cwd); const visited = new Set(); t.is(await m(async dir => { @@ -289,7 +292,7 @@ test('async (custom function)', async t => { t.true(visited.has(root)); }); -test('sync (custom function)', t => { +test('sync (matcher function)', t => { const cwd = process.cwd(); t.is(m.sync(dir => { @@ -316,7 +319,10 @@ test('sync (custom function)', t => { t.is(m.sync(dir => { return (dir !== cwd) && 'foo.txt'; }), path.join(cwd, '..', 'foo.txt')); +}); +test('sync (not found, matcher function)', t => { + const cwd = process.cwd(); const {root} = path.parse(cwd); const visited = new Set(); t.is(m.sync(dir => { From 44e67bffc26881db7326cf02435b209db6ae3fb8 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 28 Dec 2018 22:19:43 -0500 Subject: [PATCH 04/16] Bubble up errors correctly --- index.js | 4 ++-- test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 797b99a..8a51fd5 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ module.exports = (filename, opts = {}) => { const filenames = [].concat(filename); - return new Promise(resolve => { + return new Promise((resolve, reject) => { (function find(dir) { const locating = typeof filename === 'function' ? Promise.resolve(filename(dir)) : locatePath(filenames, {cwd: dir}); locating.then(file => { @@ -19,7 +19,7 @@ module.exports = (filename, opts = {}) => { } else { find(path.dirname(dir)); } - }); + }).catch(reject); })(startDir); }); }; diff --git a/test.js b/test.js index 084b440..47de445 100644 --- a/test.js +++ b/test.js @@ -292,6 +292,32 @@ test('async (not found, matcher function)', async t => { t.true(visited.has(root)); }); +test('async (matcher function throws)', async t => { + const cwd = process.cwd(); + const visited = new Set(); + await t.throwsAsync(m(dir => { + visited.add(dir); + throw new Error('Some sync throw'); + }), { + message : 'Some sync throw' + }); + t.true(visited.has(cwd)); + t.is(visited.size, 1); +}); + +test('async (matcher function rejects)', async t => { + const cwd = process.cwd(); + const visited = new Set(); + await t.throwsAsync(m(async dir => { + visited.add(dir); + throw new Error('Some async rejection'); + }), { + message : 'Some async rejection' + }); + t.true(visited.has(cwd)); + t.is(visited.size, 1); +}); + test('sync (matcher function)', t => { const cwd = process.cwd(); @@ -336,3 +362,18 @@ test('sync (not found, matcher function)', t => { t.true(visited.has(cwd)); t.true(visited.has(root)); }); + +test('sync (matcher function throws)', t => { + const cwd = process.cwd(); + const visited = new Set(); + t.throws(() => { + m.sync(dir => { + visited.add(dir); + throw new Error('Some problem'); + }) + }, { + message : 'Some problem' + }); + t.true(visited.has(cwd)); + t.is(visited.size, 1); +}); From 102b68827aad98a576cfc252d9feb83b55aabb21 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 28 Dec 2018 22:22:49 -0500 Subject: [PATCH 05/16] Fix indentation --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 25f3b96..ff1da80 100644 --- a/readme.md +++ b/readme.md @@ -55,11 +55,11 @@ const findUp = require('find-up'); })); //=> '/Users/sindresorhus/foo' - console.log(await findUp(async dir => { - const children = await fs.promises.readdir(dir); + console.log(await findUp(async dir => { + const children = await fs.promises.readdir(dir); if (children.some(fileName => fileName.endsWith('.png'))) { - return dir; - } + return dir; + } })); //=> '/Users/sindresorhus' })(); From 6ad0dd80329e4210e7091e99e9bcc499818136fc Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Sun, 6 Jan 2019 20:14:57 -0500 Subject: [PATCH 06/16] Fix lint errors --- test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test.js b/test.js index 47de445..1f97b53 100644 --- a/test.js +++ b/test.js @@ -299,7 +299,7 @@ test('async (matcher function throws)', async t => { visited.add(dir); throw new Error('Some sync throw'); }), { - message : 'Some sync throw' + message: 'Some sync throw' }); t.true(visited.has(cwd)); t.is(visited.size, 1); @@ -312,7 +312,7 @@ test('async (matcher function rejects)', async t => { visited.add(dir); throw new Error('Some async rejection'); }), { - message : 'Some async rejection' + message: 'Some async rejection' }); t.true(visited.has(cwd)); t.is(visited.size, 1); @@ -370,9 +370,9 @@ test('sync (matcher function throws)', t => { m.sync(dir => { visited.add(dir); throw new Error('Some problem'); - }) + }); }, { - message : 'Some problem' + message: 'Some problem' }); t.true(visited.has(cwd)); t.is(visited.size, 1); From ca48c766f0966508583ea0a970ecbce0e3e77477 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Thu, 18 Apr 2019 20:54:31 -0400 Subject: [PATCH 07/16] Add Symbol for stopping early --- index.js | 12 +++++++++++- test.js | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 8a51fd5..ad0fd0c 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,8 @@ const path = require('path'); const locatePath = require('locate-path'); +const stop = Symbol('findUp.stop'); + module.exports = (filename, opts = {}) => { const startDir = path.resolve(opts.cwd || ''); const {root} = path.parse(startDir); @@ -12,7 +14,9 @@ module.exports = (filename, opts = {}) => { (function find(dir) { const locating = typeof filename === 'function' ? Promise.resolve(filename(dir)) : locatePath(filenames, {cwd: dir}); locating.then(file => { - if (file) { + if (file === stop) { + resolve(null); + } else if (file) { resolve(path.resolve(dir, file)); } else if (dir === root) { resolve(null); @@ -34,6 +38,10 @@ module.exports.sync = (filename, opts = {}) => { while (true) { const file = typeof filename === 'function' ? filename(dir) : locatePath.sync(filenames, {cwd: dir}); + if (file === stop) { + return null; + } + if (file) { return path.resolve(dir, file); } @@ -45,3 +53,5 @@ module.exports.sync = (filename, opts = {}) => { dir = path.dirname(dir); } }; + +module.exports.stop = stop; diff --git a/test.js b/test.js index 1f97b53..f497cf6 100644 --- a/test.js +++ b/test.js @@ -318,6 +318,17 @@ test('async (matcher function rejects)', async t => { t.is(visited.size, 1); }); +test('async (matcher function stops early)', async t => { + const cwd = process.cwd(); + const visited = new Set(); + t.is(await m(async dir => { + visited.add(dir); + return m.stop; + }), null); + t.true(visited.has(cwd)); + t.is(visited.size, 1); +}); + test('sync (matcher function)', t => { const cwd = process.cwd(); @@ -377,3 +388,14 @@ test('sync (matcher function throws)', t => { t.true(visited.has(cwd)); t.is(visited.size, 1); }); + +test('sync (matcher function stops early)', t => { + const cwd = process.cwd(); + const visited = new Set(); + t.is(m.sync(dir => { + visited.add(dir); + return m.stop; + }), null); + t.true(visited.has(cwd)); + t.is(visited.size, 1); +}); From eff847910867172997d5fab839a0915ca371c3d5 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 19 Apr 2019 10:44:54 -0400 Subject: [PATCH 08/16] Use util.promisify instead of pify --- package.json | 1 - test.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ec3a888..cb92c5a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "devDependencies": { "ava": "^1.4.1", "is-path-inside": "^2.1.0", - "pify": "^4.0.1", "tempy": "^0.3.0", "tsd": "^0.7.2", "xo": "^0.24.0" diff --git a/test.js b/test.js index 994a295..0ec92a7 100644 --- a/test.js +++ b/test.js @@ -1,8 +1,8 @@ import fs from 'fs'; import path from 'path'; +import {promisify} from 'util'; import test from 'ava'; import isPathInside from 'is-path-inside'; -import pify from 'pify'; import tempy from 'tempy'; import findUp from '.'; @@ -285,7 +285,7 @@ test('async (not found, matcher function)', async t => { const visited = new Set(); t.is(await findUp(async directory => { t.is(typeof directory, 'string'); - const stat = await pify(fs.stat)(directory); + const stat = await promisify(fs.stat)(directory); t.true(stat.isDirectory()); t.true((directory === cwd) || isPathInside(cwd, directory)); t.false(visited.has(directory)); From cba6f26be4979653bc54318f0fa5967bc26c5b5a Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 19 Apr 2019 14:36:09 -0400 Subject: [PATCH 09/16] Add documentation and TypeScript types --- index.d.ts | 25 ++++++++++++++++++++++++- index.test-d.ts | 26 ++++++++++++++++++++++++++ readme.md | 14 ++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 426866c..44e3785 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,6 +9,8 @@ declare namespace findUp { } } +type Match = string | boolean | null | undefined; + declare const findUp: { /** Find a file or directory by walking up parent directories. @@ -41,13 +43,34 @@ declare const findUp: { */ (name: string | string[], options?: findUp.Options): Promise; + /** + Find a file or directory by walking up parent directories. + + @param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search. + @returns The first path found or `undefined` if none could be found. + */ + (matcher: (directory: string) => (Match | Promise), options?: findUp.Options): Promise; + /** Synchronously find a file or directory by walking up parent directories. @param name - Name of the file or directory to find. Can be multiple. - @returns The first path found (by respecting the order of `names`s) or `undefined` if none could be found. + @returns The first path found (by respecting the order of `name`s) or `undefined` if none could be found. */ sync(name: string | string[], options?: findUp.Options): string | undefined; + + /** + Synchronously find a file or directory by walking up parent directories. + + @param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search. + @returns The first path found or `undefined` if none could be found. + */ + sync(matcher: (directory: string) => Match, options?: findUp.Options): string | undefined; + + /** + Return this in a `matcher` function to stop the search and force `findUp` to immediately return `undefined`. + */ + stop: symbol }; export = findUp; diff --git a/index.test-d.ts b/index.test-d.ts index 93e298f..bfa297f 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -5,8 +5,34 @@ expectType>(findUp('unicorn.png')); expectType>(findUp('unicorn.png', {cwd: ''})); expectType>(findUp(['rainbow.png', 'unicorn.png'])); expectType>(findUp(['rainbow.png', 'unicorn.png'], {cwd: ''})); +expectType>(findUp(() => 'unicorn.png')); +expectType>(findUp(() => 'unicorn.png', {cwd: ''})); +expectType>(findUp(() => false)); +expectType>(findUp(() => false, {cwd: ''})); +expectType>(findUp(() => null)); +expectType>(findUp(() => null, {cwd: ''})); +expectType>(findUp(() => undefined)); +expectType>(findUp(() => undefined, {cwd: ''})); +expectType>(findUp(async () => 'unicorn.png')); +expectType>(findUp(async () => 'unicorn.png', {cwd: ''})); +expectType>(findUp(async () => false)); +expectType>(findUp(async () => false, {cwd: ''})); +expectType>(findUp(async () => null)); +expectType>(findUp(async () => null, {cwd: ''})); +expectType>(findUp(async () => undefined)); +expectType>(findUp(async () => undefined, {cwd: ''})); expectType(findUp.sync('unicorn.png')); expectType(findUp.sync('unicorn.png', {cwd: ''})); expectType(findUp.sync(['rainbow.png', 'unicorn.png'])); expectType(findUp.sync(['rainbow.png', 'unicorn.png'], {cwd: ''})); +expectType(findUp.sync(() => 'unicorn.png')); +expectType(findUp.sync(() => 'unicorn.png', {cwd: ''})); +expectType(findUp.sync(() => false)); +expectType(findUp.sync(() => false, {cwd: ''})); +expectType(findUp.sync(() => null)); +expectType(findUp.sync(() => null, {cwd: ''})); +expectType(findUp.sync(() => undefined)); +expectType(findUp.sync(() => undefined, {cwd: ''})); + +expectType(findUp.stop); diff --git a/readme.md b/readme.md index 45baf29..97ce0b7 100644 --- a/readme.md +++ b/readme.md @@ -112,6 +112,20 @@ Default: `process.cwd()` Directory to start from. +### findUp.stop + +A [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) that can be returned by a `matcher` function to stop the search and cause `findUp` to immediately return `undefined`. Useful as a performance optimization in case the current working directory is deeply nested in the filesystem. + +```js +const path = require('path'); +const findUp = require('find-up'); + +(async () => { + await findUp((directory) => { + return path.basename(directory) === 'work' ? findUp.stop : 'logo.png'; + }); +})(); +``` ## Security From af9d361db48ed648c079348182ff31e82cfc827f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 19 Apr 2019 15:15:19 -0400 Subject: [PATCH 10/16] Tweak documentation Co-Authored-By: sholladay --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 97ce0b7..aa8a81f 100644 --- a/readme.md +++ b/readme.md @@ -97,9 +97,9 @@ Name of the file or directory to find. Type: `Function` -A function that will be called with each directory until it returns a filepath to stop the search or the root directory has been reached and nothing was found. Useful if you want to match files with a certain pattern, set of permissions, or other advanced use cases. +A function that will be called with each directory until it returns a `string` with the path, which stops the search, or the root directory has been reached and nothing was found. Useful if you want to match files with certain patterns, set of permissions, or other advanced use cases. -When using async mode, `matcher` may optionally be an `async` function or return a `Promise` for the filepath. +When using async mode, the `matcher` may optionally be an async or promise-returning function that returns the path. #### options From b10d5b251da0614deba981383104b82a3356505e Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Fri, 19 Apr 2019 15:39:31 -0400 Subject: [PATCH 11/16] Simplify examples --- readme.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 97ce0b7..e97a376 100644 --- a/readme.md +++ b/readme.md @@ -50,17 +50,11 @@ const findUp = require('find-up'); console.log(await findUp(['rainbow.png', 'unicorn.png'])); //=> '/Users/sindresorhus/unicorn.png' - console.log(await findUp(dir => { - return fs.existsSync(path.join(dir, 'unicorn.png')) && 'foo'; - })); - //=> '/Users/sindresorhus/foo' - - console.log(await findUp(async dir => { - const children = await fs.promises.readdir(dir); - if (children.some(fileName => fileName.endsWith('.png'))) { - return dir; - } - })); + const pathExists = filepath => fs.promises.access(filepath).then(_ => true).catch(_ => false); + console.log(await findUp(async (directory) => { + const hasUnicorns = await pathExists(path.join(directory, 'unicorn.png')); + return hasUnicorns && directory; + }}); //=> '/Users/sindresorhus' })(); ``` From ae4d8bc8095a0626cc36b9dd7fcda5de387c893a Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 23 Apr 2019 14:16:36 -0400 Subject: [PATCH 12/16] Use unique symbol Co-Authored-By: sholladay --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 44e3785..23d14fe 100644 --- a/index.d.ts +++ b/index.d.ts @@ -70,7 +70,7 @@ declare const findUp: { /** Return this in a `matcher` function to stop the search and force `findUp` to immediately return `undefined`. */ - stop: symbol + stop: unique symbol }; export = findUp; From bb10d62bf6a2a9a6e7dadd2078375ced68f08214 Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Tue, 23 Apr 2019 17:19:08 -0400 Subject: [PATCH 13/16] Ensure symbol is readonly --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 23d14fe..0a710ed 100644 --- a/index.d.ts +++ b/index.d.ts @@ -70,7 +70,7 @@ declare const findUp: { /** Return this in a `matcher` function to stop the search and force `findUp` to immediately return `undefined`. */ - stop: unique symbol + readonly stop: unique symbol; }; export = findUp; From 94fafd1c65bafbaf30a26fb9ce5c9f35c2c22338 Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Tue, 23 Apr 2019 23:41:05 -0400 Subject: [PATCH 14/16] Add test for finding nested dir at path.basename(cwd) (#1) * Addition testing for edge cases * Return absolute path if found regardless of cwd * Return undefined for non-existant absolute path * Find nested path in basename of cwd * Tweak variable name --- test.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/test.js b/test.js index 0ec92a7..8fbb5d6 100644 --- a/test.js +++ b/test.js @@ -10,13 +10,15 @@ const name = { packageDirectory: 'find-up', packageJson: 'package.json', fixtureDirectory: 'fixture', + modulesDirectory: 'node_modules', baz: 'baz.js', qux: 'qux.js' }; // These paths are relative to the project root const relative = { - fixtureDirectory: name.fixtureDirectory + fixtureDirectory: name.fixtureDirectory, + modulesDirectory: name.modulesDirectory }; relative.baz = path.join(relative.fixtureDirectory, name.baz); relative.qux = path.join(relative.fixtureDirectory, name.qux); @@ -188,6 +190,22 @@ test('sync (nested descendant directory)', t => { t.is(foundPath, absolute.barDir); }); +test('async (nested descendant directory, custom cwd)', async t => { + const filePath = await findUp(relative.barDir, { + cwd: relative.modulesDirectory + }); + + t.is(filePath, absolute.barDir); +}); + +test('sync (nested descendant directory, custom cwd)', t => { + const filePath = findUp.sync(relative.barDir, { + cwd: relative.modulesDirectory + }); + + t.is(filePath, absolute.barDir); +}); + test('async (nested cousin directory, custom cwd)', async t => { const foundPath = await findUp(relative.barDir, { cwd: relative.fixtureDirectory @@ -220,6 +238,46 @@ test('sync (ancestor directory, custom cwd)', t => { t.is(foundPath, absolute.fixtureDirectory); }); +test('async (absolute directory)', async t => { + const filePath = await findUp(absolute.barDir); + + t.is(filePath, absolute.barDir); +}); + +test('sync (absolute directory)', t => { + const filePath = findUp.sync(absolute.barDir); + + t.is(filePath, absolute.barDir); +}); + +test('async (not found, absolute file)', async t => { + const filePath = await findUp(path.resolve('somenonexistentfile.js')); + + t.is(filePath, undefined); +}); + +test('sync (not found, absolute file)', t => { + const filePath = findUp.sync(path.resolve('somenonexistentfile.js')); + + t.is(filePath, undefined); +}); + +test('async (absolute directory, disjoint cwd)', async t => { + const filePath = await findUp(absolute.barDir, { + cwd: t.context.disjoint + }); + + t.is(filePath, absolute.barDir); +}); + +test('sync (absolute directory, disjoint cwd)', t => { + const filePath = findUp.sync(absolute.barDir, { + cwd: t.context.disjoint + }); + + t.is(filePath, absolute.barDir); +}); + test('async (not found)', async t => { const foundPath = await findUp('somenonexistentfile.js'); From 41b8bdb1994062edfb6f49b69814f6f23e0753ce Mon Sep 17 00:00:00 2001 From: Seth Holladay Date: Wed, 24 Apr 2019 11:25:38 -0400 Subject: [PATCH 15/16] More strict TypeScript types --- index.d.ts | 7 +++---- index.test-d.ts | 18 ++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0a710ed..9203688 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,10 +7,9 @@ declare namespace findUp { */ readonly cwd?: string; } + type Match = string | symbol | undefined; } -type Match = string | boolean | null | undefined; - declare const findUp: { /** Find a file or directory by walking up parent directories. @@ -49,7 +48,7 @@ declare const findUp: { @param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search. @returns The first path found or `undefined` if none could be found. */ - (matcher: (directory: string) => (Match | Promise), options?: findUp.Options): Promise; + (matcher: (directory: string) => (findUp.Match | Promise), options?: findUp.Options): Promise; /** Synchronously find a file or directory by walking up parent directories. @@ -65,7 +64,7 @@ declare const findUp: { @param matcher - Called for each directory in the search. Return a path or `findUp.stop` to stop the search. @returns The first path found or `undefined` if none could be found. */ - sync(matcher: (directory: string) => Match, options?: findUp.Options): string | undefined; + sync(matcher: (directory: string) => findUp.Match, options?: findUp.Options): string | undefined; /** Return this in a `matcher` function to stop the search and force `findUp` to immediately return `undefined`. diff --git a/index.test-d.ts b/index.test-d.ts index bfa297f..42b1397 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -7,20 +7,16 @@ expectType>(findUp(['rainbow.png', 'unicorn.png'])); expectType>(findUp(['rainbow.png', 'unicorn.png'], {cwd: ''})); expectType>(findUp(() => 'unicorn.png')); expectType>(findUp(() => 'unicorn.png', {cwd: ''})); -expectType>(findUp(() => false)); -expectType>(findUp(() => false, {cwd: ''})); -expectType>(findUp(() => null)); -expectType>(findUp(() => null, {cwd: ''})); expectType>(findUp(() => undefined)); expectType>(findUp(() => undefined, {cwd: ''})); +expectType>(findUp(() => findUp.stop)); +expectType>(findUp(() => findUp.stop, {cwd: ''})); expectType>(findUp(async () => 'unicorn.png')); expectType>(findUp(async () => 'unicorn.png', {cwd: ''})); -expectType>(findUp(async () => false)); -expectType>(findUp(async () => false, {cwd: ''})); -expectType>(findUp(async () => null)); -expectType>(findUp(async () => null, {cwd: ''})); expectType>(findUp(async () => undefined)); expectType>(findUp(async () => undefined, {cwd: ''})); +expectType>(findUp(async () => findUp.stop)); +expectType>(findUp(async () => findUp.stop, {cwd: ''})); expectType(findUp.sync('unicorn.png')); expectType(findUp.sync('unicorn.png', {cwd: ''})); @@ -28,11 +24,9 @@ expectType(findUp.sync(['rainbow.png', 'unicorn.png'])); expectType(findUp.sync(['rainbow.png', 'unicorn.png'], {cwd: ''})); expectType(findUp.sync(() => 'unicorn.png')); expectType(findUp.sync(() => 'unicorn.png', {cwd: ''})); -expectType(findUp.sync(() => false)); -expectType(findUp.sync(() => false, {cwd: ''})); -expectType(findUp.sync(() => null)); -expectType(findUp.sync(() => null, {cwd: ''})); expectType(findUp.sync(() => undefined)); expectType(findUp.sync(() => undefined, {cwd: ''})); +expectType(findUp.sync(() => findUp.stop)); +expectType(findUp.sync(() => findUp.stop, {cwd: ''})); expectType(findUp.stop); From cf3157c73376407e2fd7d04b5cc8244b5f98ba4e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 24 Apr 2019 23:55:31 +0700 Subject: [PATCH 16/16] Update index.d.ts --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 9203688..425e324 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,6 +7,7 @@ declare namespace findUp { */ readonly cwd?: string; } + type Match = string | symbol | undefined; }