Skip to content

Commit

Permalink
feat: find file and walk directory up
Browse files Browse the repository at this point in the history
  • Loading branch information
skarab42 committed Aug 5, 2022
1 parent 4c480ac commit b87b3a3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { existsSync } from 'node:fs';
import { dirname, resolve as pathResolve } from 'node:path';

export type OnDirectory<ReturnType> = (directory: string) => ReturnType;

/**
* Executes a callback function with the current directory path as first argument. If the callback function returns a value other than `undefined` then the search is stopped and the value is returned. Otherwise it starts again with the parent directory.
*
* @example
* ```
* // Find the nearest package.json from current working directory.
* const packageJSON = walkDirectoryUp(directory => {
* const path = resolve(directory, 'package.json');
* return existsSync(path) ? path : undefined;
* });
* ```
*
* @param callback a callback function to be executed on each directory.
* @param directory the directory where the search begins, default to current working directory.
*/
export async function walkDirectoryUp<ReturnType>(
callback: OnDirectory<Promise<ReturnType | undefined>>,
directory = process.cwd(),
): Promise<ReturnType | undefined> {
const currentDirectory = pathResolve(directory);
const result = await callback(currentDirectory);

if (result !== undefined) {
return result;
}

const parentDirectory = dirname(currentDirectory);

if (parentDirectory === currentDirectory) {
return;
}

return walkDirectoryUp(callback, parentDirectory);
}

/**
* Returns the absolute path to the first file found in the directory provided or `undefined`.
*
* @param fileName file name or array of file name to search.
* @param directory the directory where the search begins, default to current working directory.
*/
export async function findFile(fileName: string | string[], directory = process.cwd()): Promise<string | undefined> {
if (typeof fileName === 'string') {
const path = pathResolve(directory, fileName);

return existsSync(path) ? path : undefined;
}

for (const name of fileName) {
const file = await findFile(name, directory);

if (file) {
return file;
}
}

return;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './process.js';
export * from './version.js';
export * from './agent.js';
export * from './fs.js';
45 changes: 45 additions & 0 deletions test/fs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, it } from 'vitest';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';

import * as api from '../src/index.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJSONPath = resolve(__dirname, '../package.json');

it('should find package.json path', async () => {
const actualPath = await api.findFile('package.json');

expect(actualPath).toBe(packageJSONPath);
});

it('should find README.md path', async () => {
const expectedPath = resolve(__dirname, '../README.md');
const actualPath = await api.findFile(['a_file_that_does_not.exist', 'README.md', 'package.json']);

expect(actualPath).toBe(expectedPath);
});

it('should find nearest up package.json path', async () => {
const actualPath = await api.walkDirectoryUp((directory) => {
return api.findFile('package.json', directory);
});

expect(actualPath).toBe(packageJSONPath);
});

it('should not find nearest up package.json path', async () => {
const actualPath = await api.walkDirectoryUp((directory) => {
return api.findFile('package.json', directory);
}, '../..');

expect(actualPath).toBe(undefined);
});

it('should find nearest up package.json path (deep)', async () => {
const actualPath = await api.walkDirectoryUp((directory) => {
return api.findFile('package.json', directory);
}, './deep/deep/path');

expect(actualPath).toBe(packageJSONPath);
});

0 comments on commit b87b3a3

Please sign in to comment.