New Upstream Release - node-cosmiconfig

Ready changes

Summary

Merged new upstream version: 8.1.3 (was: 7.1.0+ds1).

Diff

diff --git a/.eslintrc.js b/.eslintrc.js
index 01130d8..57a6407 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -20,11 +20,11 @@ module.exports = {
     'plugin:import/errors',
     'plugin:import/warnings',
     'plugin:import/typescript',
-    'plugin:jest/recommended',
+    'plugin:vitest/recommended',
     'prettier',
     'prettier/@typescript-eslint',
   ],
-  plugins: ['jest', '@typescript-eslint', 'import'],
+  plugins: ['vitest', '@typescript-eslint', 'import'],
   rules: {
     'no-var': 'off',
     'prefer-const': 'off',
@@ -32,6 +32,7 @@ module.exports = {
     'func-names': ['error', 'always'],
     'prefer-template': 'error',
     'no-prototype-builtins': 'error',
+    'no-use-before-define': 'off',
     'object-shorthand': [
       'error',
       'always',
@@ -102,16 +103,14 @@ module.exports = {
     'import/prefer-default-export': 'off',
 
     /**
-     * eslint-plugin-jest
+     * eslint-plugin-vitest
      */
-    'jest/consistent-test-it': ['error', { fn: 'test' }],
-    'jest/valid-title': 'error',
-    'jest/no-test-callback': 'error',
-    'jest/prefer-todo': 'error',
-    'jest/require-to-throw-message': 'off',
+    'vitest/consistent-test-it': ['error', { fn: 'test' }],
+    'vitest/valid-title': 'error',
+    'vitest/no-done-callback': 'error',
     // Many tests make assertions indirectly in a way the plugin
     // does not understand.
-    'jest/expect-expect': 'off',
+    'vitest/expect-expect': 'off',
   },
   settings: {
     node: {
@@ -125,6 +124,7 @@ module.exports = {
       node: {
         extensions: allExtensions,
       },
+      typescript: {},
     },
     'import/extensions': allExtensions,
   },
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..3fa11e1
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: d-fischer
+custom: paypal.me/dfischerdev
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..dff14bb
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,46 @@
+name: Node.js CI
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - '**'
+
+env:
+  FORCE_COLOR: 2
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    name: Test on Node.js ${{ matrix.node }} and ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: true
+      matrix:
+        node: [14, 16, 18]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: ${{ matrix.node }}
+
+      - name: Install dependencies
+        run: npm i
+
+      - name: Test
+        run: npm run test
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v3
+        if: startsWith(matrix.os, 'ubuntu-latest') && matrix.node == 18
+        with:
+          files: ./coverage/lcov.info
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..007eee1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+node_modules
+*.log
+.nyc_output
+coverage
+*.DS_Store
+dist
+.idea
+.vscode
+wallaby.config.js
+
+# lock files
+package-lock.json
+yarn.lock
+pnpm-lock.yaml
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index 80c7210..0000000
--- a/.npmrc
+++ /dev/null
@@ -1,2 +0,0 @@
-package-lock=false
-save-exact=false
diff --git a/.yarnrc b/.yarnrc
deleted file mode 100644
index 75f4dc7..0000000
--- a/.yarnrc
+++ /dev/null
@@ -1,2 +0,0 @@
-save-prefix "^"
---no-lockfile true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c6b0a8..d7e03d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,33 @@
 # Changelog
 
+## 8.1.3
+
+- Fixed: existence of meta config breaking default loaders
+
+## 8.1.2
+
+- Fixed: generation of TypeScript types going to the wrong output path
+
+## 8.1.1
+
+- Fixed: meta config overriding original options completely (now merges correctly)
+
+## 8.1.0
+
+- Added: always look at `.config.{yml,yaml,json,js,cjs}` file to configure cosmiconfig itself, and look for tool configuration in it using `packageProp` (similar to package.json)
+  - For more info on this, look at the [end user configuration section of the README](README.md#usage-for-end-users)
+
+## 8.0.0
+
+**No major breaking changes!** We dropped support for Node 10 and 12 -- which you're probably not using. And we swapped out the YAML parser -- which you probably won't notice.
+
+- **Breaking change:** Drop support for Node 10 and 12.
+- **Breaking change:** Use npm package [js-yaml](https://www.npmjs.com/package/js-yaml) to parse YAML instead of npm package [yaml](https://www.npmjs.com/package/yaml).
+- Added: Loader errors now include the path of the file that was tried to be loaded.
+
 ## 7.1.0
 
-- Added: additional default `searchPlaces` within a .config subdirectory (without leading dot in the file name)
+- Added: Additional default `searchPlaces` within a .config subdirectory (without leading dot in the file name)
 
 ## 7.0.1
 
@@ -206,4 +231,4 @@ More details:
 
 [parse-json-pr-12]: https://github.com/sindresorhus/parse-json/pull/12
 
-[pr-101]: https://github.com/davidtheclark/cosmiconfig/pull/101
+[pr-101]: https://github.com/cosmiconfig/cosmiconfig/pull/101
diff --git a/README.md b/README.md
index 8195c91..07ec3b3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
 # cosmiconfig
 
-[![Build Status](https://img.shields.io/travis/davidtheclark/cosmiconfig/main.svg?label=unix%20build)](https://travis-ci.org/davidtheclark/cosmiconfig) [![Build status](https://img.shields.io/appveyor/ci/davidtheclark/cosmiconfig/main.svg?label=windows%20build)](https://ci.appveyor.com/project/davidtheclark/cosmiconfig/branch/main)
-[![codecov](https://codecov.io/gh/davidtheclark/cosmiconfig/branch/main/graph/badge.svg)](https://codecov.io/gh/davidtheclark/cosmiconfig)
+[![codecov](https://codecov.io/gh/cosmiconfig/cosmiconfig/branch/main/graph/badge.svg)](https://codecov.io/gh/cosmiconfig/cosmiconfig)
 
 Cosmiconfig searches for and loads configuration for your program.
 
@@ -21,7 +20,7 @@ For example, if your module's name is "myapp", cosmiconfig will search up the di
 - a `myapp` property in `package.json`
 - a `.myapprc` file in JSON or YAML format
 - a `.myapprc.json`, `.myapprc.yaml`, `.myapprc.yml`, `.myapprc.js`, or `.myapprc.cjs` file
-- a `myapprc`, `myapprc.json`, `myapprc.yaml`, `myapprc.yml`, `myapprc.js` or `myapprc.cjs` file inside a `.config` subdirectory`
+- a `myapprc`, `myapprc.json`, `myapprc.yaml`, `myapprc.yml`, `myapprc.js` or `myapprc.cjs` file inside a `.config` subdirectory
 - a `myapp.config.js` or `myapp.config.cjs` CommonJS module exporting an object
 
 Cosmiconfig continues to search up the directory tree, checking each of these places in each directory, until it finds some acceptable configuration (or hits the home directory).
@@ -29,7 +28,7 @@ Cosmiconfig continues to search up the directory tree, checking each of these pl
 ## Table of contents
 
 - [Installation](#installation)
-- [Usage](#usage)
+- [Usage for tooling developers](#usage-for-tooling-developers)
 - [Result](#result)
 - [Asynchronous API](#asynchronous-api)
   - [cosmiconfig()](#cosmiconfig-1)
@@ -55,6 +54,7 @@ Cosmiconfig continues to search up the directory tree, checking each of these pl
   - [ignoreEmptySearchPlaces](#ignoreemptysearchplaces)
 - [Caching](#caching)
 - [Differences from rc](#differences-from-rc)
+- [Usage for end users](#usage-for-end-users)
 - [Contributing & Development](#contributing--development)
 
 ## Installation
@@ -63,9 +63,12 @@ Cosmiconfig continues to search up the directory tree, checking each of these pl
 npm install cosmiconfig
 ```
 
-Tested in Node 10+.
+Tested in Node 14+.
 
-## Usage
+## Usage for tooling developers
+
+*If you are an end user (i.e. a user of a tool that uses cosmiconfig, like `prettier` or `stylelint`),
+you can skip down to [the end user section](#usage-for-end-users).*
 
 Create a Cosmiconfig explorer, then either `search` for or directly `load` a configuration file.
 
@@ -211,7 +214,7 @@ const explorerSync = cosmiconfigSync(moduleName[, cosmiconfigOptions])
 
 Creates a *synchronous* cosmiconfig instance ("explorerSync") configured according to the arguments, and initializes its caches.
 
-See [`cosmiconfig()`].
+See [`cosmiconfig()`](#cosmiconfig-1).
 
 ### explorerSync.search()
 
@@ -538,6 +541,89 @@ To avoid or work around caching, you can do the following:
 - Options.
 - Asynchronous by default (though can be run synchronously).
 
+## Usage for end users
+
+When configuring a tool, you can use multiple file formats and put these in multiple places.
+
+Usually, a tool would mention this in its own README file,
+but by default, these are the following places, where `{NAME}` represents the name of the tool:
+
+```
+package.json
+.{NAME}rc
+.{NAME}rc.json
+.{NAME}rc.yaml
+.{NAME}rc.yml
+.{NAME}rc.js
+.{NAME}rc.cjs
+.config/{NAME}rc
+.config/{NAME}rc.json
+.config/{NAME}rc.yaml
+.config/{NAME}rc.yml
+.config/{NAME}rc.js
+.config/{NAME}rc.cjs
+{NAME}.config.js
+{NAME}.config.cjs
+```
+
+The contents of these files are defined by the tool.
+For example, you can configure prettier to enforce semicolons at the end of the line
+using a file named `.config/prettierrc.yml`:
+
+```yaml
+semi: true
+```
+
+Additionally, you have the option to put a property named after the tool in your `package.json` file,
+with the contents of that property being the same as the file contents. To use the same example as above:
+
+```json
+{
+  "name": "your-project",
+  "dependencies": {},
+  "prettier": {
+    "semi": true
+  }
+}
+```
+
+This has the advantage that you can put the configuration of all tools
+(at least the ones that use cosmiconfig) in one file.
+
+You can also add a `cosmiconfig` key within your `package.json` file or create one of the following files
+to configure `cosmiconfig` itself:
+
+```
+.config.json
+.config.yaml
+.config.yml
+.config.js
+.config.cjs
+```
+
+The following property is currently actively supported in these places:
+
+```yaml
+cosmiconfig:
+  # overrides where configuration files are being searched to enforce a custom naming convention and format
+  searchPlaces:
+    - .config/{name}.yml
+```
+
+> **Note:** technically, you can overwrite all options described in [cosmiconfigOptions](#cosmiconfigoptions) here,
+> but everything not listed above should be used at your own risk, as it has not been tested explicitly.
+
+You can also add more root properties outside the `cosmiconfig` property
+to configure your tools, entirely eliminating the need to look for additional configuration files:
+
+```yaml
+cosmiconfig:
+  searchPlaces: []
+
+prettier:
+  semi: true
+```
+
 ## Contributing & Development
 
 Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 6b22989..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-# http://www.appveyor.com/docs/appveyor-yml
-
-branches:
-  only:
-    - main
-    - v7
-
-clone_depth: 5
-version: '{build}'
-build: off
-deploy: off
-cache:
-  - "%LOCALAPPDATA%\\Yarn"
-
-environment:
-  matrix:
-    - nodejs_version: '10'
-    - nodejs_version: '12'
-    - nodejs_version: '14'
-
-install:
-  # https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs
-  - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
-  - yarn install --frozen-lockfile
-
-test_script:
-  - node --version
-  - yarn --version
-  - yarn run check:all
diff --git a/codecov.yml b/codecov.yml
index 9037dc6..69cb760 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1 +1 @@
-comment: off;
+comment: false
diff --git a/debian/changelog b/debian/changelog
index f219f4f..998cc01 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+node-cosmiconfig (8.1.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 21 Apr 2023 14:49:42 -0000
+
 node-cosmiconfig (7.1.0+ds1-1) unstable; urgency=medium
 
   * Team upload
diff --git a/debian/patches/workaround-tsc-update.patch b/debian/patches/workaround-tsc-update.patch
index ebab1d6..d48afd9 100644
--- a/debian/patches/workaround-tsc-update.patch
+++ b/debian/patches/workaround-tsc-update.patch
@@ -3,8 +3,10 @@ Author: Yadd <yadd@debian.org>
 Forwarded: not-needed
 Last-Update: 2021-10-25
 
---- a/tsconfig.json
-+++ b/tsconfig.json
+Index: node-cosmiconfig.git/tsconfig.json
+===================================================================
+--- node-cosmiconfig.git.orig/tsconfig.json
++++ node-cosmiconfig.git/tsconfig.json
 @@ -12,7 +12,7 @@
      "noEmit": true,
      "pretty": true,
diff --git a/global.d.ts b/global.d.ts
deleted file mode 100644
index e4ae2cb..0000000
--- a/global.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module 'yaml';
diff --git a/package.json b/package.json
index 17241bb..d2a270f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "cosmiconfig",
-  "version": "7.1.0",
+  "version": "8.1.3",
   "description": "Find and load configuration from a package.json property, rc file, or CommonJS module",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
@@ -20,8 +20,8 @@
     "format:md": "remark-preset-davidtheclark --format",
     "format:check": "prettier \"**/*.{js,ts,json,yml,yaml}\" --check",
     "typescript": "tsc",
-    "test": "jest --coverage",
-    "test:watch": "jest --watch",
+    "test": "vitest run --coverage",
+    "test:watch": "vitest",
     "check:all": "npm run test && npm run typescript && npm run lint && npm run format:check",
     "prepublishOnly": "npm run check:all && npm run build"
   },
@@ -46,23 +46,25 @@
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/davidtheclark/cosmiconfig.git"
+    "url": "git+https://github.com/cosmiconfig/cosmiconfig.git"
   },
   "keywords": [
     "load",
     "configuration",
     "config"
   ],
-  "author": "David Clark <david.dave.clark@gmail.com>",
+  "author": "Daniel Fischer <daniel@d-fischer.dev>",
   "contributors": [
+    "David Clark <david.dave.clark@gmail.com>",
     "Bogdan Chadkin <trysound@yandex.ru>",
     "Suhas Karanth <sudo.suhas@gmail.com>"
   ],
+  "funding": "https://github.com/sponsors/d-fischer",
   "license": "MIT",
   "bugs": {
-    "url": "https://github.com/davidtheclark/cosmiconfig/issues"
+    "url": "https://github.com/cosmiconfig/cosmiconfig/issues"
   },
-  "homepage": "https://github.com/davidtheclark/cosmiconfig#readme",
+  "homepage": "https://github.com/cosmiconfig/cosmiconfig#readme",
   "prettier": {
     "trailingComma": "all",
     "arrowParens": "always",
@@ -70,35 +72,13 @@
     "printWidth": 80,
     "tabWidth": 2
   },
-  "jest": {
-    "testEnvironment": "node",
-    "collectCoverageFrom": [
-      "src/**/*.{js,ts}"
-    ],
-    "coverageReporters": [
-      "text",
-      "html",
-      "lcov"
-    ],
-    "coverageThreshold": {
-      "global": {
-        "branches": 100,
-        "functions": 100,
-        "lines": 100,
-        "statements": 100
-      }
-    },
-    "resetModules": true,
-    "resetMocks": true,
-    "restoreMocks": true
-  },
   "babel": {
     "presets": [
       [
         "@babel/preset-env",
         {
           "targets": {
-            "node": "10"
+            "node": "14"
           }
         }
       ],
@@ -106,40 +86,42 @@
     ]
   },
   "dependencies": {
-    "@types/parse-json": "^4.0.0",
     "import-fresh": "^3.2.1",
+    "js-yaml": "^4.1.0",
     "parse-json": "^5.0.0",
-    "path-type": "^4.0.0",
-    "yaml": "^1.10.0"
+    "path-type": "^4.0.0"
   },
   "devDependencies": {
     "@babel/cli": "^7.10.4",
     "@babel/core": "^7.10.4",
     "@babel/preset-env": "^7.10.4",
     "@babel/preset-typescript": "^7.10.4",
-    "@types/jest": "^26.0.4",
+    "@types/js-yaml": "^4.0.5",
     "@types/node": "^14.0.22",
-    "@typescript-eslint/eslint-plugin": "^3.6.0",
-    "@typescript-eslint/parser": "^3.6.0",
+    "@types/parse-json": "^4.0.0",
+    "@typescript-eslint/eslint-plugin": "^5.54.1",
+    "@typescript-eslint/parser": "^5.54.1",
+    "@vitest/coverage-istanbul": "^0.29.2",
     "cross-env": "^7.0.2",
     "del": "^5.1.0",
     "del-cli": "^3.0.1",
-    "eslint": "^7.4.0",
+    "eslint": "^8.36.0",
     "eslint-config-davidtheclark-node": "^0.2.2",
     "eslint-config-prettier": "^6.11.0",
+    "eslint-import-resolver-typescript": "^3.5.3",
     "eslint-plugin-import": "^2.22.0",
-    "eslint-plugin-jest": "^23.18.0",
     "eslint-plugin-node": "^11.1.0",
+    "eslint-plugin-vitest": "^0.0.54",
     "husky": "^4.2.5",
-    "jest": "^26.1.0",
     "lint-staged": "^10.2.11",
     "make-dir": "^3.1.0",
     "parent-module": "^2.0.0",
     "prettier": "^2.0.5",
     "remark-preset-davidtheclark": "^0.12.0",
-    "typescript": "^3.9.6"
+    "typescript": "^4.9.5",
+    "vitest": "^0.29.2"
   },
   "engines": {
-    "node": ">=10"
+    "node": ">=14"
   }
 }
diff --git a/src/Explorer.ts b/src/Explorer.ts
index b9b22ae..f4b287e 100644
--- a/src/Explorer.ts
+++ b/src/Explorer.ts
@@ -1,8 +1,8 @@
 import path from 'path';
-import { ExplorerBase } from './ExplorerBase';
-import { readFile } from './readFile';
 import { cacheWrapper } from './cacheWrapper';
+import { ExplorerBase } from './ExplorerBase';
 import { getDirectory } from './getDirectory';
+import { readFile } from './readFile';
 import { CosmiconfigResult, ExplorerOptions, LoadedFileContent } from './types';
 
 class Explorer extends ExplorerBase<ExplorerOptions> {
@@ -13,10 +13,13 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
   public async search(
     searchFrom: string = process.cwd(),
   ): Promise<CosmiconfigResult> {
-    const startDirectory = await getDirectory(searchFrom);
-    const result = await this.searchFromDirectory(startDirectory);
-
-    return result;
+    if (this.config.metaConfigFilePath) {
+      const config = await this._loadFile(this.config.metaConfigFilePath, true);
+      if (config && !config.isEmpty) {
+        return config;
+      }
+    }
+    return await this.searchFromDirectory(await getDirectory(searchFrom));
   }
 
   private async searchFromDirectory(dir: string): Promise<CosmiconfigResult> {
@@ -30,9 +33,7 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
         return this.searchFromDirectory(nextDir);
       }
 
-      const transformResult = await this.config.transform(result);
-
-      return transformResult;
+      return await this.config.transform(result);
     };
 
     if (this.searchCache) {
@@ -46,7 +47,7 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
     for await (const place of this.config.searchPlaces) {
       const placeResult = await this.loadSearchPlace(dir, place);
 
-      if (this.shouldSearchStopWithResult(placeResult) === true) {
+      if (this.shouldSearchStopWithResult(placeResult)) {
         return placeResult;
       }
     }
@@ -62,9 +63,7 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
     const filepath = path.join(dir, place);
     const fileContents = await readFile(filepath);
 
-    const result = await this.createCosmiconfigResult(filepath, fileContents);
-
-    return result;
+    return await this.createCosmiconfigResult(filepath, fileContents, false);
   }
 
   private async loadFileContent(
@@ -78,21 +77,36 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
       return undefined;
     }
     const loader = this.getLoaderEntryForFile(filepath);
-    const loaderResult = await loader(filepath, content);
-    return loaderResult;
+    try {
+      return await loader(filepath, content);
+    } catch (e: any) {
+      e.filepath = filepath;
+      throw e;
+    }
   }
 
   private async createCosmiconfigResult(
     filepath: string,
     content: string | null,
+    forceProp: boolean,
   ): Promise<CosmiconfigResult> {
     const fileContent = await this.loadFileContent(filepath, content);
-    const result = this.loadedContentToCosmiconfigResult(filepath, fileContent);
 
-    return result;
+    return this.loadedContentToCosmiconfigResult(
+      filepath,
+      fileContent,
+      forceProp,
+    );
   }
 
   public async load(filepath: string): Promise<CosmiconfigResult> {
+    return this._loadFile(filepath, false);
+  }
+
+  private async _loadFile(
+    filepath: string,
+    forceProp: boolean,
+  ): Promise<CosmiconfigResult> {
     this.validateFilePath(filepath);
     const absoluteFilePath = path.resolve(process.cwd(), filepath);
 
@@ -104,11 +118,10 @@ class Explorer extends ExplorerBase<ExplorerOptions> {
       const result = await this.createCosmiconfigResult(
         absoluteFilePath,
         fileContents,
+        forceProp,
       );
 
-      const transformResult = await this.config.transform(result);
-
-      return transformResult;
+      return await this.config.transform(result);
     };
 
     if (this.loadCache) {
diff --git a/src/ExplorerBase.ts b/src/ExplorerBase.ts
index bebfa53..c3c6dab 100644
--- a/src/ExplorerBase.ts
+++ b/src/ExplorerBase.ts
@@ -1,14 +1,14 @@
 import path from 'path';
-import { loaders } from './loaders';
 import { getPropertyByPath } from './getPropertyByPath';
+import { Loader } from './index';
+import { loaders } from './loaders';
 import {
+  Cache,
   CosmiconfigResult,
   ExplorerOptions,
   ExplorerOptionsSync,
-  Cache,
   LoadedFileContent,
 } from './types';
-import { Loader } from './index';
 
 class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
   protected readonly loadCache?: Cache;
@@ -16,7 +16,7 @@ class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
   protected readonly config: T;
 
   public constructor(options: T) {
-    if (options.cache === true) {
+    if (options.cache) {
       this.loadCache = new Map();
       this.searchCache = new Map();
     }
@@ -68,8 +68,7 @@ class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
 
   protected shouldSearchStopWithResult(result: CosmiconfigResult): boolean {
     if (result === null) return false;
-    if (result.isEmpty && this.config.ignoreEmptySearchPlaces) return false;
-    return true;
+    return !(result.isEmpty && this.config.ignoreEmptySearchPlaces);
   }
 
   protected nextDirectoryToSearch(
@@ -97,8 +96,7 @@ class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
 
   protected getLoaderEntryForFile(filepath: string): Loader {
     if (path.basename(filepath) === 'package.json') {
-      const loader = this.loadPackageProp.bind(this);
-      return loader;
+      return this.loadPackageProp.bind(this);
     }
 
     const loaderKey = path.extname(filepath) || 'noExt';
@@ -117,6 +115,7 @@ class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
   protected loadedContentToCosmiconfigResult(
     filepath: string,
     loadedContent: LoadedFileContent,
+    forceProp: boolean,
   ): CosmiconfigResult {
     if (loadedContent === null) {
       return null;
@@ -124,6 +123,12 @@ class ExplorerBase<T extends ExplorerOptions | ExplorerOptionsSync> {
     if (loadedContent === undefined) {
       return { filepath, config: undefined, isEmpty: true };
     }
+    if (this.config.usePackagePropInConfigFiles || forceProp) {
+      loadedContent = getPropertyByPath(loadedContent, this.config.packageProp);
+    }
+    if (loadedContent === undefined) {
+      return { filepath, config: undefined, isEmpty: true };
+    }
     return { config: loadedContent, filepath };
   }
 
diff --git a/src/ExplorerSync.ts b/src/ExplorerSync.ts
index 418777c..bd656ea 100644
--- a/src/ExplorerSync.ts
+++ b/src/ExplorerSync.ts
@@ -1,8 +1,8 @@
 import path from 'path';
-import { ExplorerBase } from './ExplorerBase';
-import { readFileSync } from './readFile';
 import { cacheWrapperSync } from './cacheWrapper';
+import { ExplorerBase } from './ExplorerBase';
 import { getDirectorySync } from './getDirectory';
+import { readFileSync } from './readFile';
 import {
   CosmiconfigResult,
   ExplorerOptionsSync,
@@ -15,10 +15,13 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
   }
 
   public searchSync(searchFrom: string = process.cwd()): CosmiconfigResult {
-    const startDirectory = getDirectorySync(searchFrom);
-    const result = this.searchFromDirectorySync(startDirectory);
-
-    return result;
+    if (this.config.metaConfigFilePath) {
+      const config = this._loadFileSync(this.config.metaConfigFilePath, true);
+      if (config && !config.isEmpty) {
+        return config;
+      }
+    }
+    return this.searchFromDirectorySync(getDirectorySync(searchFrom));
   }
 
   private searchFromDirectorySync(dir: string): CosmiconfigResult {
@@ -32,9 +35,7 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
         return this.searchFromDirectorySync(nextDir);
       }
 
-      const transformResult = this.config.transform(result);
-
-      return transformResult;
+      return this.config.transform(result);
     };
 
     if (this.searchCache) {
@@ -48,7 +49,7 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
     for (const place of this.config.searchPlaces) {
       const placeResult = this.loadSearchPlaceSync(dir, place);
 
-      if (this.shouldSearchStopWithResult(placeResult) === true) {
+      if (this.shouldSearchStopWithResult(placeResult)) {
         return placeResult;
       }
     }
@@ -61,9 +62,7 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
     const filepath = path.join(dir, place);
     const content = readFileSync(filepath);
 
-    const result = this.createCosmiconfigResultSync(filepath, content);
-
-    return result;
+    return this.createCosmiconfigResultSync(filepath, content, false);
   }
 
   private loadFileContentSync(
@@ -77,22 +76,36 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
       return undefined;
     }
     const loader = this.getLoaderEntryForFile(filepath);
-    const loaderResult = loader(filepath, content);
-
-    return loaderResult;
+    try {
+      return loader(filepath, content);
+    } catch (e: any) {
+      e.filepath = filepath;
+      throw e;
+    }
   }
 
   private createCosmiconfigResultSync(
     filepath: string,
     content: string | null,
+    forceProp: boolean,
   ): CosmiconfigResult {
     const fileContent = this.loadFileContentSync(filepath, content);
-    const result = this.loadedContentToCosmiconfigResult(filepath, fileContent);
 
-    return result;
+    return this.loadedContentToCosmiconfigResult(
+      filepath,
+      fileContent,
+      forceProp,
+    );
   }
 
   public loadSync(filepath: string): CosmiconfigResult {
+    return this._loadFileSync(filepath, false);
+  }
+
+  private _loadFileSync(
+    filepath: string,
+    forceProp: boolean,
+  ): CosmiconfigResult {
     this.validateFilePath(filepath);
     const absoluteFilePath = path.resolve(process.cwd(), filepath);
 
@@ -101,11 +114,10 @@ class ExplorerSync extends ExplorerBase<ExplorerOptionsSync> {
       const cosmiconfigResult = this.createCosmiconfigResultSync(
         absoluteFilePath,
         content,
+        forceProp,
       );
 
-      const transformResult = this.config.transform(cosmiconfigResult);
-
-      return transformResult;
+      return this.config.transform(cosmiconfigResult);
     };
 
     if (this.loadCache) {
diff --git a/src/index.ts b/src/index.ts
index cb8eb68..761d48b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -44,9 +44,107 @@ export interface OptionsSync extends OptionsBase {
   transform?: TransformSync;
 }
 
-// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
-function cosmiconfig(moduleName: string, options: Options = {}) {
-  const normalizedOptions: ExplorerOptions = normalizeOptions(
+export interface PublicExplorerBase {
+  clearLoadCache: () => void;
+  clearSearchCache: () => void;
+  clearCaches: () => void;
+}
+
+export interface PublicExplorer extends PublicExplorerBase {
+  search: (searchFrom?: string) => Promise<CosmiconfigResult>;
+  load: (filepath: string) => Promise<CosmiconfigResult>;
+}
+
+export interface PublicExplorerSync extends PublicExplorerBase {
+  search: (searchFrom?: string) => CosmiconfigResult;
+  load: (filepath: string) => CosmiconfigResult;
+}
+
+// this needs to be hardcoded, as this is intended for end users, who can't supply options at this point
+export const metaSearchPlaces = [
+  'package.json',
+  '.config.json',
+  '.config.yaml',
+  '.config.yml',
+  '.config.js',
+  '.config.cjs',
+];
+
+// do not allow mutation of default loaders. Make sure it is set inside options
+const defaultLoaders = Object.freeze({
+  '.cjs': loaders.loadJs,
+  '.js': loaders.loadJs,
+  '.json': loaders.loadJson,
+  '.yaml': loaders.loadYaml,
+  '.yml': loaders.loadYaml,
+  noExt: loaders.loadYaml,
+} as const);
+
+const identity: TransformSync = function identity(x) {
+  return x;
+};
+
+function replaceMetaPlaceholders(
+  paths: Array<string>,
+  moduleName: string,
+): Array<string> {
+  return paths.map((path) => path.replace('{name}', moduleName));
+}
+
+function getExplorerOptions(
+  moduleName: string,
+  options: OptionsSync,
+): ExplorerOptionsSync;
+function getExplorerOptions(
+  moduleName: string,
+  options: Options,
+): ExplorerOptions;
+function getExplorerOptions(
+  moduleName: string,
+  options: Options | OptionsSync,
+): ExplorerOptions | ExplorerOptionsSync {
+  const metaExplorer = new ExplorerSync({
+    packageProp: 'cosmiconfig',
+    stopDir: process.cwd(),
+    searchPlaces: metaSearchPlaces,
+    ignoreEmptySearchPlaces: false,
+    usePackagePropInConfigFiles: true,
+    loaders: defaultLoaders,
+    transform: identity,
+    cache: true,
+    metaConfigFilePath: null,
+  });
+  const metaConfig = metaExplorer.searchSync();
+
+  if (!metaConfig) {
+    return normalizeOptions(moduleName, options);
+  }
+
+  if (metaConfig.config?.loaders) {
+    throw new Error('Can not specify loaders in meta config file');
+  }
+
+  const overrideOptions = metaConfig.config ?? {};
+
+  if (overrideOptions.searchPlaces) {
+    overrideOptions.searchPlaces = replaceMetaPlaceholders(
+      overrideOptions.searchPlaces,
+      moduleName,
+    );
+  }
+
+  overrideOptions.metaConfigFilePath = metaConfig.filepath;
+
+  const mergedOptions = { ...options, ...overrideOptions };
+
+  return normalizeOptions(moduleName, mergedOptions);
+}
+
+function cosmiconfig(
+  moduleName: string,
+  options: Options = {},
+): PublicExplorer {
+  const normalizedOptions: ExplorerOptions = getExplorerOptions(
     moduleName,
     options,
   );
@@ -59,12 +157,15 @@ function cosmiconfig(moduleName: string, options: Options = {}) {
     clearLoadCache: explorer.clearLoadCache.bind(explorer),
     clearSearchCache: explorer.clearSearchCache.bind(explorer),
     clearCaches: explorer.clearCaches.bind(explorer),
-  } as const;
+  };
 }
 
 // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
-function cosmiconfigSync(moduleName: string, options: OptionsSync = {}) {
-  const normalizedOptions: ExplorerOptionsSync = normalizeOptions(
+function cosmiconfigSync(
+  moduleName: string,
+  options: OptionsSync = {},
+): PublicExplorerSync {
+  const normalizedOptions: ExplorerOptionsSync = getExplorerOptions(
     moduleName,
     options,
   );
@@ -77,23 +178,9 @@ function cosmiconfigSync(moduleName: string, options: OptionsSync = {}) {
     clearLoadCache: explorerSync.clearLoadCache.bind(explorerSync),
     clearSearchCache: explorerSync.clearSearchCache.bind(explorerSync),
     clearCaches: explorerSync.clearCaches.bind(explorerSync),
-  } as const;
+  };
 }
 
-// do not allow mutation of default loaders. Make sure it is set inside options
-const defaultLoaders = Object.freeze({
-  '.cjs': loaders.loadJs,
-  '.js': loaders.loadJs,
-  '.json': loaders.loadJson,
-  '.yaml': loaders.loadYaml,
-  '.yml': loaders.loadYaml,
-  noExt: loaders.loadYaml,
-} as const);
-
-const identity: TransformSync = function identity(x) {
-  return x;
-};
-
 function normalizeOptions(
   moduleName: string,
   options: OptionsSync,
@@ -130,18 +217,22 @@ function normalizeOptions(
     cache: true,
     transform: identity,
     loaders: defaultLoaders,
+    metaConfigFilePath: null,
+  };
+
+  let loaders = {
+    ...defaults.loaders,
   };
 
-  const normalizedOptions: ExplorerOptions | ExplorerOptionsSync = {
+  if (options.loaders) {
+    Object.assign(loaders, options.loaders);
+  }
+
+  return {
     ...defaults,
     ...options,
-    loaders: {
-      ...defaults.loaders,
-      ...options.loaders,
-    },
+    loaders,
   };
-
-  return normalizedOptions;
 }
 
 export { cosmiconfig, cosmiconfigSync, defaultLoaders };
diff --git a/src/loaders.ts b/src/loaders.ts
index 43f4018..07951a3 100644
--- a/src/loaders.ts
+++ b/src/loaders.ts
@@ -1,12 +1,9 @@
 /* eslint-disable @typescript-eslint/no-require-imports */
 
-import parseJsonType from 'parse-json';
-import yamlType from 'yaml';
-import importFreshType from 'import-fresh';
 import { LoaderSync } from './index';
 import { LoadersSync } from './types';
 
-let importFresh: typeof importFreshType;
+let importFresh: typeof import('import-fresh');
 const loadJs: LoaderSync = function loadJs(filepath) {
   if (importFresh === undefined) {
     importFresh = require('import-fresh');
@@ -16,7 +13,7 @@ const loadJs: LoaderSync = function loadJs(filepath) {
   return result;
 };
 
-let parseJson: typeof parseJsonType;
+let parseJson: typeof import('parse-json');
 const loadJson: LoaderSync = function loadJson(filepath, content) {
   if (parseJson === undefined) {
     parseJson = require('parse-json');
@@ -25,22 +22,22 @@ const loadJson: LoaderSync = function loadJson(filepath, content) {
   try {
     const result = parseJson(content);
     return result;
-  } catch (error) {
+  } catch (error: any) {
     error.message = `JSON Error in ${filepath}:\n${error.message}`;
     throw error;
   }
 };
 
-let yaml: typeof yamlType;
+let yaml: typeof import('js-yaml');
 const loadYaml: LoaderSync = function loadYaml(filepath, content) {
   if (yaml === undefined) {
-    yaml = require('yaml');
+    yaml = require('js-yaml');
   }
 
   try {
-    const result = yaml.parse(content, { prettyErrors: true });
+    const result = yaml.load(content);
     return result;
-  } catch (error) {
+  } catch (error: any) {
     error.message = `YAML Error in ${filepath}:\n${error.message}`;
     throw error;
   }
diff --git a/src/readFile.ts b/src/readFile.ts
index 636766e..7b8c6bc 100644
--- a/src/readFile.ts
+++ b/src/readFile.ts
@@ -30,7 +30,7 @@ async function readFile(
     const content = await fsReadFileAsync(filepath, 'utf8');
 
     return content;
-  } catch (error) {
+  } catch (error: any) {
     if (
       throwNotFound === false &&
       (error.code === 'ENOENT' || error.code === 'EISDIR')
@@ -49,7 +49,7 @@ function readFileSync(filepath: string, options: Options = {}): string | null {
     const content = fs.readFileSync(filepath, 'utf8');
 
     return content;
-  } catch (error) {
+  } catch (error: any) {
     if (
       throwNotFound === false &&
       (error.code === 'ENOENT' || error.code === 'EISDIR')
diff --git a/src/types.ts b/src/types.ts
index 70507df..b9abcb9 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -9,11 +9,16 @@ export type CosmiconfigResult = {
   isEmpty?: boolean;
 } | null;
 
-// These are the user options with defaults applied.
-/* eslint-disable @typescript-eslint/no-empty-interface */
-export interface ExplorerOptions extends Required<Options> {}
-export interface ExplorerOptionsSync extends Required<OptionsSync> {}
-/* eslint-enable @typescript-eslint/no-empty-interface */
+export interface InternalOptions {
+  usePackagePropInConfigFiles?: boolean;
+  metaConfigFilePath: string | null;
+}
+
+// These are the user options with defaults applied, plus internal options possibly inferred from meta config
+export interface ExplorerOptions extends Required<Options>, InternalOptions {}
+export interface ExplorerOptionsSync
+  extends Required<OptionsSync>,
+    InternalOptions {}
 
 export type Cache = Map<string, CosmiconfigResult>;
 
diff --git a/test/caches.test.ts b/test/caches.test.ts
index 8b54186..c607a69 100644
--- a/test/caches.test.ts
+++ b/test/caches.test.ts
@@ -1,6 +1,15 @@
+import {
+  beforeEach,
+  afterEach,
+  afterAll,
+  describe,
+  expect,
+  test,
+  vi,
+} from 'vitest';
 import fs from 'fs';
-import { TempDir } from './util';
 import { cosmiconfig, cosmiconfigSync } from '../src';
+import { TempDir } from './util';
 
 const temp = new TempDir();
 
@@ -13,7 +22,7 @@ beforeEach(() => {
 });
 
 afterEach(() => {
-  jest.restoreAllMocks();
+  vi.restoreAllMocks();
 });
 
 afterAll(() => {
@@ -52,15 +61,17 @@ describe('cache is not used initially', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
-    const cachedSearch = cosmiconfig('foo').search;
+    const explorer = cosmiconfig('foo');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
+    const cachedSearch = explorer.search;
     const result = await cachedSearch(searchPath);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-    const cachedSearchSync = cosmiconfigSync('foo').search;
+    const explorer = cosmiconfigSync('foo');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
+    const cachedSearchSync = explorer.search;
     const result = cachedSearchSync(searchPath);
     checkResult(readFileSpy, result);
   });
@@ -78,7 +89,7 @@ describe('cache is used for already-visited directories', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const cachedSearch = cosmiconfig('foo').search;
     // First pass, prime the cache ...
     await cachedSearch(searchPath);
@@ -90,7 +101,7 @@ describe('cache is used for already-visited directories', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const cachedSearchSync = cosmiconfigSync('foo').search;
     // First pass, prime the cache ...
     cachedSearchSync(searchPath);
@@ -114,7 +125,7 @@ describe('cache is used for already-loaded file', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const cachedLoad = cosmiconfig('foo').load;
     // First pass, prime the cache ...
     await cachedLoad(loadPath);
@@ -126,7 +137,7 @@ describe('cache is used for already-loaded file', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const cachedLoadSync = cosmiconfigSync('foo').load;
     // First pass, prime the cache ...
     cachedLoadSync(loadPath);
@@ -168,7 +179,7 @@ describe('cache is used when some directories in search are already visted', ()
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const cachedSearch = cosmiconfig('foo').search;
     // First pass, prime the cache ...
     await cachedSearch(firstSearchPath);
@@ -180,7 +191,7 @@ describe('cache is used when some directories in search are already visted', ()
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const cachedSearchSync = cosmiconfigSync('foo').search;
     // First pass, prime the cache ...
     cachedSearchSync(firstSearchPath);
@@ -205,7 +216,7 @@ describe('cache is not used when directly loading an unvisited file', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo');
     // First pass, prime the cache ...
     await explorer.search(firstSearchPath);
@@ -217,7 +228,7 @@ describe('cache is not used when directly loading an unvisited file', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo');
     // First pass, prime the cache ...
     explorer.search(firstSearchPath);
@@ -260,24 +271,22 @@ describe('cache is not used in a new cosmiconfig instance', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
     // First pass, prime the cache ...
     await cosmiconfig('foo').search(searchPath);
-    // Reset readFile mocks and search again.
-    readFileSpy.mockClear();
-
-    const result = await cosmiconfig('foo').search(searchPath);
+    // Search again.
+    const explorer = cosmiconfig('foo');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
+    const result = await explorer.search(searchPath);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
     // First pass, prime the cache ...
     cosmiconfigSync('foo').search(searchPath);
-    // Reset readFile mocks and search again.
-    readFileSpy.mockClear();
-
-    const result = cosmiconfigSync('foo').search(searchPath);
+    // Search again.
+    const explorer = cosmiconfigSync('foo');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
+    const result = explorer.search(searchPath);
     checkResult(readFileSpy, result);
   });
 });
@@ -295,7 +304,7 @@ describe('clears file cache on calling clearLoadCache', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo');
     await explorer.load(loadPath);
     readFileSpy.mockClear();
@@ -306,7 +315,7 @@ describe('clears file cache on calling clearLoadCache', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo');
     explorer.load(loadPath);
     // Reset readFile mocks and search again.
@@ -331,7 +340,7 @@ describe('clears file cache on calling clearCaches', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo');
     await explorer.load(loadPath);
     readFileSpy.mockClear();
@@ -342,7 +351,7 @@ describe('clears file cache on calling clearCaches', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo');
     explorer.load(loadPath);
     // Reset readFile mocks and search again.
@@ -384,7 +393,7 @@ describe('clears directory cache on calling clearSearchCache', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo');
     await explorer.search(searchPath);
     readFileSpy.mockClear();
@@ -395,7 +404,7 @@ describe('clears directory cache on calling clearSearchCache', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo');
     explorer.search(searchPath);
     // Reset readFile mocks and search again.
@@ -438,7 +447,7 @@ describe('clears directory cache on calling clearCaches', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo');
     await explorer.search(searchPath);
     readFileSpy.mockClear();
@@ -449,7 +458,7 @@ describe('clears directory cache on calling clearCaches', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo');
     explorer.search(searchPath);
     // Reset readFile mocks and search again.
@@ -508,7 +517,7 @@ describe('with cache disabled, does not cache directory results', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo', { cache: false });
     await explorer.search(searchPath);
     readFileSpy.mockClear();
@@ -518,7 +527,7 @@ describe('with cache disabled, does not cache directory results', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo', { cache: false });
     explorer.search(searchPath);
     readFileSpy.mockClear();
@@ -541,7 +550,7 @@ describe('with cache disabled, does not cache file results', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const readFileSpy = vi.spyOn(fs, 'readFile');
     const explorer = cosmiconfig('foo', { cache: false });
     await explorer.load(loadPath);
     readFileSpy.mockClear();
@@ -551,7 +560,7 @@ describe('with cache disabled, does not cache file results', () => {
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
     const explorer = cosmiconfigSync('foo', { cache: false });
     explorer.load(loadPath);
     readFileSpy.mockClear();
diff --git a/test/failed-directories.test.ts b/test/failed-directories.test.ts
index d5786c0..afc27e1 100644
--- a/test/failed-directories.test.ts
+++ b/test/failed-directories.test.ts
@@ -1,3 +1,4 @@
+import { beforeEach, afterAll, describe, expect, test, vi } from 'vitest';
 import fs from 'fs';
 import { TempDir } from './util';
 import {
@@ -76,22 +77,26 @@ describe('gives up if it cannot find the file', () => {
       'foo.config.cjs',
     ]);
 
-    expect(result).toBe(null);
+    expect(result).toBeNull();
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
-    const statSpy = jest.spyOn(fs, 'stat');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(fs, 'readFile');
+    const statSpy = vi.spyOn(fs, 'stat');
+
+    const result = await explorer.search(startDir);
     checkResult(statSpy, readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-    const statSpy = jest.spyOn(fs, 'statSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
+    const statSpy = vi.spyOn(fs, 'statSync');
+
+    const result = explorer.search(startDir);
     checkResult(statSpy, readFileSpy, result);
   });
 });
@@ -135,20 +140,22 @@ describe('stops at stopDir and gives up', () => {
       'a/foo.config.cjs',
     ]);
 
-    expect(result).toBe(null);
+    expect(result).toBeNull();
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(fs, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(fs, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -163,7 +170,7 @@ describe('throws error for invalid YAML in rc file', () => {
 
   const expectedError = `YAML Error in ${temp.absolutePath(
     'a/b/.foorc',
-  )}:\nNested mappings are not allowed in compact mappings at line 1, column 8:`;
+  )}:\nbad indentation of a mapping entry (1:12)`;
 
   test('async', async () => {
     await expect(
@@ -290,7 +297,7 @@ describe('throws error for invalid YAML in .foorc.yml', () => {
 
   const expectedError = `YAML Error in ${temp.absolutePath(
     'a/b/c/d/e/f/.foorc.yml',
-  )}:\nNested mappings are not allowed in compact mappings at line 1, column 8:`;
+  )}:\nbad indentation of a mapping entry (1:13)`;
 
   test('async', async () => {
     await expect(
diff --git a/test/failed-files.test.ts b/test/failed-files.test.ts
index 62c6ae6..910a0df 100644
--- a/test/failed-files.test.ts
+++ b/test/failed-files.test.ts
@@ -1,3 +1,4 @@
+import { beforeEach, afterAll, describe, test, expect } from 'vitest';
 import { TempDir } from './util';
 import { cosmiconfig, cosmiconfigSync } from '../src';
 
@@ -56,7 +57,7 @@ describe('throws error if defined YAML file has syntax error', () => {
   });
 
   const file = temp.absolutePath('foo-invalid.yaml');
-  const expectedError = `YAML Error in ${file}:\nNested mappings are not allowed in compact mappings at line 1, column 6:`;
+  const expectedError = `YAML Error in ${file}:\nbad indentation of a mapping entry (1:10)`;
 
   test('async', async () => {
     await expect(cosmiconfig('failed-files-tests').load(file)).rejects.toThrow(
diff --git a/test/getDirectory.test.ts b/test/getDirectory.test.ts
index 218bf8b..dd6d0cc 100644
--- a/test/getDirectory.test.ts
+++ b/test/getDirectory.test.ts
@@ -1,3 +1,4 @@
+import { describe, expect, test } from 'vitest';
 import path from 'path';
 import { getDirectory, getDirectorySync } from '../src/getDirectory';
 
@@ -35,7 +36,7 @@ describe('returns the parent directory if it is a file', () => {
   });
 });
 
-// https://github.com/davidtheclark/cosmiconfig/issues/63
+// https://github.com/cosmiconfig/cosmiconfig/issues/63
 describe('handles process.cwd()/stdin', () => {
   const subject = path.join(process.cwd(), 'stdin');
   const checkResult = (result: string) => {
diff --git a/test/getPropertyByPath.test.ts b/test/getPropertyByPath.test.ts
index 66860da..1c0c85b 100644
--- a/test/getPropertyByPath.test.ts
+++ b/test/getPropertyByPath.test.ts
@@ -1,3 +1,4 @@
+import { describe, test, expect } from 'vitest';
 import { getPropertyByPath } from '../src/getPropertyByPath';
 
 const source = {
diff --git a/test/index.test.ts b/test/index.test.ts
index d1ec894..03b0474 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -1,71 +1,107 @@
-/* eslint-disable @typescript-eslint/no-extraneous-class,@typescript-eslint/explicit-member-accessibility,@typescript-eslint/no-empty-function */
+/* eslint-disable @typescript-eslint/no-extraneous-class,@typescript-eslint/explicit-member-accessibility,@typescript-eslint/no-empty-function*/
+import path from 'path';
+import {
+  expect,
+  describe,
+  beforeEach,
+  afterAll,
+  test,
+  vi,
+  SpyInstance,
+  Mock,
+  afterEach,
+} from 'vitest';
 import os from 'os';
+import { defaultLoaders, LoaderSync } from '../src';
+import { ExplorerOptions, ExplorerOptionsSync, Loaders } from '../src/types';
 import { TempDir } from './util';
-import {
-  cosmiconfig as cosmiconfigModule,
-  cosmiconfigSync as cosmiconfigSyncModule,
-  defaultLoaders,
-  LoaderSync,
-} from '../src';
-import { Loaders } from '../src/types';
+import { ExplorerSync } from '../src/ExplorerSync';
+import { Explorer } from '../src/Explorer';
+
+vi.mock('../src/ExplorerSync', async () => {
+  const { ExplorerSync } = await vi.importActual<
+    typeof import('../src/ExplorerSync')
+  >('../src/ExplorerSync');
+
+  const mock = vi.fn();
+
+  return {
+    ExplorerSync: class FakeExplorerSync extends ExplorerSync {
+      static mock = mock;
+      constructor(options: ExplorerOptionsSync) {
+        mock(options);
+        super(options);
+      }
+    },
+  };
+});
+
+vi.mock('../src/Explorer', async () => {
+  const { Explorer } = await vi.importActual<typeof import('../src/Explorer')>(
+    '../src/Explorer',
+  );
+
+  const mock = vi.fn();
+
+  return {
+    Explorer: class FakeExplorer extends Explorer {
+      static mock = mock;
+      constructor(options: ExplorerOptions) {
+        mock(options);
+        super(options);
+      }
+    },
+  };
+});
+
+const { cosmiconfig, cosmiconfigSync } = await import('../src/index');
+const createExplorerSyncMock = (ExplorerSync as any).mock;
+const createExplorerMock = (Explorer as any).mock;
 
 const temp = new TempDir();
 
 function getLoaderFunctionsByName(loaders: Loaders) {
-  const loaderFunctionsByName = Object.entries(loaders).reduce(
-    (acc, [extension, loader]) => {
-      return {
-        ...acc,
-        [extension]: loader.name,
-      };
-    },
-    {},
+  return Object.fromEntries(
+    Object.entries(loaders).map(([extension, loader]) => [
+      extension,
+      loader.name,
+    ]),
   );
-
-  return loaderFunctionsByName;
 }
 
-let cosmiconfig: typeof cosmiconfigModule;
-let cosmiconfigSync: typeof cosmiconfigSyncModule;
-let createExplorerMock: jest.SpyInstance & typeof cosmiconfigModule;
-let createExplorerSyncMock: jest.SpyInstance & typeof cosmiconfigSyncModule;
+const checkConfigResult = (
+  mock: SpyInstance,
+  instanceNum: number,
+  expectedLoaderNames: Record<string, string>,
+  expectedExplorerOptions: Omit<
+    ExplorerOptions & ExplorerOptionsSync,
+    'transform' | 'loaders'
+  >,
+) => {
+  const instanceIndex = instanceNum - 1;
+
+  expect(mock.mock.calls.length).toEqual(instanceNum);
+  expect(mock.mock.calls[instanceIndex].length).toBe(1);
+
+  const { transform, loaders, ...explorerOptions } =
+    mock.mock.calls[instanceIndex][0];
+  expect(transform.name).toMatch(/identity/);
+  const loaderFunctionsByName = getLoaderFunctionsByName(loaders);
+
+  // Vitest adds a number suffix to our functions names,
+  // so we can't compare them with strict equals
+  for (const [key, value] of Object.entries(loaderFunctionsByName)) {
+    expect(value).toContain(expectedLoaderNames[key]);
+  }
+
+  expect(explorerOptions).toEqual(expectedExplorerOptions);
+};
+
 describe('cosmiconfig', () => {
   const moduleName = 'foo';
 
-  beforeEach(() => {
+  beforeEach(async () => {
     temp.clean();
-
-    createExplorerMock = jest.fn();
-    jest.doMock('../src/Explorer', () => {
-      return {
-        Explorer: class FakeExplorer {
-          constructor(options: Parameters<typeof cosmiconfigModule>[0]) {
-            createExplorerMock(options);
-            const { Explorer } = jest.requireActual('../src/Explorer');
-
-            return new Explorer(options);
-          }
-        },
-      };
-    });
-
-    createExplorerSyncMock = jest.fn();
-    jest.doMock('../src/ExplorerSync', () => {
-      return {
-        ExplorerSync: class FakeExplorerSync {
-          constructor(options: Parameters<typeof cosmiconfigSyncModule>[0]) {
-            createExplorerSyncMock(options);
-            const { ExplorerSync } = jest.requireActual('../src/ExplorerSync');
-
-            return new ExplorerSync(options);
-          }
-        },
-      };
-    });
-
-    const index = require('../src/index');
-    cosmiconfig = index.cosmiconfig;
-    cosmiconfigSync = index.cosmiconfigSync;
   });
 
   afterAll(() => {
@@ -74,60 +110,63 @@ describe('cosmiconfig', () => {
   });
 
   describe('creates explorer with default options if not specified', () => {
-    const checkResult = (mock: jest.SpyInstance) => {
-      expect(mock.mock.calls.length).toEqual(1);
-      expect(mock.mock.calls[0].length).toEqual(1);
-
-      const { transform, loaders, ...explorerOptions } = mock.mock.calls[0][0];
-      expect(transform.name).toBe('identity');
-      const loaderFunctionsByName = getLoaderFunctionsByName(loaders);
-      expect(loaderFunctionsByName).toEqual({
-        '.cjs': 'loadJs',
-        '.js': 'loadJs',
-        '.json': 'loadJson',
-        '.yaml': 'loadYaml',
-        '.yml': 'loadYaml',
-        noExt: 'loadYaml',
-      });
-
-      expect(explorerOptions).toEqual({
-        packageProp: moduleName,
-        searchPlaces: [
-          'package.json',
-          `.${moduleName}rc`,
-          `.${moduleName}rc.json`,
-          `.${moduleName}rc.yaml`,
-          `.${moduleName}rc.yml`,
-          `.${moduleName}rc.js`,
-          `.${moduleName}rc.cjs`,
-          `.config/${moduleName}rc`,
-          `.config/${moduleName}rc.json`,
-          `.config/${moduleName}rc.yaml`,
-          `.config/${moduleName}rc.yml`,
-          `.config/${moduleName}rc.js`,
-          `.config/${moduleName}rc.cjs`,
-          `${moduleName}.config.js`,
-          `${moduleName}.config.cjs`,
-        ],
-        ignoreEmptySearchPlaces: true,
-        stopDir: os.homedir(),
-        cache: true,
-      });
+    const expectedLoaderNames = {
+      '.cjs': 'loadJs',
+      '.js': 'loadJs',
+      '.json': 'loadJson',
+      '.yaml': 'loadYaml',
+      '.yml': 'loadYaml',
+      noExt: 'loadYaml',
+    };
+
+    const expectedExplorerOptions = {
+      packageProp: moduleName,
+      searchPlaces: [
+        'package.json',
+        `.${moduleName}rc`,
+        `.${moduleName}rc.json`,
+        `.${moduleName}rc.yaml`,
+        `.${moduleName}rc.yml`,
+        `.${moduleName}rc.js`,
+        `.${moduleName}rc.cjs`,
+        `.config/${moduleName}rc`,
+        `.config/${moduleName}rc.json`,
+        `.config/${moduleName}rc.yaml`,
+        `.config/${moduleName}rc.yml`,
+        `.config/${moduleName}rc.js`,
+        `.config/${moduleName}rc.cjs`,
+        `${moduleName}.config.js`,
+        `${moduleName}.config.cjs`,
+      ],
+      ignoreEmptySearchPlaces: true,
+      stopDir: os.homedir(),
+      cache: true,
+      metaConfigFilePath: null,
     };
 
     test('async', () => {
       cosmiconfig(moduleName);
-      checkResult(createExplorerMock);
+      checkConfigResult(
+        createExplorerMock,
+        1,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
     });
 
     test('sync', () => {
       cosmiconfigSync(moduleName);
-      checkResult(createExplorerSyncMock);
+      checkConfigResult(
+        createExplorerSyncMock,
+        2,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
     });
   });
 
   describe('defaults transform to sync identity function', () => {
-    const checkResult = (mock: jest.SpyInstance) => {
+    const checkResult = (mock: Mock) => {
       const explorerOptions = mock.mock.calls[0][0];
       const x = {};
       // @ts-ignore
@@ -147,10 +186,77 @@ describe('cosmiconfig', () => {
   });
 
   describe('creates explorer with preference for given options over defaults', () => {
+    const noExtLoader: LoaderSync = () => {};
+    const jsLoader: LoaderSync = () => {};
+    const jsonLoader: LoaderSync = () => {};
+    const yamlLoader: LoaderSync = () => {};
+
+    const options = {
+      stopDir: __dirname,
+      cache: false,
+      searchPlaces: ['.foorc.json', 'wildandfree.js'],
+      packageProp: 'wildandfree',
+      ignoreEmptySearchPlaces: false,
+      loaders: {
+        noExt: noExtLoader,
+        '.cjs': jsLoader,
+        '.js': jsLoader,
+        '.json': jsonLoader,
+        '.yaml': yamlLoader,
+      },
+    };
+
+    const expectedLoaderNames = {
+      '.cjs': 'jsLoader',
+      '.js': 'jsLoader',
+      '.json': 'jsonLoader',
+      '.yaml': 'yamlLoader',
+      '.yml': 'loadYaml',
+      noExt: 'noExtLoader',
+    };
+
+    const expectedExplorerOptions = {
+      packageProp: 'wildandfree',
+      searchPlaces: ['.foorc.json', 'wildandfree.js'],
+      ignoreEmptySearchPlaces: false,
+      stopDir: __dirname,
+      cache: false,
+      metaConfigFilePath: null,
+    };
+
+    test('async', () => {
+      cosmiconfig(moduleName, options);
+      checkConfigResult(
+        createExplorerMock,
+        1,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
+    });
+
+    test('sync', () => {
+      cosmiconfigSync(moduleName, options);
+      checkConfigResult(
+        createExplorerSyncMock,
+        2,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
+    });
+  });
+
+  describe('creates explorer with preference of user options over consumer options', () => {
+    const currentDir = process.cwd();
     beforeEach(() => {
-      temp.createFile('foo.json', '{ "foo": true }');
+      temp.createFile(
+        '.config.json',
+        '{"cosmiconfig": {"searchPlaces": [".config/{name}.json"]}}',
+      );
+      process.chdir(temp.dir);
     });
 
+    afterEach(() => process.chdir(currentDir));
+
     const noExtLoader: LoaderSync = () => {};
     const jsLoader: LoaderSync = () => {};
     const jsonLoader: LoaderSync = () => {};
@@ -171,39 +277,42 @@ describe('cosmiconfig', () => {
       },
     };
 
-    const checkResult = (mock: jest.SpyInstance) => {
-      expect(mock.mock.calls.length).toEqual(1);
-      expect(mock.mock.calls[0].length).toEqual(1);
-      const { transform, loaders, ...explorerOptions } = mock.mock.calls[0][0];
-
-      expect(transform.name).toBe('identity');
-      const loaderFunctionsByName = getLoaderFunctionsByName(loaders);
-      expect(loaderFunctionsByName).toEqual({
-        '.cjs': 'jsLoader',
-        '.js': 'jsLoader',
-        '.json': 'jsonLoader',
-        '.yaml': 'yamlLoader',
-        '.yml': 'loadYaml',
-        noExt: 'noExtLoader',
-      });
-
-      expect(explorerOptions).toEqual({
-        packageProp: 'wildandfree',
-        searchPlaces: ['.foorc.json', 'wildandfree.js'],
-        ignoreEmptySearchPlaces: false,
-        stopDir: __dirname,
-        cache: false,
-      });
+    const expectedLoaderNames = {
+      '.cjs': 'jsLoader',
+      '.js': 'jsLoader',
+      '.json': 'jsonLoader',
+      '.yaml': 'yamlLoader',
+      '.yml': 'loadYaml',
+      noExt: 'noExtLoader',
+    };
+
+    const expectedExplorerOptions = {
+      packageProp: 'wildandfree',
+      searchPlaces: ['.config/foo.json'],
+      ignoreEmptySearchPlaces: false,
+      stopDir: __dirname,
+      cache: false,
+      metaConfigFilePath: path.join(temp.dir, '.config.json'),
     };
 
     test('async', () => {
       cosmiconfig(moduleName, options);
-      checkResult(createExplorerMock);
+      checkConfigResult(
+        createExplorerMock,
+        1,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
     });
 
     test('sync', () => {
       cosmiconfigSync(moduleName, options);
-      checkResult(createExplorerSyncMock);
+      checkConfigResult(
+        createExplorerSyncMock,
+        2,
+        expectedLoaderNames,
+        expectedExplorerOptions,
+      );
     });
   });
 
@@ -225,6 +334,7 @@ describe('cosmiconfig', () => {
 
     const expectedError =
       'loader for extension ".things" is not a function (type provided: "number"), so searchPlaces item ".foorc.things" is invalid';
+
     test('async', () => {
       expect(() =>
         // @ts-ignore
diff --git a/test/meta-config.test.ts b/test/meta-config.test.ts
new file mode 100644
index 0000000..6daf8eb
--- /dev/null
+++ b/test/meta-config.test.ts
@@ -0,0 +1,208 @@
+import {
+  describe,
+  beforeEach,
+  afterAll,
+  test,
+  expect,
+  afterEach,
+  vi,
+  SpyInstance,
+} from 'vitest';
+import fs from 'fs';
+import { cosmiconfig, cosmiconfigSync, Options, OptionsSync } from '../src';
+import { TempDir } from './util';
+
+describe('cosmiconfig meta config', () => {
+  const temp = new TempDir();
+
+  beforeEach(() => {
+    temp.clean();
+  });
+
+  afterAll(() => {
+    // Remove temp.dir created for tests
+    temp.deleteTempDir();
+  });
+
+  test('throws when trying to supply loaders', () => {
+    temp.createFile('.config.yml', 'cosmiconfig:\n  loaders: []');
+
+    const currentDir = process.cwd();
+    process.chdir(temp.dir);
+
+    expect(() => cosmiconfigSync('foo')).toThrow();
+
+    process.chdir(currentDir);
+  });
+
+  describe('uses user-configured searchPlaces without placeholders', () => {
+    const currentDir = process.cwd();
+
+    beforeEach(() => {
+      temp.createDir('sub');
+      temp.createFile('.foorc', 'a: b');
+      temp.createFile('.foo-config', 'a: c');
+
+      process.chdir(temp.dir);
+    });
+
+    afterEach(() => {
+      process.chdir(currentDir);
+    });
+
+    async function runTest() {
+      const file = temp.absolutePath('.foo-config');
+      const startDir = temp.absolutePath('sub');
+      const explorerOptions = { stopDir: temp.absolutePath('.') };
+
+      const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
+      const explorer = cosmiconfig('foo', explorerOptions);
+      expect(
+        temp
+          .getSpyPathCalls(readFileSyncSpy)
+          .filter((path) => !path.includes('/node_modules/')),
+      ).toEqual([
+        'package.json',
+        '.config.json',
+        '.config.yaml',
+        '.config.yml',
+      ]);
+      readFileSyncSpy.mockClear();
+
+      const readFileSpy = vi.spyOn(fs, 'readFile');
+      const result = await explorer.search(startDir);
+      expect(temp.getSpyPathCalls(readFileSpy)).toEqual([
+        '.config.yml',
+        'sub/.foo-config',
+        'sub/.foo.config.yml',
+        '.foo-config',
+      ]);
+
+      expect(result).toEqual({
+        config: { a: 'c' },
+        filepath: file,
+      });
+    }
+
+    test('without placeholder', async () => {
+      temp.createFile(
+        '.config.yml',
+        'cosmiconfig:\n  searchPlaces: [".foo-config", ".foo.config.yml"]',
+      );
+      await runTest();
+    });
+
+    test('with placeholder', async () => {
+      temp.createFile(
+        '.config.yml',
+        'cosmiconfig:\n  searchPlaces: [".{name}-config", ".{name}.config.yml"]',
+      );
+      await runTest();
+    });
+  });
+
+  describe('checks config in meta file', () => {
+    const currentDir = process.cwd();
+
+    beforeEach(() => {
+      temp.createDir('sub');
+      temp.createFile('.foo-config', 'a: c');
+      process.chdir(temp.dir);
+    });
+
+    afterEach(() => {
+      process.chdir(currentDir);
+    });
+
+    describe('not existing', () => {
+      beforeEach(() => {
+        temp.createFile(
+          '.config.yml',
+          'cosmiconfig:\n  searchPlaces: [".foo-config"]',
+        );
+      });
+
+      const file = temp.absolutePath('.foo-config');
+      const explorerOptions: Options & OptionsSync = {
+        stopDir: temp.absolutePath('.'),
+        ignoreEmptySearchPlaces: false,
+      };
+
+      function checkResult(
+        constructFiles: Array<string>,
+        readFileSpy: SpyInstance,
+        result: any,
+      ) {
+        expect(
+          constructFiles.filter((path) => !path.includes('/node_modules/')),
+        ).toEqual([
+          'package.json',
+          '.config.json',
+          '.config.yaml',
+          '.config.yml',
+        ]);
+
+        expect(
+          temp
+            .getSpyPathCalls(readFileSpy)
+            .filter((path) => !path.includes('/node_modules/')),
+        ).toEqual(['.config.yml', '.foo-config']);
+        expect(result).toEqual({
+          config: { a: 'c' },
+          filepath: file,
+        });
+      }
+
+      test('async', async () => {
+        const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
+        const explorer = cosmiconfig('foo', explorerOptions);
+        const constructFiles = temp.getSpyPathCalls(readFileSyncSpy);
+        readFileSyncSpy.mockClear();
+
+        const readFileSpy = vi.spyOn(fs, 'readFile');
+        const result = await explorer.search(temp.dir);
+        checkResult(constructFiles, readFileSpy, result);
+      });
+
+      test('sync', () => {
+        const readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
+        const explorer = cosmiconfigSync('foo', explorerOptions);
+        const constructFiles = temp.getSpyPathCalls(readFileSyncSpy);
+
+        readFileSyncSpy.mockClear();
+        const result = explorer.search(temp.dir);
+        checkResult(constructFiles, readFileSyncSpy, result);
+      });
+    });
+
+    describe('existing', () => {
+      beforeEach(() => {
+        temp.createFile('.config.yml', 'foo:\n  a: d');
+      });
+
+      function checkResult(readFileSpy: SpyInstance, result: any) {
+        expect(temp.getSpyPathCalls(readFileSpy)).toEqual(['.config.yml']);
+        expect(result).toEqual({
+          config: { a: 'd' },
+          filepath: temp.absolutePath('.config.yml'),
+        });
+      }
+
+      const explorerOptions = { stopDir: temp.absolutePath('.') };
+
+      test('async', async () => {
+        const explorer = cosmiconfig('foo', explorerOptions);
+        const readFileSpy = vi.spyOn(fs, 'readFile');
+        const result = await explorer.search(temp.dir);
+        checkResult(readFileSpy, result);
+      });
+
+      test('sync', () => {
+        const explorer = cosmiconfigSync('foo', explorerOptions);
+        const readFileSpy = vi.spyOn(fs, 'readFileSync');
+        const result = explorer.search(temp.dir);
+        checkResult(readFileSpy, result);
+      });
+    });
+  });
+});
diff --git a/test/successful-directories.test.ts b/test/successful-directories.test.ts
index 8404a55..c8e00ab 100644
--- a/test/successful-directories.test.ts
+++ b/test/successful-directories.test.ts
@@ -1,6 +1,15 @@
-import fs from 'fs';
+import {
+  beforeEach,
+  afterAll,
+  describe,
+  expect,
+  test,
+  afterEach,
+  vi,
+} from 'vitest';
 import { TempDir } from './util';
 import { cosmiconfig, cosmiconfigSync, defaultLoaders } from '../src';
+import * as readFile from '../src/readFile';
 
 const temp = new TempDir();
 
@@ -67,16 +76,18 @@ describe('finds rc file in third searched dir, with a package.json lacking prop'
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -120,16 +131,18 @@ describe('finds package.json prop in second searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -181,14 +194,16 @@ describe('finds package.json with nested packageProp in second searched dir', ()
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfig('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfigSync('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -231,16 +246,18 @@ describe('finds JS file in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -284,16 +301,18 @@ describe('finds CJS file in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -328,16 +347,18 @@ describe('finds .foorc.js file in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -373,16 +394,18 @@ describe('finds .foorc.cjs file in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -416,16 +439,18 @@ describe("finds foorc file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -460,16 +485,18 @@ describe("finds foorc.json file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -505,16 +532,18 @@ describe("finds foorc.yaml file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -551,16 +580,18 @@ describe("finds foorc.yml file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -601,16 +632,18 @@ describe("finds foorc.js file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -652,16 +685,18 @@ describe("finds foorc.cjs file in first searched dir's .config subdir", () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -705,16 +740,18 @@ describe('skips over empty file to find JS file in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -747,16 +784,18 @@ describe('finds package.json in second dir searched, with alternate names', () =
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -792,15 +831,16 @@ describe('finds rc file in third searched dir, skipping packageProp, parsing ext
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
-
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfig('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfigSync('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -832,16 +872,18 @@ describe('finds package.json file in second searched dir, skipping JS and RC fil
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -886,16 +928,18 @@ describe('finds .foorc.json in second searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -926,16 +970,18 @@ describe('finds .foorc.yaml in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -967,16 +1013,18 @@ describe('finds .foorc.yml in first searched dir', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1028,15 +1076,16 @@ describe('adding myfooconfig.js to searchPlaces, finds it in first searched dir'
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
-
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfig('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const explorer = cosmiconfigSync('foo', explorerOptions);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1062,6 +1111,7 @@ describe('finds JS file traversing from cwd', () => {
 
   const checkResult = (readFileSpy: any, result: any) => {
     const filesChecked = temp.getSpyPathCalls(readFileSpy);
+
     expect(filesChecked).toEqual([
       'a/b/c/d/e/f/package.json',
       'a/b/c/d/e/f/.foorc',
@@ -1101,16 +1151,18 @@ describe('finds JS file traversing from cwd', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search();
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search();
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search();
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search();
     checkResult(readFileSpy, result);
   });
 });
@@ -1150,16 +1202,18 @@ describe('searchPlaces can include subdirectories', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1196,16 +1250,18 @@ describe('directories with the same name as a search place are not treated as fi
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1263,16 +1319,18 @@ describe('custom loaders allow non-default file types', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1330,16 +1388,18 @@ describe('adding custom loaders allows for default and non-default file types',
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1387,16 +1447,18 @@ describe('defaults loaders can be overridden', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1417,8 +1479,8 @@ describe('custom loaders can be async', () => {
       'one\ntwo\nthree\t\t\n  four\n',
     );
 
-    loadThingsSync = jest.fn(() => ({ things: true }));
-    loadThingsAsync = jest.fn(async () => ({ things: true }));
+    loadThingsSync = vi.fn(() => ({ things: true }));
+    loadThingsAsync = vi.fn(async () => ({ things: true }));
   });
 
   const checkResult = (readFileSpy: any, result: any) => {
@@ -1432,31 +1494,32 @@ describe('custom loaders can be async', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
     const explorerOptions = {
       ...baseOptions,
       loaders: {
         '.things': loadThingsAsync,
       },
     };
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     expect(loadThingsSync).not.toHaveBeenCalled();
     expect(loadThingsAsync).toHaveBeenCalled();
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
-
     const explorerOptions = {
       ...baseOptions,
       loaders: {
         '.things': loadThingsSync,
       },
     };
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     expect(loadThingsSync).toHaveBeenCalled();
     expect(loadThingsAsync).not.toHaveBeenCalled();
     checkResult(readFileSpy, result);
@@ -1494,9 +1557,10 @@ describe('a custom loader entry can include just an async loader', () => {
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
@@ -1534,16 +1598,18 @@ describe('a custom loader entry can include only a sync loader and work for both
   };
 
   test('async', async () => {
-    const readFileSpy = jest.spyOn(fs, 'readFile');
+    const explorer = cosmiconfig('foo', explorerOptions);
 
-    const result = await cosmiconfig('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFile');
+    const result = await explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 
   test('sync', () => {
-    const readFileSpy = jest.spyOn(fs, 'readFileSync');
+    const explorer = cosmiconfigSync('foo', explorerOptions);
 
-    const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
+    const readFileSpy = vi.spyOn(readFile, 'readFileSync');
+    const result = explorer.search(startDir);
     checkResult(readFileSpy, result);
   });
 });
diff --git a/test/successful-files.test.ts b/test/successful-files.test.ts
index 789abb8..6e9fed0 100644
--- a/test/successful-files.test.ts
+++ b/test/successful-files.test.ts
@@ -1,3 +1,12 @@
+import {
+  beforeEach,
+  afterAll,
+  describe,
+  expect,
+  test,
+  afterEach,
+  vi,
+} from 'vitest';
 import { TempDir } from './util';
 import { cosmiconfig, cosmiconfigSync } from '../src';
 
@@ -317,8 +326,8 @@ describe('custom loaders can be async', () => {
   let loadThingsAsync: any;
   beforeEach(() => {
     temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n  four\n');
-    loadThingsSync = jest.fn(() => ({ things: true }));
-    loadThingsAsync = jest.fn(async () => ({ things: true }));
+    loadThingsSync = vi.fn(() => ({ things: true }));
+    loadThingsAsync = vi.fn(async () => ({ things: true }));
   });
 
   const file = temp.absolutePath('.foorc.things');
diff --git a/test/util.ts b/test/util.ts
index 4f61739..609f8ea 100644
--- a/test/util.ts
+++ b/test/util.ts
@@ -3,8 +3,9 @@ import del from 'del';
 import makeDir from 'make-dir';
 import parentModule from 'parent-module';
 import os from 'os';
+import { Mock, SpyInstance, vi } from 'vitest';
 
-const fs = jest.requireActual('fs');
+const fs = await vi.importActual<typeof import('fs')>('fs');
 
 function normalizeDirectorySlash(pathname: string): string {
   const normalizeCrossPlatform = pathname.replace(/\\/g, '/');
@@ -68,7 +69,7 @@ class TempDir {
     fs.writeFileSync(filePath, `${contents}\n`);
   }
 
-  public getSpyPathCalls(spy: jest.Mock | jest.SpyInstance): Array<string> {
+  public getSpyPathCalls(spy: Mock | SpyInstance): Array<string> {
     const calls = spy.mock.calls;
 
     const result = calls.map((call): string => {
diff --git a/tsconfig.json b/tsconfig.json
index d8e9cde..cb6a67e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -18,9 +18,8 @@
     // Import non-ES modules as default imports.
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
-    "skipLibCheck": true,
-    "resolveJsonModule": true
+    "skipLibCheck": true
   },
-  "include": ["src", "test", "global.d.ts"],
+  "include": ["src", "test", "global.d.ts", "vite.config.ts"],
   "exclude": ["dist", "node_modules"]
 }
diff --git a/tsconfig.types.json b/tsconfig.types.json
index e509407..f20c43b 100644
--- a/tsconfig.types.json
+++ b/tsconfig.types.json
@@ -9,5 +9,5 @@
     "allowJs": false,
     "emitDeclarationOnly": true
   },
-  "exclude": ["**/*.test.ts", "./test/"]
+  "exclude": ["**/*.test.ts", "./test/", "vite.config.ts"]
 }
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..13edb17
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,23 @@
+import { defineConfig } from 'vitest/config';
+
+const vitestConfig = defineConfig({
+  test: {
+    threads: false,
+    environment: 'node',
+    restoreMocks: true,
+    mockReset: true,
+    includeSource: ['src/**/*.{js,ts}'],
+    coverage: {
+      provider: 'istanbul',
+      reporter: ['text', 'html', 'lcov'],
+      include: ['src/**/*.{js,ts}'],
+      branches: 100,
+      functions: 100,
+      lines: 100,
+      statements: 100,
+    },
+  },
+});
+
+// eslint-disable-next-line import/no-default-export
+export default vitestConfig;

More details

Full run details

Historical runs