From 8ef2bd91197177657a2b7c11793317de9db4ca72 Mon Sep 17 00:00:00 2001 From: gitphill Date: Thu, 28 Nov 2019 09:34:28 +0000 Subject: [PATCH] feat: find files func in prep for auto detection Added find-files module with find function to help search for files. Added test for find and test fixtures. Ignore node_modules in sub directories or as given path. Co-authored-by: Lili Kastilio --- src/lib/find-files.ts | 109 ++++++++++++++++ test/find-files.test.ts | 123 ++++++++++++++++++ test/fixtures/find-files/README.md | 3 + test/fixtures/find-files/gradle/build.gradle | 0 .../find-files/gradle/subproject/build.gradle | 0 test/fixtures/find-files/maven/pom.xml | 0 test/fixtures/find-files/maven/test.txt | 0 test/fixtures/find-files/mvn/pom.xml | 0 test/fixtures/find-files/mvn/test.txt | 0 .../node_modules/dependency/package.json | 0 .../node_modules/dependency/test.txt | 0 .../npm/node_modules/dependency/package.json | 0 .../npm/node_modules/dependency/test.txt | 0 test/fixtures/find-files/npm/package.json | 0 test/fixtures/find-files/npm/test.txt | 0 test/fixtures/find-files/ruby/Gemfile | 0 test/fixtures/find-files/ruby/test.txt | 0 17 files changed, 235 insertions(+) create mode 100644 src/lib/find-files.ts create mode 100644 test/find-files.test.ts create mode 100644 test/fixtures/find-files/README.md create mode 100644 test/fixtures/find-files/gradle/build.gradle create mode 100644 test/fixtures/find-files/gradle/subproject/build.gradle create mode 100644 test/fixtures/find-files/maven/pom.xml create mode 100644 test/fixtures/find-files/maven/test.txt create mode 100644 test/fixtures/find-files/mvn/pom.xml create mode 100644 test/fixtures/find-files/mvn/test.txt create mode 100644 test/fixtures/find-files/node_modules/dependency/package.json create mode 100644 test/fixtures/find-files/node_modules/dependency/test.txt create mode 100644 test/fixtures/find-files/npm/node_modules/dependency/package.json create mode 100644 test/fixtures/find-files/npm/node_modules/dependency/test.txt create mode 100644 test/fixtures/find-files/npm/package.json create mode 100644 test/fixtures/find-files/npm/test.txt create mode 100644 test/fixtures/find-files/ruby/Gemfile create mode 100644 test/fixtures/find-files/ruby/test.txt diff --git a/src/lib/find-files.ts b/src/lib/find-files.ts new file mode 100644 index 0000000000..2dc4767f2f --- /dev/null +++ b/src/lib/find-files.ts @@ -0,0 +1,109 @@ +import * as fs from 'fs'; +import * as pathLib from 'path'; +// TODO: use util.promisify once we move to node 8 + +/** + * Returns files inside given file path. + * + * @param path file path. + */ +export async function readDirectory(path: string): Promise { + return await new Promise((resolve, reject) => { + fs.readdir(path, (err, files) => { + if (err) { + reject(err); + } + resolve(files); + }); + }); +} + +/** + * Returns file stats object for given file path. + * + * @param path path to file or directory. + */ +export async function getStats(path: string): Promise { + return await new Promise((resolve, reject) => { + fs.stat(path, (err, stats) => { + if (err) { + reject(err); + } + resolve(stats); + }); + }); +} + +/** + * Find all files in given search path. Returns paths to files found. + * + * @param path file path to search. + * @param ignore (optional) files to ignore. Will always ignore node_modules. + * @param filter (optional) file names to find. If not provided all files are returned. + * @param levelsDeep (optional) how many levels deep to search, defaults to two, this path and one sub directory. + */ +export async function find( + path: string, + ignore: string[] = [], + filter: string[] = [], + levelsDeep = 2, +): Promise { + const found: string[] = []; + // ensure we ignore find against node_modules path. + if (path.endsWith('node_modules')) { + return found; + } + // ensure node_modules is always ignored + if (!ignore.includes('node_modules')) { + ignore.push('node_modules'); + } + try { + if (levelsDeep < 0) { + return found; + } else { + levelsDeep--; + } + const fileStats = await getStats(path); + if (fileStats.isDirectory()) { + const files = await findInDirectory(path, ignore, filter, levelsDeep); + found.push(...files); + } else if (fileStats.isFile()) { + const fileFound = findFile(path, filter); + if (fileFound) { + found.push(fileFound); + } + } + return found; + } catch (err) { + throw new Error(`Error finding files in path '${path}'.\n${err.message}`); + } +} + +function findFile(path: string, filter: string[] = []): string | null { + if (filter.length > 0) { + const filename = pathLib.basename(path); + if (filter.includes(filename)) { + return path; + } + } else { + return path; + } + return null; +} + +async function findInDirectory( + path: string, + ignore: string[] = [], + filter: string[] = [], + levelsDeep = 2, +): Promise { + const files = await readDirectory(path); + const toFind = files + .filter((file) => !ignore.includes(file)) + .map((file) => { + const resolvedPath = pathLib.resolve(path, file); + return find(resolvedPath, ignore, filter, levelsDeep); + }); + const found = await Promise.all(toFind); + return Array.prototype.concat.apply([], found); +} diff --git a/test/find-files.test.ts b/test/find-files.test.ts new file mode 100644 index 0000000000..aeaa770794 --- /dev/null +++ b/test/find-files.test.ts @@ -0,0 +1,123 @@ +import * as path from 'path'; +import { test } from 'tap'; +import { find } from '../src/lib/find-files'; + +const testFixture = path.join(__dirname, 'fixtures', 'find-files'); + +test('find all files in test fixture', async (t) => { + // four levels deep to find all + const result = await find(testFixture, [], [], 4); + const expected = [ + path.join(testFixture, 'README.md'), + path.join(testFixture, 'gradle', 'build.gradle'), + path.join(testFixture, 'gradle', 'subproject', 'build.gradle'), + path.join(testFixture, 'maven', 'pom.xml'), + path.join(testFixture, 'maven', 'test.txt'), + path.join(testFixture, 'mvn', 'pom.xml'), + path.join(testFixture, 'mvn', 'test.txt'), + path.join(testFixture, 'npm', 'package.json'), + path.join(testFixture, 'npm', 'test.txt'), + path.join(testFixture, 'ruby', 'Gemfile'), + path.join(testFixture, 'ruby', 'test.txt'), + ].sort(); + t.same(result.sort(), expected, 'should return all files'); +}); + +test('find all files in test fixture ignoring node_modules', async (t) => { + // four levels deep to ensure node_modules is tested + const result = await find(testFixture, ['node_modules'], [], 4); + const expected = [ + path.join(testFixture, 'README.md'), + path.join(testFixture, 'gradle', 'build.gradle'), + path.join(testFixture, 'gradle', 'subproject', 'build.gradle'), + path.join(testFixture, 'maven', 'pom.xml'), + path.join(testFixture, 'maven', 'test.txt'), + path.join(testFixture, 'mvn', 'pom.xml'), + path.join(testFixture, 'mvn', 'test.txt'), + path.join(testFixture, 'npm', 'package.json'), + path.join(testFixture, 'npm', 'test.txt'), + path.join(testFixture, 'ruby', 'Gemfile'), + path.join(testFixture, 'ruby', 'test.txt'), + ].sort(); + t.same(result.sort(), expected, 'should return expected files'); +}); + +test('find package.json file in test fixture ignoring node_modules', async (t) => { + // four levels deep to ensure node_modules is tested + const nodeModulesPath = path.join(testFixture, 'node_modules'); + const result = await find(nodeModulesPath, [], ['package.json'], 4); + const expected = []; + t.same(result, expected, 'should return expected file'); +}); + +test('find package.json file in test fixture (by default ignoring node_modules)', async (t) => { + // four levels deep to ensure node_modules is tested + const result = await find(testFixture, [], ['package.json'], 4); + const expected = [path.join(testFixture, 'npm', 'package.json')]; + t.same(result, expected, 'should return expected file'); +}); + +test('find package.json file in test fixture (by default ignoring node_modules)', async (t) => { + // four levels deep to ensure node_modules is tested + const result = await find(testFixture, [], ['package.json'], 4); + const expected = [path.join(testFixture, 'npm', 'package.json')]; + t.same(result, expected, 'should return expected file'); +}); + +test('find Gemfile file in test fixture', async (t) => { + const result = await find(testFixture, [], ['Gemfile']); + const expected = [path.join(testFixture, 'ruby', 'Gemfile')]; + t.same(result, expected, 'should return expected file'); +}); + +test('find pom.xml files in test fixture', async (t) => { + const result = await find(testFixture, [], ['pom.xml']); + const expected = [ + path.join(testFixture, 'maven', 'pom.xml'), + path.join(testFixture, 'mvn', 'pom.xml'), + ].sort(); + t.same(result.sort(), expected, 'should return expected files'); +}); + +test('find build.gradle, but stop at first build.gradle found', async (t) => { + t.todo('stop recursion for given file names'); +}); + +test('find path that does not exist', async (t) => { + try { + await find('does-not-exist'); + t.fail('expected exception to be thrown'); + } catch (err) { + t.match( + err.message, + 'Error finding files in path', + 'throws expected exception', + ); + } +}); + +test('find path is empty string', async (t) => { + try { + await find(''); + t.fail('expected exception to be thrown'); + } catch (err) { + t.match( + err.message, + 'Error finding files in path', + 'throws expected exception', + ); + } +}); + +test('find path is relative', async (t) => { + try { + await find('fixtures/find-files'); + t.fail('expected exception to be thrown'); + } catch (err) { + t.match( + err.message, + 'Error finding files in path', + 'throws expected exception', + ); + } +}); diff --git a/test/fixtures/find-files/README.md b/test/fixtures/find-files/README.md new file mode 100644 index 0000000000..516f8971dd --- /dev/null +++ b/test/fixtures/find-files/README.md @@ -0,0 +1,3 @@ +# find-files + +Files in this directory are used by `find-files.test.ts` to test `find-files.ts` functions. \ No newline at end of file diff --git a/test/fixtures/find-files/gradle/build.gradle b/test/fixtures/find-files/gradle/build.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/gradle/subproject/build.gradle b/test/fixtures/find-files/gradle/subproject/build.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/maven/pom.xml b/test/fixtures/find-files/maven/pom.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/maven/test.txt b/test/fixtures/find-files/maven/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/mvn/pom.xml b/test/fixtures/find-files/mvn/pom.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/mvn/test.txt b/test/fixtures/find-files/mvn/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/node_modules/dependency/package.json b/test/fixtures/find-files/node_modules/dependency/package.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/node_modules/dependency/test.txt b/test/fixtures/find-files/node_modules/dependency/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/npm/node_modules/dependency/package.json b/test/fixtures/find-files/npm/node_modules/dependency/package.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/npm/node_modules/dependency/test.txt b/test/fixtures/find-files/npm/node_modules/dependency/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/npm/package.json b/test/fixtures/find-files/npm/package.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/npm/test.txt b/test/fixtures/find-files/npm/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/ruby/Gemfile b/test/fixtures/find-files/ruby/Gemfile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/find-files/ruby/test.txt b/test/fixtures/find-files/ruby/test.txt new file mode 100644 index 0000000000..e69de29bb2