-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: find file and walk directory up
- Loading branch information
Showing
3 changed files
with
109 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |