diff --git a/package.json b/package.json index c156bf2..f261e3b 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,10 @@ ], "scripts": { "prepack": "unbuild", - "release": "standard-version && pnpm publish && git push --follow-tags", + "dev": "vitest", + "release": "pnpm test && standard-version && pnpm publish && git push --follow-tags", "lint": "eslint --ext .ts,.js,.mjs,.cjs .", - "test": "vitest run", + "test": "vitest run --coverage", "test:types": "tsc --noEmit --module esnext --skipLibCheck --moduleResolution node ./test/*.test.ts" }, "dependencies": { diff --git a/src/index.ts b/src/index.ts index 8a80712..7f0b146 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ import { promises as fsp } from 'fs' import { ResolveOptions as _ResolveOptions, resolvePath } from 'mlly' import { isAbsolute } from 'pathe' -import { findNearestFile, FindNearestFileOptions } from './utils' +import { FindFileOptions, findNearestFile } from './utils' import type { PackageJson, TSConfig } from './types' export * from './types' export * from './utils' -export type ResolveOptions = _ResolveOptions & FindNearestFileOptions +export type ResolveOptions = _ResolveOptions & FindFileOptions export function definePackageJSON (pkg: PackageJson): PackageJson { return pkg diff --git a/src/utils.ts b/src/utils.ts index c9c627b..70efce2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import { statSync } from 'fs' import { join, resolve } from 'pathe' -export interface FindNearestFileOptions { +export interface FindFileOptions { /** * The starting directory for the search. * @default . (same as `process.cwd()`) @@ -12,6 +12,10 @@ export interface FindNearestFileOptions { * @default /^node_modules$/ */ rootPattern?: RegExp + /** + * If true, search starts from root level descending into subdirectories + */ + reverse?: boolean /** * A matcher that can evaluate whether the given path is a valid file (for example, * by testing whether the file path exists. @@ -21,9 +25,13 @@ export interface FindNearestFileOptions { test?: (filePath: string) => boolean | null | Promise } -const defaultFindOptions: Required = { +/** @deprecated */ +export type FindNearestFileOptions = FindFileOptions + +const defaultFindOptions: Required = { startingFrom: '.', rootPattern: /^node_modules$/, + reverse: false, test: (filePath: string) => { try { if (statSync(filePath).isFile()) { return true } @@ -32,7 +40,7 @@ const defaultFindOptions: Required = { } } -export async function findNearestFile (filename: string, _options: FindNearestFileOptions = {}): Promise { +export async function findFile (filename: string, _options: FindFileOptions = {}): Promise { const options = { ...defaultFindOptions, ..._options } const basePath = resolve(options.startingFrom) const leadingSlash = basePath[0] === '/' @@ -47,10 +55,25 @@ export async function findNearestFile (filename: string, _options: FindNearestFi let root = segments.findIndex(r => r.match(options.rootPattern)) if (root === -1) { root = 0 } - for (let i = segments.length; i > root; i--) { - const filePath = join(...segments.slice(0, i), filename) - if (await options.test(filePath)) { return filePath } + if (!options.reverse) { + for (let i = segments.length; i > root; i--) { + const filePath = join(...segments.slice(0, i), filename) + if (await options.test(filePath)) { return filePath } + } + } else { + for (let i = root + 1; i < segments.length; i++) { + const filePath = join(...segments.slice(0, i), filename) + if (await options.test(filePath)) { return filePath } + } } throw new Error(`Cannot find matching ${filename} in ${options.startingFrom} or parent directories`) } + +export function findNearestFile (filename: string, _options: FindFileOptions = {}): Promise { + return findFile(filename, _options) +} + +export function findFarthestFile (filename: string, _options: FindFileOptions = {}): Promise { + return findFile(filename, { ..._options, reverse: true }) +} diff --git a/test/fixture/sub/package.json b/test/fixture/sub/package.json new file mode 100644 index 0000000..e9a6a6a --- /dev/null +++ b/test/fixture/sub/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo-sub", + "version": "1.0.0" +} diff --git a/test/index.test.ts b/test/index.test.ts index 5a2d36a..379b144 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -9,7 +9,8 @@ import { resolvePackageJSON, writePackageJSON, writeTSConfig, - TSConfig + TSConfig, + ResolveOptions } from '../src' const fixtureDir = resolve(dirname(fileURLToPath(import.meta.url)), 'fixture') @@ -20,11 +21,15 @@ async function expectToReject (p: Promise) { return expect(await p.then(() => null).catch((err: Error) => err.toString())) } -function testResolve (filename: string, resolveFn: (id?: string) => Promise) { +function testResolve (filename: string, resolveFn: (id?: string, opts?: ResolveOptions) => Promise) { it('finds a package.json in root directory', async () => { const pkgPath = await resolveFn(rFixture('.')) expect(pkgPath).to.equal(rFixture(filename)) }) + it('finds package.json from root', async () => { + const pkgPath = await resolveFn(rFixture('.'), { reverse: true }) + expect(pkgPath).to.equal(rFixture('../..', filename)) // Top level in pkg-types repo + }) it('handles non-existent paths', async () => { const pkgPath = await resolveFn(rFixture('further', 'dir', 'file.json')) expect(pkgPath).to.equal(rFixture(filename))