diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..d1e1aaf --- /dev/null +++ b/index.d.ts @@ -0,0 +1,55 @@ +declare namespace findUp { + interface Options { + /** + Directory to start from. + + @default process.cwd() + */ + readonly cwd?: string; + } +} + +declare const findUp: { + /** + Find a file or directory by walking up parent directories. + + @param filename - Filename of the file or an array of files to find. + @returns Either the first filepath found (by respecting the order of `filename`s) or `null`. + + @example + ``` + // / + // └── Users + // └── sindresorhus + // ├── unicorn.png + // └── foo + // └── bar + // ├── baz + // └── example.js + + // example.js + import findUp = require('find-up'); + + (async () => { + console.log(await findUp('unicorn.png')); + //=> '/Users/sindresorhus/unicorn.png' + + console.log(await findUp(['rainbow.png', 'unicorn.png'])); + //=> '/Users/sindresorhus/unicorn.png' + })(); + ``` + */ + (filename: string | string[], options?: findUp.Options): Promise< + string | null + >; + + /** + Synchronously find a file or directory by walking up parent directories. + + @param filename - Filename of the file or an array of files to find. + @returns Either the first filepath found (by respecting the order of `filename`s) or `null`. + */ + sync(filename: string | string[], options?: findUp.Options): string | null; +}; + +export = findUp; diff --git a/index.js b/index.js index 8e83819..de62b65 100644 --- a/index.js +++ b/index.js @@ -2,45 +2,45 @@ const path = require('path'); const locatePath = require('locate-path'); -module.exports = (filename, opts = {}) => { - const startDir = path.resolve(opts.cwd || ''); - const {root} = path.parse(startDir); +module.exports = (filename, options = {}) => { + const startDirectory = path.resolve(options.cwd || ''); + const {root} = path.parse(startDirectory); const filenames = [].concat(filename); return new Promise(resolve => { - (function find(dir) { - locatePath(filenames, {cwd: dir}).then(file => { + (function find(directory) { + locatePath(filenames, {cwd: directory}).then(file => { if (file) { - resolve(path.join(dir, file)); - } else if (dir === root) { + resolve(path.join(directory, file)); + } else if (directory === root) { resolve(null); } else { - find(path.dirname(dir)); + find(path.dirname(directory)); } }); - })(startDir); + })(startDirectory); }); }; -module.exports.sync = (filename, opts = {}) => { - let dir = path.resolve(opts.cwd || ''); - const {root} = path.parse(dir); +module.exports.sync = (filename, options = {}) => { + let directory = path.resolve(options.cwd || ''); + const {root} = path.parse(directory); const filenames = [].concat(filename); // eslint-disable-next-line no-constant-condition while (true) { - const file = locatePath.sync(filenames, {cwd: dir}); + const file = locatePath.sync(filenames, {cwd: directory}); if (file) { - return path.join(dir, file); + return path.join(directory, file); } - if (dir === root) { + if (directory === root) { return null; } - dir = path.dirname(dir); + directory = path.dirname(directory); } }; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..6ae68d1 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,16 @@ +import {expectType} from 'tsd'; +import findUp = require('.'); + +expectType>(findUp('unicorn.png')); +expectType>(findUp('unicorn.png', {cwd: ''})); +expectType>(findUp(['rainbow.png', 'unicorn.png'])); +expectType>( + findUp(['rainbow.png', 'unicorn.png'], {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: ''}) +); diff --git a/package.json b/package.json index a708f57..6d54402 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "node": ">=6" }, "scripts": { - "test": "xo && ava" + "test": "xo && ava && tsd" }, "files": [ - "index.js" + "index.js", + "index.d.ts" ], "keywords": [ "find", @@ -43,8 +44,9 @@ "locate-path": "^3.0.0" }, "devDependencies": { - "ava": "*", + "ava": "^1.4.1", "tempy": "^0.2.1", - "xo": "*" + "tsd": "^0.7.2", + "xo": "^0.24.0" } } diff --git a/test.js b/test.js index e6971aa..6b1e224 100644 --- a/test.js +++ b/test.js @@ -2,32 +2,35 @@ import fs from 'fs'; import path from 'path'; import test from 'ava'; import tempy from 'tempy'; -import m from '.'; +import findUp from '.'; const name = { - pkgDir: 'find-up', - pkg: 'package.json', - fixtureDir: 'fixture', + packageDirectory: 'find-up', + packageJson: 'package.json', + fixtureDirectory: 'fixture', baz: 'baz.js', qux: 'qux.js' }; // These paths are relative to the project root -const rel = { - fixtureDir: name.fixtureDir +const relative = { + fixtureDirectory: name.fixtureDirectory }; -rel.baz = path.join(rel.fixtureDir, name.baz); -rel.qux = path.join(rel.fixtureDir, name.qux); -rel.barDir = path.join(rel.fixtureDir, 'foo', 'bar'); +relative.baz = path.join(relative.fixtureDirectory, name.baz); +relative.qux = path.join(relative.fixtureDirectory, name.qux); +relative.barDir = path.join(relative.fixtureDirectory, 'foo', 'bar'); -const abs = { - pkgDir: __dirname +const absolute = { + packageDirectory: __dirname }; -abs.pkg = path.join(abs.pkgDir, name.pkg); -abs.fixtureDir = path.join(abs.pkgDir, name.fixtureDir); -abs.baz = path.join(abs.fixtureDir, name.baz); -abs.qux = path.join(abs.fixtureDir, name.qux); -abs.barDir = path.join(abs.fixtureDir, 'foo', 'bar'); +absolute.packageJson = path.join(absolute.packageDirectory, name.packageJson); +absolute.fixtureDirectory = path.join( + absolute.packageDirectory, + name.fixtureDirectory +); +absolute.baz = path.join(absolute.fixtureDirectory, name.baz); +absolute.qux = path.join(absolute.fixtureDirectory, name.qux); +absolute.barDir = path.join(absolute.fixtureDirectory, 'foo', 'bar'); // Create a disjoint directory, used for the not-found tests test.beforeEach(t => { @@ -40,189 +43,189 @@ test.afterEach(t => { }); test('async (child file)', async t => { - const filePath = await m(name.pkg); + const filePath = await findUp(name.packageJson); - t.is(filePath, abs.pkg); + t.is(filePath, absolute.packageJson); }); test('sync (child file)', t => { - const filePath = m.sync(name.pkg); + const filePath = findUp.sync(name.packageJson); - t.is(filePath, abs.pkg); + t.is(filePath, absolute.packageJson); }); test('async (child dir)', async t => { - const filePath = await m(name.fixtureDir); + const filePath = await findUp(name.fixtureDirectory); - t.is(filePath, abs.fixtureDir); + t.is(filePath, absolute.fixtureDirectory); }); test('sync (child dir)', t => { - const filePath = m.sync(name.fixtureDir); + const filePath = findUp.sync(name.fixtureDirectory); - t.is(filePath, abs.fixtureDir); + t.is(filePath, absolute.fixtureDirectory); }); test('async (child file, custom cwd)', async t => { - const filePath = await m(name.baz, { - cwd: rel.fixtureDir + const filePath = await findUp(name.baz, { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('sync (child file, custom cwd)', t => { - const filePath = m.sync(name.baz, { - cwd: rel.fixtureDir + const filePath = findUp.sync(name.baz, { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('async (child file, array, custom cwd)', async t => { - const filePath = await m([name.baz], { - cwd: rel.fixtureDir + const filePath = await findUp([name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('sync (child file, array, custom cwd)', t => { - const filePath = m.sync([name.baz], { - cwd: rel.fixtureDir + const filePath = findUp.sync([name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('async (first child file, array, custom cwd)', async t => { - const filePath = await m([name.qux, name.baz], { - cwd: rel.fixtureDir + const filePath = await findUp([name.qux, name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.qux); + t.is(filePath, absolute.qux); }); test('sync (first child file, array, custom cwd)', t => { - const filePath = m.sync([name.qux, name.baz], { - cwd: rel.fixtureDir + const filePath = findUp.sync([name.qux, name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.qux); + t.is(filePath, absolute.qux); }); test('async (second child file, array, custom cwd)', async t => { - const filePath = await m(['fake', name.baz], { - cwd: rel.fixtureDir + const filePath = await findUp(['fake', name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('sync (second child file, array, custom cwd)', t => { - const filePath = m.sync(['fake', name.baz], { - cwd: rel.fixtureDir + const filePath = findUp.sync(['fake', name.baz], { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('async (cwd)', async t => { - const filePath = await m(name.pkgDir, { - cwd: abs.pkgDir + const filePath = await findUp(name.packageDirectory, { + cwd: absolute.packageDirectory }); - t.is(filePath, abs.pkgDir); + t.is(filePath, absolute.packageDirectory); }); test('sync (cwd)', t => { - const filePath = m.sync(name.pkgDir, { - cwd: abs.pkgDir + const filePath = findUp.sync(name.packageDirectory, { + cwd: absolute.packageDirectory }); - t.is(filePath, abs.pkgDir); + t.is(filePath, absolute.packageDirectory); }); test('async (cousin file, custom cwd)', async t => { - const filePath = await m(name.baz, { - cwd: rel.barDir + const filePath = await findUp(name.baz, { + cwd: relative.barDir }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('sync (cousin file, custom cwd)', t => { - const filePath = m.sync(name.baz, { - cwd: rel.barDir + const filePath = findUp.sync(name.baz, { + cwd: relative.barDir }); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('async (nested descendant file)', async t => { - const filePath = await m(rel.baz); + const filePath = await findUp(relative.baz); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('sync (nested descendant file)', t => { - const filePath = m.sync(rel.baz); + const filePath = findUp.sync(relative.baz); - t.is(filePath, abs.baz); + t.is(filePath, absolute.baz); }); test('async (nested descendant dir)', async t => { - const filePath = await m(rel.barDir); + const filePath = await findUp(relative.barDir); - t.is(filePath, abs.barDir); + t.is(filePath, absolute.barDir); }); test('sync (nested descendant dir)', t => { - const filePath = m.sync(rel.barDir); + const filePath = findUp.sync(relative.barDir); - t.is(filePath, abs.barDir); + t.is(filePath, absolute.barDir); }); test('async (nested cousin dir, custom cwd)', async t => { - const filePath = await m(rel.barDir, { - cwd: rel.fixtureDir + const filePath = await findUp(relative.barDir, { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.barDir); + t.is(filePath, absolute.barDir); }); test('sync (nested cousin dir, custom cwd)', t => { - const filePath = m.sync(rel.barDir, { - cwd: rel.fixtureDir + const filePath = findUp.sync(relative.barDir, { + cwd: relative.fixtureDirectory }); - t.is(filePath, abs.barDir); + t.is(filePath, absolute.barDir); }); test('async (ancestor dir, custom cwd)', async t => { - const filePath = await m(name.fixtureDir, { - cwd: rel.barDir + const filePath = await findUp(name.fixtureDirectory, { + cwd: relative.barDir }); - t.is(filePath, abs.fixtureDir); + t.is(filePath, absolute.fixtureDirectory); }); test('sync (ancestor dir, custom cwd)', t => { - const filePath = m.sync(name.fixtureDir, { - cwd: rel.barDir + const filePath = findUp.sync(name.fixtureDirectory, { + cwd: relative.barDir }); - t.is(filePath, abs.fixtureDir); + t.is(filePath, absolute.fixtureDirectory); }); test('async (not found)', async t => { - const filePath = await m('somenonexistentfile.js'); + const filePath = await findUp('somenonexistentfile.js'); t.is(filePath, null); }); test('sync (not found)', t => { - const filePath = m.sync('somenonexistentfile.js'); + const filePath = findUp.sync('somenonexistentfile.js'); t.is(filePath, null); }); @@ -230,7 +233,7 @@ test('sync (not found)', t => { // Both tests start in a disjoint directory. `package.json` should not be found // and `null` should be returned. test('async (not found, custom cwd)', async t => { - const filePath = await m(name.pkg, { + const filePath = await findUp(name.packageJson, { cwd: t.context.disjoint }); @@ -238,7 +241,7 @@ test('async (not found, custom cwd)', async t => { }); test('sync (not found, custom cwd)', t => { - const filePath = m.sync(name.pkg, { + const filePath = findUp.sync(name.packageJson, { cwd: t.context.disjoint });