pax_global_header 0000666 0000000 0000000 00000000064 14333725373 0014524 g ustar 00root root 0000000 0000000 52 comment=18f14419aa7015aad11b7dc684947dec7171a9c5
cosmiconfig-7.1.0/ 0000775 0000000 0000000 00000000000 14333725373 0014031 5 ustar 00root root 0000000 0000000 cosmiconfig-7.1.0/.eslintignore 0000664 0000000 0000000 00000000063 14333725373 0016533 0 ustar 00root root 0000000 0000000 test/fixtures
coverage
.nyc_output
flow-typed
dist
cosmiconfig-7.1.0/.eslintrc.js 0000664 0000000 0000000 00000012700 14333725373 0016270 0 ustar 00root root 0000000 0000000 'use strict';
// first node 8.x LTS release
const supportedNodeVersion = '8.9';
const allExtensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.json'];
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
project: './tsconfig.eslint.json',
},
extends: [
'eslint-config-davidtheclark-node',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
// This is making an annoying amount of unnecessary noise.
// If anybody would like to turn it back on, PR welcome.
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:jest/recommended',
'prettier',
'prettier/@typescript-eslint',
],
plugins: ['jest', '@typescript-eslint', 'import'],
rules: {
'no-var': 'off',
'prefer-const': 'off',
'prefer-arrow-callback': 'off',
'func-names': ['error', 'always'],
'prefer-template': 'error',
'no-prototype-builtins': 'error',
'object-shorthand': [
'error',
'always',
{ avoidExplicitReturnArrows: true },
],
/**
* eslint-plugin-typescript
*/
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowTypedFunctionExpressions: true,
},
],
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
// requires type information rules
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-for-in-array': 'error',
'@typescript-eslint/no-unnecessary-qualifier': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/prefer-includes': 'error',
'@typescript-eslint/prefer-namespace-keyword': 'error',
// '@typescript-eslint/prefer-readonly': 'error',
'@typescript-eslint/prefer-regexp-exec': 'error',
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
'@typescript-eslint/promise-function-async': ['error', { allowAny: true }],
'@typescript-eslint/require-array-sort-compare': 'error',
'@typescript-eslint/restrict-plus-operands': 'error',
// '@typescript-eslint/strict-boolean-expressions': 'error',
'@typescript-eslint/unbound-method': 'error',
// rules not in recommended
'@typescript-eslint/ban-ts-ignore': 'off', // maybe enable?
'@typescript-eslint/member-ordering': 'off', // maybe enable?
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/unified-signatures': 'error',
/**
* eslint-plugin-node
*/
'node/no-unpublished-require': 'off',
'node/no-unsupported-features': 'off',
'node/no-unsupported-features/es-builtins': 'off',
'node/no-unsupported-features/es-syntax': 'off',
'node/no-unsupported-features/node-builtins': 'off',
// Redundant with import/no-extraneous-dependencies
'node/no-extraneous-import': 'off',
'node/no-extraneous-require': 'off',
// Redundant with import/no-unresolved
'node/no-missing-import': 'off',
'node/no-missing-require': 'off',
/**
* eslint-plugin-import
*/
'import/no-default-export': 'error',
'import/no-named-export': 'off',
'import/prefer-default-export': 'off',
/**
* eslint-plugin-jest
*/
'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',
// Many tests make assertions indirectly in a way the plugin
// does not understand.
'jest/expect-expect': 'off',
},
settings: {
node: {
convertPath: {
'src/**/*.{js,ts}': ['^src/(.+?)\\.(js|ts)$', 'dist/$1.js'],
'src/**/.*.{js,ts}': ['^src/(.+?)\\.(js|ts)$', 'dist/$1.js'],
},
tryExtensions: allExtensions,
},
'import/resolver': {
node: {
extensions: allExtensions,
},
},
'import/extensions': allExtensions,
},
overrides: [
{
files: ['*.test.{js,ts}', '.*.test.{js,ts}'],
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/require-await': 'off',
},
},
{
files: ['*.js', '.*.js'],
excludedFiles: ['*/**', '*/.**'],
parserOptions: {
sourceType: 'script',
},
rules: {
strict: ['error', 'safe'],
'node/no-unpublished-require': 'off',
'node/no-unsupported-features/es-builtins': [
'error',
{ version: supportedNodeVersion },
],
'node/no-unsupported-features/es-syntax': [
'error',
{ version: supportedNodeVersion },
],
'node/no-unsupported-features/node-builtins': [
'error',
{ version: supportedNodeVersion },
],
},
},
],
};
cosmiconfig-7.1.0/.gitignore 0000664 0000000 0000000 00000000130 14333725373 0016013 0 ustar 00root root 0000000 0000000 node_modules
*.log
.nyc_output
coverage
*.DS_Store
dist
.idea
.vscode
wallaby.config.js
cosmiconfig-7.1.0/.npmrc 0000664 0000000 0000000 00000000046 14333725373 0015151 0 ustar 00root root 0000000 0000000 package-lock=false
save-exact=false
cosmiconfig-7.1.0/.prettierignore 0000664 0000000 0000000 00000000126 14333725373 0017073 0 ustar 00root root 0000000 0000000 node_modules
*.log
.nyc_output
coverage
flow-typed
*.DS_Store
dist
.idea
.vscode
*.md
cosmiconfig-7.1.0/.travis.yml 0000664 0000000 0000000 00000000455 14333725373 0016146 0 ustar 00root root 0000000 0000000 language: node_js
git:
depth: 5
cache: yarn
branches:
only:
- main
- v7
matrix:
include:
- node_js: '10'
after_success: 'bash <(curl -s https://codecov.io/bash)'
- node_js: '12'
- node_js: '14'
install:
- yarn install --frozen-lockfile
script:
- yarn run check:all
cosmiconfig-7.1.0/.yarnrc 0000664 0000000 0000000 00000000043 14333725373 0015325 0 ustar 00root root 0000000 0000000 save-prefix "^"
--no-lockfile true
cosmiconfig-7.1.0/CHANGELOG.md 0000664 0000000 0000000 00000020703 14333725373 0015644 0 ustar 00root root 0000000 0000000 # Changelog
## 7.1.0
- Added: additional default `searchPlaces` within a .config subdirectory (without leading dot in the file name)
## 7.0.1
- Fixed: If there was a directory that had the same name as a search place (e.g. "package.json"), we would try to read it as a file, which would cause an exception.
## 7.0.0
- **Breaking change:** Add `${moduleName}rc.cjs` and `${moduleName}.config.cjs` to the default `searchPlaces`, to support users of `"type": "module"` in recent versions of Node.
- **Breaking change:** Drop support for Node 8. Now requires Node 10+.
## 6.0.0
- **Breaking change:** The package now has named exports. See examples below.
- **Breaking change:** Separate async and sync APIs, accessible from different named exports. If you used `explorer.searchSync()` or `explorer.loadSync()`, you'll now create a sync explorer with `cosmiconfigSync()`, then use `explorerSync.search()` and `explorerSync.load()`.
```js
// OLD: cosmiconfig v5
import cosmiconfig from 'cosmiconfig';
const explorer = cosmiconfig('example');
const searchAsyncResult = await explorer.search();
const loadAsyncResult = await explorer.load('./file/to/load');
const searchSyncResult = explorer.searchSync();
const loadSyncResult = explorer.loadSync('./file/to/load');
// NEW: cosmiconfig v6
import { cosmiconfig, cosmiconfigSync } from 'cosmiconfig';
const explorer = cosmiconfig('example');
const searchAsyncResult = await explorer.search();
const loadAsyncResult = await explorer.load('./file/to/load');
const explorerSync = cosmiconfigSync('example');
const searchSyncResult = explorerSync.search();
const loadSyncResult = explorerSync.load('./file/to/load');
```
- **Breaking change:** Remove support for Node 4 and 6. Requires Node 8+.
- **Breaking change:** Use npm package [yaml](https://www.npmjs.com/package/yaml) to parse YAML instead of npm package [js-yaml](https://www.npmjs.com/package/js-yaml).
- **Breaking change:** Remove `cosmiconfig.loaders` and add named export `defaultLoaders` that exports the default loaders used for each extension.
```js
import { defaultLoaders } from 'cosmiconfig';
console.log(Object.entries(defaultLoaders))
// [
// [ '.js', [Function: loadJs] ],
// [ '.json', [Function: loadJson] ],
// [ '.yaml', [Function: loadYaml] ],
// [ '.yml', [Function: loadYaml] ],
// [ 'noExt', [Function: loadYaml] ]
// ]
```
- Migrate from Flowtype to Typescript.
- Lazy load all default loaders.
## 5.2.1
- Chore: Upgrade `js-yaml` to avoid npm audit warning.
## 5.2.0
- Added: `packageProp` values can be arrays of strings, to allow for property names that include periods. (This was possible before, but not documented or deliberately supported.)
- Chore: Replaced the `lodash.get` dependency with a locally defined function.
- Chore: Upgrade `js-yaml` to avoid npm audit warning.
## 5.1.0
- Added: `packageProp` values can include periods to describe paths to nested objects within `package.json`.
## 5.0.7
- Fixed: JS loader bypasses Node's `require` cache, fixing a bug where updates to `.js` config files would not load even when Cosmiconfig was told not to cache.
## 5.0.6
- Fixed: Better error message if the end user tries an extension Cosmiconfig is not configured to understand.
## 5.0.5
- Fixed: `load` and `loadSync` work with paths relative to `process.cwd()`.
## 5.0.4
- Fixed: `rc` files with `.js` extensions included in default `searchPlaces`.
## 5.0.3
- Docs: Minor corrections to documentation. *Released to update package documentation on npm*.
## 5.0.2
- Fixed: Allow `searchSync` and `loadSync` to load JS configuration files whose export is a Promise.
## 5.0.1
The API has been completely revamped to increase clarity and enable a very wide range of new usage. **Please read the readme for all the details.**
While the defaults remain just as useful as before — and you can still pass no options at all — now you can also do all kinds of wild and crazy things.
- The `loaders` option allows you specify custom functions to derive config objects from files. Your loader functions could parse ES2015 modules or TypeScript, JSON5, even INI or XML. Whatever suits you.
- The `searchPlaces` option allows you to specify exactly where cosmiconfig looks within each directory it searches.
- The combination of `loaders` and `searchPlaces` means that you should be able to load pretty much any kind of configuration file you want, from wherever you want it to look.
Additionally, the overloaded `load()` function has been split up into several clear and focused functions:
- `search()` now searches up the directory tree, and `load()` loads a configuration file that you don't need to search for.
- The `sync` option has been replaced with separate synchronous functions: `searchSync()` and `loadSync()`.
- `clearFileCache()` and `clearDirectoryCache()` have been renamed to `clearLoadCache()` and `clearSearchPath()` respectively.
More details:
- The default JS loader uses `require`, instead of `require-from-string`. So you *could* use `require` hooks to control the loading of JS files (e.g. pass them through esm or Babel). In most cases it is probably preferable to use a custom loader.
- The options `rc`, `js`, and `rcExtensions` have all been removed. You can accomplish the same and more with `searchPlaces`.
- The default `searchPlaces` include `rc` files with extensions, e.g. `.thingrc.json`, `.thingrc.yaml`, `.thingrc.yml`. This is the equivalent of switching the default value of the old `rcExtensions` option to `true`.
- The option `rcStrictJson` has been removed. To get the same effect, you can specify `noExt: cosmiconfig.loadJson` in your `loaders` object.
- `packageProp` no longer accepts `false`. If you don't want to look in `package.json`, write a `searchPlaces` array that does not include it.
- By default, empty files are ignored by `search()`. The new option `ignoreEmptySearchPlaces` allows you to load them, instead, in case you want to do something with empty files.
- The option `configPath` has been removed. Just pass your filepaths directory to `load()`.
- Removed the `format` option. Formats are now all handled via the file extensions specified in `loaders`.
(If you're wondering with happened to 5.0.0 ... it was a silly publishing mistake.)
## 4.0.0
- Licensing improvement: updated `parse-json` from `3.0.0` to `4.0.0`(see [sindresorhus/parse-json#12][parse-json-pr-12]).
- Changed: error message format for `JSON` parse errors(see [#101][pr-101]). If you were relying on the format of JSON-parsing error messages, this will be a breaking change for you.
- Changed: set default for `searchPath` as `process.cwd()` in `explorer.load`.
## 3.1.0
- Added: infer format based on filePath
## 3.0.1
- Fixed: memory leak due to bug in `require-from-string`.
- Added: for JSON files, append position to end of error message.
## 3.0.0
- Removed: support for loading config path using the `--config` flag. cosmiconfig will not parse command line arguments. Your application can parse command line arguments and pass them to cosmiconfig.
- Removed: `argv` config option.
- Removed: support for Node versions < 4.
- Added: `sync` option.
- Fixed: Throw a clear error on getting empty config file.
- Fixed: when a `options.configPath` is `package.json`, return the package prop, not the entire JSON file.
## 2.2.2
- Fixed: `options.configPath` and `--config` flag are respected.
## 2.2.0, 2.2.1
- 2.2.0 included a number of improvements but somehow broke stylelint. The changes were reverted in 2.2.1, to be restored later.
## 2.1.3
- Licensing improvement: switched from `json-parse-helpfulerror` to `parse-json`.
## 2.1.2
- Fixed: bug where an `ENOENT` error would be thrown is `searchPath` referenced a non-existent file.
- Fixed: JSON parsing errors in Node v7.
## 2.1.1
- Fixed: swapped `graceful-fs` for regular `fs`, fixing a garbage collection problem.
## 2.1.0
- Added: Node 0.12 support.
## 2.0.2
- Fixed: Node version specified in `package.json`.
## 2.0.1
- Fixed: no more infinite loop in Windows.
## 2.0.0
- Changed: module now creates cosmiconfig instances with `load` methods (see README).
- Added: caching (enabled by the change above).
- Removed: support for Node versions <4.
## 1.1.0
- Add `rcExtensions` option.
## 1.0.2
- Fix handling of `require()`'s within JS module configs.
## 1.0.1
- Switch Promise implementation to pinkie-promise.
## 1.0.0
- Initial release.
[parse-json-pr-12]: https://github.com/sindresorhus/parse-json/pull/12
[pr-101]: https://github.com/davidtheclark/cosmiconfig/pull/101
cosmiconfig-7.1.0/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000003615 14333725373 0016635 0 ustar 00root root 0000000 0000000 # Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery
- Personal attacks
- Trolling or insulting/derogatory comments
- Public or private harassment
- Publishing other's private information, such as physical or electronic addresses, without explicit permission
- Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at
cosmiconfig-7.1.0/LICENSE 0000664 0000000 0000000 00000002067 14333725373 0015043 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2015 David Clark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
cosmiconfig-7.1.0/README.md 0000664 0000000 0000000 00000046413 14333725373 0015320 0 ustar 00root root 0000000 0000000 # cosmiconfig
[](https://travis-ci.org/davidtheclark/cosmiconfig) [](https://ci.appveyor.com/project/davidtheclark/cosmiconfig/branch/main)
[](https://codecov.io/gh/davidtheclark/cosmiconfig)
Cosmiconfig searches for and loads configuration for your program.
It features smart defaults based on conventional expectations in the JavaScript ecosystem.
But it's also flexible enough to search wherever you'd like to search, and load whatever you'd like to load.
By default, Cosmiconfig will start where you tell it to start and search up the directory tree for the following:
- a `package.json` property
- a JSON or YAML, extensionless "rc file"
- an "rc file" with the extensions `.json`, `.yaml`, `.yml`, `.js`, or `.cjs`
- any of the above two inside a `.config` subdirectory
- a `.config.js` or `.config.cjs` CommonJS module
For example, if your module's name is "myapp", cosmiconfig will search up the directory tree for configuration in the following places:
- 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 `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).
## Table of contents
- [Installation](#installation)
- [Usage](#usage)
- [Result](#result)
- [Asynchronous API](#asynchronous-api)
- [cosmiconfig()](#cosmiconfig-1)
- [explorer.search()](#explorersearch)
- [explorer.load()](#explorerload)
- [explorer.clearLoadCache()](#explorerclearloadcache)
- [explorer.clearSearchCache()](#explorerclearsearchcache)
- [explorer.clearCaches()](#explorerclearcaches)
- [Synchronous API](#synchronous-api)
- [cosmiconfigSync()](#cosmiconfigsync)
- [explorerSync.search()](#explorersyncsearch)
- [explorerSync.load()](#explorersyncload)
- [explorerSync.clearLoadCache()](#explorersyncclearloadcache)
- [explorerSync.clearSearchCache()](#explorersyncclearsearchcache)
- [explorerSync.clearCaches()](#explorersyncclearcaches)
- [cosmiconfigOptions](#cosmiconfigoptions)
- [searchPlaces](#searchplaces)
- [loaders](#loaders)
- [packageProp](#packageprop)
- [stopDir](#stopdir)
- [cache](#cache)
- [transform](#transform)
- [ignoreEmptySearchPlaces](#ignoreemptysearchplaces)
- [Caching](#caching)
- [Differences from rc](#differences-from-rc)
- [Contributing & Development](#contributing--development)
## Installation
```
npm install cosmiconfig
```
Tested in Node 10+.
## Usage
Create a Cosmiconfig explorer, then either `search` for or directly `load` a configuration file.
```js
const { cosmiconfig, cosmiconfigSync } = require('cosmiconfig');
// ...
const explorer = cosmiconfig(moduleName);
// Search for a configuration by walking up directories.
// See documentation for search, below.
explorer.search()
.then((result) => {
// result.config is the parsed configuration object.
// result.filepath is the path to the config file that was found.
// result.isEmpty is true if there was nothing to parse in the config file.
})
.catch((error) => {
// Do something constructive.
});
// Load a configuration directly when you know where it should be.
// The result object is the same as for search.
// See documentation for load, below.
explorer.load(pathToConfig).then(..);
// You can also search and load synchronously.
const explorerSync = cosmiconfigSync(moduleName);
const searchedFor = explorerSync.search();
const loaded = explorerSync.load(pathToConfig);
```
## Result
The result object you get from `search` or `load` has the following properties:
- **config:** The parsed configuration object. `undefined` if the file is empty.
- **filepath:** The path to the configuration file that was found.
- **isEmpty:** `true` if the configuration file is empty. This property will not be present if the configuration file is not empty.
## Asynchronous API
### cosmiconfig()
```js
const { cosmiconfig } = require('cosmiconfig');
const explorer = cosmiconfig(moduleName[, cosmiconfigOptions])
```
Creates a cosmiconfig instance ("explorer") configured according to the arguments, and initializes its caches.
#### moduleName
Type: `string`. **Required.**
Your module name. This is used to create the default [`searchPlaces`] and [`packageProp`].
If your [`searchPlaces`] value will include files, as it does by default (e.g. `${moduleName}rc`), your `moduleName` must consist of characters allowed in filenames. That means you should not copy scoped package names, such as `@my-org/my-package`, directly into `moduleName`.
**[`cosmiconfigOptions`] are documented below.**
You may not need them, and should first read about the functions you'll use.
### explorer.search()
```js
explorer.search([searchFrom]).then(result => {..})
```
Searches for a configuration file. Returns a Promise that resolves with a [result] or with `null`, if no configuration file is found.
You can do the same thing synchronously with [`explorerSync.search()`].
Let's say your module name is `goldengrahams` so you initialized with `const explorer = cosmiconfig('goldengrahams');`.
Here's how your default [`search()`] will work:
- Starting from `process.cwd()` (or some other directory defined by the `searchFrom` argument to [`search()`]), look for configuration objects in the following places:
1. A `goldengrahams` property in a `package.json` file.
2. A `.goldengrahamsrc` file with JSON or YAML syntax.
3. A `.goldengrahamsrc.json`, `.goldengrahamsrc.yaml`, `.goldengrahamsrc.yml`, `.goldengrahamsrc.js`, or `.goldengrahamsrc.cjs` file.
4. A `goldengrahamsrc`, `goldengrahamsrc.json`, `goldengrahamsrc.yaml`, `goldengrahamsrc.yml`, `goldengrahamsrc.js`, or `goldengrahamsrc.cjs` file in the `.config` subdirectory.
5. A `goldengrahams.config.js` or `goldengrahams.config.cjs` CommonJS module exporting the object.
- If none of those searches reveal a configuration object, move up one directory level and try again.
So the search continues in `./`, `../`, `../../`, `../../../`, etc., checking the same places in each directory.
- Continue searching until arriving at your home directory (or some other directory defined by the cosmiconfig option [`stopDir`]).
- If at any point a parsable configuration is found, the [`search()`] Promise resolves with its [result] \(or, with [`explorerSync.search()`], the [result] is returned).
- If no configuration object is found, the [`search()`] Promise resolves with `null` (or, with [`explorerSync.search()`], `null` is returned).
- If a configuration object is found *but is malformed* (causing a parsing error), the [`search()`] Promise rejects with that error (so you should `.catch()` it). (Or, with [`explorerSync.search()`], the error is thrown.)
**If you know exactly where your configuration file should be, you can use [`load()`], instead.**
**The search process is highly customizable.**
Use the cosmiconfig options [`searchPlaces`] and [`loaders`] to precisely define where you want to look for configurations and how you want to load them.
#### searchFrom
Type: `string`.
Default: `process.cwd()`.
A filename.
[`search()`] will start its search here.
If the value is a directory, that's where the search starts.
If it's a file, the search starts in that file's directory.
### explorer.load()
```js
explorer.load(loadPath).then(result => {..})
```
Loads a configuration file. Returns a Promise that resolves with a [result] or rejects with an error (if the file does not exist or cannot be loaded).
Use `load` if you already know where the configuration file is and you just need to load it.
```js
explorer.load('load/this/file.json'); // Tries to load load/this/file.json.
```
If you load a `package.json` file, the result will be derived from whatever property is specified as your [`packageProp`].
You can do the same thing synchronously with [`explorerSync.load()`].
### explorer.clearLoadCache()
Clears the cache used in [`load()`].
### explorer.clearSearchCache()
Clears the cache used in [`search()`].
### explorer.clearCaches()
Performs both [`clearLoadCache()`] and [`clearSearchCache()`].
## Synchronous API
### cosmiconfigSync()
```js
const { cosmiconfigSync } = require('cosmiconfig');
const explorerSync = cosmiconfigSync(moduleName[, cosmiconfigOptions])
```
Creates a *synchronous* cosmiconfig instance ("explorerSync") configured according to the arguments, and initializes its caches.
See [`cosmiconfig()`].
### explorerSync.search()
```js
const result = explorerSync.search([searchFrom]);
```
Synchronous version of [`explorer.search()`].
Returns a [result] or `null`.
### explorerSync.load()
```js
const result = explorerSync.load(loadPath);
```
Synchronous version of [`explorer.load()`].
Returns a [result].
### explorerSync.clearLoadCache()
Clears the cache used in [`load()`].
### explorerSync.clearSearchCache()
Clears the cache used in [`search()`].
### explorerSync.clearCaches()
Performs both [`clearLoadCache()`] and [`clearSearchCache()`].
## cosmiconfigOptions
Type: `Object`.
Possible options are documented below.
### searchPlaces
Type: `Array`.
Default: See below.
An array of places that [`search()`] will check in each directory as it moves up the directory tree.
Each place is relative to the directory being searched, and the places are checked in the specified order.
**Default `searchPlaces`:**
```js
[
'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`,
]
```
Create your own array to search more, fewer, or altogether different places.
Every item in `searchPlaces` needs to have a loader in [`loaders`] that corresponds to its extension.
(Common extensions are covered by default loaders.)
Read more about [`loaders`] below.
`package.json` is a special value: When it is included in `searchPlaces`, Cosmiconfig will always parse it as JSON and load a property within it, not the whole file.
That property is defined with the [`packageProp`] option, and defaults to your module name.
Examples, with a module named `porgy`:
```js
// Disallow extensions on rc files:
[
'package.json',
'.porgyrc',
'porgy.config.js'
]
// ESLint searches for configuration in these places:
[
'.eslintrc.js',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'.eslintrc',
'package.json'
]
// Babel looks in fewer places:
[
'package.json',
'.babelrc'
]
// Maybe you want to look for a wide variety of JS flavors:
[
'porgy.config.js',
'porgy.config.mjs',
'porgy.config.ts',
'porgy.config.coffee'
]
// ^^ You will need to designate custom loaders to tell
// Cosmiconfig how to handle these special JS flavors.
// Look within a .config/ subdirectory of every searched directory:
[
'package.json',
'.porgyrc',
'.config/.porgyrc',
'.porgyrc.json',
'.config/.porgyrc.json'
]
```
### loaders
Type: `Object`.
Default: See below.
An object that maps extensions to the loader functions responsible for loading and parsing files with those extensions.
Cosmiconfig exposes its default loaders on a named export `defaultLoaders`.
**Default `loaders`:**
```js
const { defaultLoaders } = require('cosmiconfig');
console.log(Object.entries(defaultLoaders))
// [
// [ '.cjs', [Function: loadJs] ],
// [ '.js', [Function: loadJs] ],
// [ '.json', [Function: loadJson] ],
// [ '.yaml', [Function: loadYaml] ],
// [ '.yml', [Function: loadYaml] ],
// [ 'noExt', [Function: loadYaml] ]
// ]
```
(YAML is a superset of JSON; which means YAML parsers can parse JSON; which is how extensionless files can be either YAML *or* JSON with only one parser.)
**If you provide a `loaders` object, your object will be *merged* with the defaults.**
So you can override one or two without having to override them all.
**Keys in `loaders`** are extensions (starting with a period), or `noExt` to specify the loader for files *without* extensions, like `.myapprc`.
**Values in `loaders`** are a loader function (described below) whose values are loader functions.
**The most common use case for custom loaders value is to load extensionless `rc` files as strict JSON**, instead of JSON *or* YAML (the default).
To accomplish that, provide the following `loaders` value:
```js
{
noExt: defaultLoaders['.json']
}
```
If you want to load files that are not handled by the loader functions Cosmiconfig exposes, you can write a custom loader function or use one from NPM if it exists.
**Third-party loaders:**
- [cosmiconfig-typescript-loader](https://github.com/codex-/cosmiconfig-typescript-loader)
**Use cases for custom loader function:**
- Allow configuration syntaxes that aren't handled by Cosmiconfig's defaults, like JSON5, INI, or XML.
- Allow ES2015 modules from `.mjs` configuration files.
- Parse JS files with Babel before deriving the configuration.
**Custom loader functions** have the following signature:
```js
// Sync
(filepath: string, content: string) => Object | null
// Async
(filepath: string, content: string) => Object | null | Promise
```
Cosmiconfig reads the file when it checks whether the file exists, so it will provide you with both the file's path and its content.
Do whatever you need to, and return either a configuration object or `null` (or, for async-only loaders, a Promise that resolves with one of those).
`null` indicates that no real configuration was found and the search should continue.
A few things to note:
- If you use a custom loader, be aware of whether it's sync or async: you cannot use async customer loaders with the sync API ([`cosmiconfigSync()`]).
- **Special JS syntax can also be handled by using a `require` hook**, because `defaultLoaders['.js']` just uses `require`.
Whether you use custom loaders or a `require` hook is up to you.
Examples:
```js
// Allow JSON5 syntax:
{
'.json': json5Loader
}
// Allow a special configuration syntax of your own creation:
{
'.special': specialLoader
}
// Allow many flavors of JS, using custom loaders:
{
'.mjs': esmLoader,
'.ts': typeScriptLoader,
'.coffee': coffeeScriptLoader
}
// Allow many flavors of JS but rely on require hooks:
{
'.mjs': defaultLoaders['.js'],
'.ts': defaultLoaders['.js'],
'.coffee': defaultLoaders['.js']
}
```
### packageProp
Type: `string | Array`.
Default: `` `${moduleName}` ``.
Name of the property in `package.json` to look for.
Use a period-delimited string or an array of strings to describe a path to nested properties.
For example, the value `'configs.myPackage'` or `['configs', 'myPackage']` will get you the `"myPackage"` value in a `package.json` like this:
```json
{
"configs": {
"myPackage": {..}
}
}
```
If nested property names within the path include periods, you need to use an array of strings. For example, the value `['configs', 'foo.bar', 'baz']` will get you the `"baz"` value in a `package.json` like this:
```json
{
"configs": {
"foo.bar": {
"baz": {..}
}
}
}
```
If a string includes period but corresponds to a top-level property name, it will not be interpreted as a period-delimited path. For example, the value `'one.two'` will get you the `"three"` value in a `package.json` like this:
```json
{
"one.two": "three",
"one": {
"two": "four"
}
}
```
### stopDir
Type: `string`.
Default: Absolute path to your home directory.
Directory where the search will stop.
### cache
Type: `boolean`.
Default: `true`.
If `false`, no caches will be used.
Read more about ["Caching"](#caching) below.
### transform
Type: `(Result) => Promise | Result`.
A function that transforms the parsed configuration. Receives the [result].
If using [`search()`] or [`load()`] \(which are async), the transform function can return the transformed result or return a Promise that resolves with the transformed result.
If using `cosmiconfigSync`, [`search()`] or [`load()`], the function must be synchronous and return the transformed result.
The reason you might use this option — instead of simply applying your transform function some other way — is that *the transformed result will be cached*. If your transformation involves additional filesystem I/O or other potentially slow processing, you can use this option to avoid repeating those steps every time a given configuration is searched or loaded.
### ignoreEmptySearchPlaces
Type: `boolean`.
Default: `true`.
By default, if [`search()`] encounters an empty file (containing nothing but whitespace) in one of the [`searchPlaces`], it will ignore the empty file and move on.
If you'd like to load empty configuration files, instead, set this option to `false`.
Why might you want to load empty configuration files?
If you want to throw an error, or if an empty configuration file means something to your program.
## Caching
As of v2, cosmiconfig uses caching to reduce the need for repetitious reading of the filesystem or expensive transforms. Every new cosmiconfig instance (created with `cosmiconfig()`) has its own caches.
To avoid or work around caching, you can do the following:
- Set the `cosmiconfig` option [`cache`] to `false`.
- Use the cache-clearing methods [`clearLoadCache()`], [`clearSearchCache()`], and [`clearCaches()`].
- Create separate instances of cosmiconfig (separate "explorers").
## Differences from [rc](https://github.com/dominictarr/rc)
[rc](https://github.com/dominictarr/rc) serves its focused purpose well. cosmiconfig differs in a few key ways — making it more useful for some projects, less useful for others:
- Looks for configuration in some different places: in a `package.json` property, an rc file, a `.config.js` file, and rc files with extensions.
- Built-in support for JSON, YAML, and CommonJS formats.
- Stops at the first configuration found, instead of finding all that can be found up the directory tree and merging them automatically.
- Options.
- Asynchronous by default (though can be run synchronously).
## 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.
And please do participate!
[result]: #result
[`load()`]: #explorerload
[`search()`]: #explorersearch
[`clearloadcache()`]: #explorerclearloadcache
[`clearsearchcache()`]: #explorerclearsearchcache
[`cosmiconfig()`]: #cosmiconfig
[`cosmiconfigSync()`]: #cosmiconfigsync
[`clearcaches()`]: #explorerclearcaches
[`packageprop`]: #packageprop
[`cache`]: #cache
[`stopdir`]: #stopdir
[`searchplaces`]: #searchplaces
[`loaders`]: #loaders
[`cosmiconfigoptions`]: #cosmiconfigoptions
[`explorerSync.search()`]: #explorersyncsearch
[`explorerSync.load()`]: #explorersyncload
[`explorer.search()`]: #explorersearch
[`explorer.load()`]: #explorerload
cosmiconfig-7.1.0/appveyor.yml 0000664 0000000 0000000 00000001077 14333725373 0016426 0 ustar 00root root 0000000 0000000 # 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
cosmiconfig-7.1.0/codecov.yml 0000664 0000000 0000000 00000000016 14333725373 0016173 0 ustar 00root root 0000000 0000000 comment: off;
cosmiconfig-7.1.0/global.d.ts 0000664 0000000 0000000 00000000027 14333725373 0016062 0 ustar 00root root 0000000 0000000 declare module 'yaml';
cosmiconfig-7.1.0/package.json 0000664 0000000 0000000 00000007706 14333725373 0016331 0 ustar 00root root 0000000 0000000 {
"name": "cosmiconfig",
"version": "7.1.0",
"description": "Find and load configuration from a package.json property, rc file, or CommonJS module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"clean": "del-cli --dot=true \"./dist/**/*\"",
"build": "npm run clean && npm run build:compile && npm run build:types",
"build:compile": "cross-env NODE_ENV=production babel src -d dist --verbose --extensions .js,.ts --ignore \"**/**/*.test.js\",\"**/**/*.test.ts\" --source-maps",
"build:types": "cross-env NODE_ENV=production tsc --project tsconfig.types.json",
"dev": "npm run clean && npm run build:compile -- --watch",
"lint": "eslint --ext .js,.ts . && npm run lint:md",
"lint:fix": "eslint --ext .js,.ts . --fix",
"lint:md": "remark-preset-davidtheclark",
"format": "prettier \"**/*.{js,ts,json,yml,yaml}\" --write",
"format:md": "remark-preset-davidtheclark --format",
"format:check": "prettier \"**/*.{js,ts,json,yml,yaml}\" --check",
"typescript": "tsc",
"test": "jest --coverage",
"test:watch": "jest --watch",
"check:all": "npm run test && npm run typescript && npm run lint && npm run format:check",
"prepublishOnly": "npm run check:all && npm run build"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && npm run typescript && npm run test",
"pre-push": "npm run check:all"
}
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"prettier --write"
],
"*.{json,yml,yaml}": [
"prettier --write"
],
"*.md": [
"remark-preset-davidtheclark",
"remark-preset-davidtheclark --format"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/davidtheclark/cosmiconfig.git"
},
"keywords": [
"load",
"configuration",
"config"
],
"author": "David Clark ",
"contributors": [
"Bogdan Chadkin ",
"Suhas Karanth "
],
"license": "MIT",
"bugs": {
"url": "https://github.com/davidtheclark/cosmiconfig/issues"
},
"homepage": "https://github.com/davidtheclark/cosmiconfig#readme",
"prettier": {
"trailingComma": "all",
"arrowParens": "always",
"singleQuote": true,
"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"
}
}
],
"@babel/preset-typescript"
]
},
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.10.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/node": "^14.0.22",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"cross-env": "^7.0.2",
"del": "^5.1.0",
"del-cli": "^3.0.1",
"eslint": "^7.4.0",
"eslint-config-davidtheclark-node": "^0.2.2",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.18.0",
"eslint-plugin-node": "^11.1.0",
"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"
},
"engines": {
"node": ">=10"
}
}
cosmiconfig-7.1.0/src/ 0000775 0000000 0000000 00000000000 14333725373 0014620 5 ustar 00root root 0000000 0000000 cosmiconfig-7.1.0/src/Explorer.ts 0000664 0000000 0000000 00000006430 14333725373 0016773 0 ustar 00root root 0000000 0000000 import path from 'path';
import { ExplorerBase } from './ExplorerBase';
import { readFile } from './readFile';
import { cacheWrapper } from './cacheWrapper';
import { getDirectory } from './getDirectory';
import { CosmiconfigResult, ExplorerOptions, LoadedFileContent } from './types';
class Explorer extends ExplorerBase {
public constructor(options: ExplorerOptions) {
super(options);
}
public async search(
searchFrom: string = process.cwd(),
): Promise {
const startDirectory = await getDirectory(searchFrom);
const result = await this.searchFromDirectory(startDirectory);
return result;
}
private async searchFromDirectory(dir: string): Promise {
const absoluteDir = path.resolve(process.cwd(), dir);
const run = async (): Promise => {
const result = await this.searchDirectory(absoluteDir);
const nextDir = this.nextDirectoryToSearch(absoluteDir, result);
if (nextDir) {
return this.searchFromDirectory(nextDir);
}
const transformResult = await this.config.transform(result);
return transformResult;
};
if (this.searchCache) {
return cacheWrapper(this.searchCache, absoluteDir, run);
}
return run();
}
private async searchDirectory(dir: string): Promise {
for await (const place of this.config.searchPlaces) {
const placeResult = await this.loadSearchPlace(dir, place);
if (this.shouldSearchStopWithResult(placeResult) === true) {
return placeResult;
}
}
// config not found
return null;
}
private async loadSearchPlace(
dir: string,
place: string,
): Promise {
const filepath = path.join(dir, place);
const fileContents = await readFile(filepath);
const result = await this.createCosmiconfigResult(filepath, fileContents);
return result;
}
private async loadFileContent(
filepath: string,
content: string | null,
): Promise {
if (content === null) {
return null;
}
if (content.trim() === '') {
return undefined;
}
const loader = this.getLoaderEntryForFile(filepath);
const loaderResult = await loader(filepath, content);
return loaderResult;
}
private async createCosmiconfigResult(
filepath: string,
content: string | null,
): Promise {
const fileContent = await this.loadFileContent(filepath, content);
const result = this.loadedContentToCosmiconfigResult(filepath, fileContent);
return result;
}
public async load(filepath: string): Promise {
this.validateFilePath(filepath);
const absoluteFilePath = path.resolve(process.cwd(), filepath);
const runLoad = async (): Promise => {
const fileContents = await readFile(absoluteFilePath, {
throwNotFound: true,
});
const result = await this.createCosmiconfigResult(
absoluteFilePath,
fileContents,
);
const transformResult = await this.config.transform(result);
return transformResult;
};
if (this.loadCache) {
return cacheWrapper(this.loadCache, absoluteFilePath, runLoad);
}
return runLoad();
}
}
export { Explorer };
cosmiconfig-7.1.0/src/ExplorerBase.ts 0000664 0000000 0000000 00000007315 14333725373 0017571 0 ustar 00root root 0000000 0000000 import path from 'path';
import { loaders } from './loaders';
import { getPropertyByPath } from './getPropertyByPath';
import {
CosmiconfigResult,
ExplorerOptions,
ExplorerOptionsSync,
Cache,
LoadedFileContent,
} from './types';
import { Loader } from './index';
class ExplorerBase {
protected readonly loadCache?: Cache;
protected readonly searchCache?: Cache;
protected readonly config: T;
public constructor(options: T) {
if (options.cache === true) {
this.loadCache = new Map();
this.searchCache = new Map();
}
this.config = options;
this.validateConfig();
}
public clearLoadCache(): void {
if (this.loadCache) {
this.loadCache.clear();
}
}
public clearSearchCache(): void {
if (this.searchCache) {
this.searchCache.clear();
}
}
public clearCaches(): void {
this.clearLoadCache();
this.clearSearchCache();
}
private validateConfig(): void {
const config = this.config;
config.searchPlaces.forEach((place): void => {
const loaderKey = path.extname(place) || 'noExt';
const loader = config.loaders[loaderKey];
if (!loader) {
throw new Error(
`No loader specified for ${getExtensionDescription(
place,
)}, so searchPlaces item "${place}" is invalid`,
);
}
if (typeof loader !== 'function') {
throw new Error(
`loader for ${getExtensionDescription(
place,
)} is not a function (type provided: "${typeof loader}"), so searchPlaces item "${place}" is invalid`,
);
}
});
}
protected shouldSearchStopWithResult(result: CosmiconfigResult): boolean {
if (result === null) return false;
if (result.isEmpty && this.config.ignoreEmptySearchPlaces) return false;
return true;
}
protected nextDirectoryToSearch(
currentDir: string,
currentResult: CosmiconfigResult,
): string | null {
if (this.shouldSearchStopWithResult(currentResult)) {
return null;
}
const nextDir = nextDirUp(currentDir);
if (nextDir === currentDir || currentDir === this.config.stopDir) {
return null;
}
return nextDir;
}
private loadPackageProp(filepath: string, content: string): unknown {
const parsedContent = loaders.loadJson(filepath, content);
const packagePropValue = getPropertyByPath(
parsedContent,
this.config.packageProp,
);
return packagePropValue || null;
}
protected getLoaderEntryForFile(filepath: string): Loader {
if (path.basename(filepath) === 'package.json') {
const loader = this.loadPackageProp.bind(this);
return loader;
}
const loaderKey = path.extname(filepath) || 'noExt';
const loader = this.config.loaders[loaderKey];
if (!loader) {
throw new Error(
`No loader specified for ${getExtensionDescription(filepath)}`,
);
}
return loader;
}
protected loadedContentToCosmiconfigResult(
filepath: string,
loadedContent: LoadedFileContent,
): CosmiconfigResult {
if (loadedContent === null) {
return null;
}
if (loadedContent === undefined) {
return { filepath, config: undefined, isEmpty: true };
}
return { config: loadedContent, filepath };
}
protected validateFilePath(filepath: string): void {
if (!filepath) {
throw new Error('load must pass a non-empty string');
}
}
}
function nextDirUp(dir: string): string {
return path.dirname(dir);
}
function getExtensionDescription(filepath: string): string {
const ext = path.extname(filepath);
return ext ? `extension "${ext}"` : 'files without extensions';
}
export { ExplorerBase, getExtensionDescription };
cosmiconfig-7.1.0/src/ExplorerSync.ts 0000664 0000000 0000000 00000006237 14333725373 0017635 0 ustar 00root root 0000000 0000000 import path from 'path';
import { ExplorerBase } from './ExplorerBase';
import { readFileSync } from './readFile';
import { cacheWrapperSync } from './cacheWrapper';
import { getDirectorySync } from './getDirectory';
import {
CosmiconfigResult,
ExplorerOptionsSync,
LoadedFileContent,
} from './types';
class ExplorerSync extends ExplorerBase {
public constructor(options: ExplorerOptionsSync) {
super(options);
}
public searchSync(searchFrom: string = process.cwd()): CosmiconfigResult {
const startDirectory = getDirectorySync(searchFrom);
const result = this.searchFromDirectorySync(startDirectory);
return result;
}
private searchFromDirectorySync(dir: string): CosmiconfigResult {
const absoluteDir = path.resolve(process.cwd(), dir);
const run = (): CosmiconfigResult => {
const result = this.searchDirectorySync(absoluteDir);
const nextDir = this.nextDirectoryToSearch(absoluteDir, result);
if (nextDir) {
return this.searchFromDirectorySync(nextDir);
}
const transformResult = this.config.transform(result);
return transformResult;
};
if (this.searchCache) {
return cacheWrapperSync(this.searchCache, absoluteDir, run);
}
return run();
}
private searchDirectorySync(dir: string): CosmiconfigResult {
for (const place of this.config.searchPlaces) {
const placeResult = this.loadSearchPlaceSync(dir, place);
if (this.shouldSearchStopWithResult(placeResult) === true) {
return placeResult;
}
}
// config not found
return null;
}
private loadSearchPlaceSync(dir: string, place: string): CosmiconfigResult {
const filepath = path.join(dir, place);
const content = readFileSync(filepath);
const result = this.createCosmiconfigResultSync(filepath, content);
return result;
}
private loadFileContentSync(
filepath: string,
content: string | null,
): LoadedFileContent {
if (content === null) {
return null;
}
if (content.trim() === '') {
return undefined;
}
const loader = this.getLoaderEntryForFile(filepath);
const loaderResult = loader(filepath, content);
return loaderResult;
}
private createCosmiconfigResultSync(
filepath: string,
content: string | null,
): CosmiconfigResult {
const fileContent = this.loadFileContentSync(filepath, content);
const result = this.loadedContentToCosmiconfigResult(filepath, fileContent);
return result;
}
public loadSync(filepath: string): CosmiconfigResult {
this.validateFilePath(filepath);
const absoluteFilePath = path.resolve(process.cwd(), filepath);
const runLoadSync = (): CosmiconfigResult => {
const content = readFileSync(absoluteFilePath, { throwNotFound: true });
const cosmiconfigResult = this.createCosmiconfigResultSync(
absoluteFilePath,
content,
);
const transformResult = this.config.transform(cosmiconfigResult);
return transformResult;
};
if (this.loadCache) {
return cacheWrapperSync(this.loadCache, absoluteFilePath, runLoadSync);
}
return runLoadSync();
}
}
export { ExplorerSync };
cosmiconfig-7.1.0/src/cacheWrapper.ts 0000664 0000000 0000000 00000001222 14333725373 0017571 0 ustar 00root root 0000000 0000000 import { Cache, CosmiconfigResult } from './types';
async function cacheWrapper(
cache: Cache,
key: string,
fn: () => Promise,
): Promise {
const cached = cache.get(key);
if (cached !== undefined) {
return cached;
}
const result = await fn();
cache.set(key, result);
return result;
}
function cacheWrapperSync(
cache: Cache,
key: string,
fn: () => CosmiconfigResult,
): CosmiconfigResult {
const cached = cache.get(key);
if (cached !== undefined) {
return cached;
}
const result = fn();
cache.set(key, result);
return result;
}
export { cacheWrapper, cacheWrapperSync };
cosmiconfig-7.1.0/src/getDirectory.ts 0000664 0000000 0000000 00000001164 14333725373 0017636 0 ustar 00root root 0000000 0000000 import path from 'path';
import { isDirectory, isDirectorySync } from 'path-type';
async function getDirectory(filepath: string): Promise {
const filePathIsDirectory = await isDirectory(filepath);
if (filePathIsDirectory === true) {
return filepath;
}
const directory = path.dirname(filepath);
return directory;
}
function getDirectorySync(filepath: string): string {
const filePathIsDirectory = isDirectorySync(filepath);
if (filePathIsDirectory === true) {
return filepath;
}
const directory = path.dirname(filepath);
return directory;
}
export { getDirectory, getDirectorySync };
cosmiconfig-7.1.0/src/getPropertyByPath.ts 0000664 0000000 0000000 00000001572 14333725373 0020631 0 ustar 00root root 0000000 0000000 // Resolves property names or property paths defined with period-delimited
// strings or arrays of strings. Property names that are found on the source
// object are used directly (even if they include a period).
// Nested property names that include periods, within a path, are only
// understood in array paths.
function getPropertyByPath(
source: { [key: string]: unknown },
path: string | Array,
): unknown {
if (
typeof path === 'string' &&
Object.prototype.hasOwnProperty.call(source, path)
) {
return source[path];
}
const parsedPath = typeof path === 'string' ? path.split('.') : path;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return parsedPath.reduce((previous: any, key): unknown => {
if (previous === undefined) {
return previous;
}
return previous[key];
}, source);
}
export { getPropertyByPath };
cosmiconfig-7.1.0/src/index.ts 0000664 0000000 0000000 00000007760 14333725373 0016311 0 ustar 00root root 0000000 0000000 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import os from 'os';
import { Explorer } from './Explorer';
import { ExplorerSync } from './ExplorerSync';
import { loaders } from './loaders';
import {
Config,
CosmiconfigResult,
ExplorerOptions,
ExplorerOptionsSync,
Loaders,
LoadersSync,
} from './types';
type LoaderResult = Config | null;
export type Loader =
| ((filepath: string, content: string) => Promise)
| LoaderSync;
export type LoaderSync = (filepath: string, content: string) => LoaderResult;
export type Transform =
| ((CosmiconfigResult: CosmiconfigResult) => Promise)
| TransformSync;
export type TransformSync = (
CosmiconfigResult: CosmiconfigResult,
) => CosmiconfigResult;
interface OptionsBase {
packageProp?: string | Array;
searchPlaces?: Array;
ignoreEmptySearchPlaces?: boolean;
stopDir?: string;
cache?: boolean;
}
export interface Options extends OptionsBase {
loaders?: Loaders;
transform?: Transform;
}
export interface OptionsSync extends OptionsBase {
loaders?: LoadersSync;
transform?: TransformSync;
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function cosmiconfig(moduleName: string, options: Options = {}) {
const normalizedOptions: ExplorerOptions = normalizeOptions(
moduleName,
options,
);
const explorer = new Explorer(normalizedOptions);
return {
search: explorer.search.bind(explorer),
load: explorer.load.bind(explorer),
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(
moduleName,
options,
);
const explorerSync = new ExplorerSync(normalizedOptions);
return {
search: explorerSync.searchSync.bind(explorerSync),
load: explorerSync.loadSync.bind(explorerSync),
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,
): ExplorerOptionsSync;
function normalizeOptions(
moduleName: string,
options: Options,
): ExplorerOptions;
function normalizeOptions(
moduleName: string,
options: Options | OptionsSync,
): ExplorerOptions | ExplorerOptionsSync {
const defaults: ExplorerOptions | ExplorerOptionsSync = {
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,
transform: identity,
loaders: defaultLoaders,
};
const normalizedOptions: ExplorerOptions | ExplorerOptionsSync = {
...defaults,
...options,
loaders: {
...defaults.loaders,
...options.loaders,
},
};
return normalizedOptions;
}
export { cosmiconfig, cosmiconfigSync, defaultLoaders };
cosmiconfig-7.1.0/src/loaders.ts 0000664 0000000 0000000 00000002416 14333725373 0016624 0 ustar 00root root 0000000 0000000 /* 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;
const loadJs: LoaderSync = function loadJs(filepath) {
if (importFresh === undefined) {
importFresh = require('import-fresh');
}
const result = importFresh(filepath);
return result;
};
let parseJson: typeof parseJsonType;
const loadJson: LoaderSync = function loadJson(filepath, content) {
if (parseJson === undefined) {
parseJson = require('parse-json');
}
try {
const result = parseJson(content);
return result;
} catch (error) {
error.message = `JSON Error in ${filepath}:\n${error.message}`;
throw error;
}
};
let yaml: typeof yamlType;
const loadYaml: LoaderSync = function loadYaml(filepath, content) {
if (yaml === undefined) {
yaml = require('yaml');
}
try {
const result = yaml.parse(content, { prettyErrors: true });
return result;
} catch (error) {
error.message = `YAML Error in ${filepath}:\n${error.message}`;
throw error;
}
};
const loaders: LoadersSync = { loadJs, loadJson, loadYaml };
export { loaders };
cosmiconfig-7.1.0/src/readFile.ts 0000664 0000000 0000000 00000002350 14333725373 0016703 0 ustar 00root root 0000000 0000000 import fs from 'fs';
async function fsReadFileAsync(
pathname: string,
encoding: BufferEncoding,
): Promise {
return new Promise((resolve, reject): void => {
fs.readFile(pathname, encoding, (error, contents): void => {
if (error) {
reject(error);
return;
}
resolve(contents);
});
});
}
interface Options {
throwNotFound?: boolean;
}
async function readFile(
filepath: string,
options: Options = {},
): Promise {
const throwNotFound = options.throwNotFound === true;
try {
const content = await fsReadFileAsync(filepath, 'utf8');
return content;
} catch (error) {
if (
throwNotFound === false &&
(error.code === 'ENOENT' || error.code === 'EISDIR')
) {
return null;
}
throw error;
}
}
function readFileSync(filepath: string, options: Options = {}): string | null {
const throwNotFound = options.throwNotFound === true;
try {
const content = fs.readFileSync(filepath, 'utf8');
return content;
} catch (error) {
if (
throwNotFound === false &&
(error.code === 'ENOENT' || error.code === 'EISDIR')
) {
return null;
}
throw error;
}
}
export { readFile, readFileSync };
cosmiconfig-7.1.0/src/types.ts 0000664 0000000 0000000 00000001744 14333725373 0016342 0 ustar 00root root 0000000 0000000 import { Loader, LoaderSync, Options, OptionsSync } from './index';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Config = any;
export type CosmiconfigResult = {
config: Config;
filepath: string;
isEmpty?: boolean;
} | null;
// These are the user options with defaults applied.
/* eslint-disable @typescript-eslint/no-empty-interface */
export interface ExplorerOptions extends Required {}
export interface ExplorerOptionsSync extends Required {}
/* eslint-enable @typescript-eslint/no-empty-interface */
export type Cache = Map;
// An object value represents a config object.
// null represents that the loader did not find anything relevant.
// undefined represents that the loader found something relevant
// but it was empty.
export type LoadedFileContent = Config | null | undefined;
export interface Loaders {
[key: string]: Loader;
}
export interface LoadersSync {
[key: string]: LoaderSync;
}
cosmiconfig-7.1.0/test/ 0000775 0000000 0000000 00000000000 14333725373 0015010 5 ustar 00root root 0000000 0000000 cosmiconfig-7.1.0/test/caches.test.ts 0000664 0000000 0000000 00000042025 14333725373 0017567 0 ustar 00root root 0000000 0000000 import fs from 'fs';
import { TempDir } from './util';
import { cosmiconfig, cosmiconfigSync } from '../src';
const temp = new TempDir();
beforeEach(() => {
temp.clean();
temp.createDir('a/b/c/d/e/f/');
temp.createFile('a/b/c/d/package.json', '{ "false": "hope" }');
temp.createFile('a/b/c/d/.foorc', '{ "foundInD": true }');
temp.createFile('a/b/package.json', '{ "foo": { "foundInB": true } }');
});
afterEach(() => {
jest.restoreAllMocks();
});
afterAll(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
describe('cache is not used initially', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const cachedSearch = cosmiconfig('foo').search;
const result = await cachedSearch(searchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const cachedSearchSync = cosmiconfigSync('foo').search;
const result = cachedSearchSync(searchPath);
checkResult(readFileSpy, result);
});
});
describe('cache is used for already-visited directories', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
expect(readFileSpy).toHaveBeenCalledTimes(0);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const cachedSearch = cosmiconfig('foo').search;
// First pass, prime the cache ...
await cachedSearch(searchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = await cachedSearch(searchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const cachedSearchSync = cosmiconfigSync('foo').search;
// First pass, prime the cache ...
cachedSearchSync(searchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = cachedSearchSync(searchPath);
checkResult(readFileSpy, result);
});
});
describe('cache is used for already-loaded file', () => {
const loadPath = temp.absolutePath('a/b/c/d/.foorc');
const checkResult = (readFileSpy: any, result: any) => {
expect(readFileSpy).toHaveBeenCalledTimes(0);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const cachedLoad = cosmiconfig('foo').load;
// First pass, prime the cache ...
await cachedLoad(loadPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = await cachedLoad(loadPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const cachedLoadSync = cosmiconfigSync('foo').load;
// First pass, prime the cache ...
cachedLoadSync(loadPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = cachedLoadSync(loadPath);
checkResult(readFileSpy, result);
});
});
describe('cache is used when some directories in search are already visted', () => {
const firstSearchPath = temp.absolutePath('a/b/c/d/e');
const secondSearchPath = temp.absolutePath('a/b/c/d/e/f');
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const cachedSearch = cosmiconfig('foo').search;
// First pass, prime the cache ...
await cachedSearch(firstSearchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = await cachedSearch(secondSearchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const cachedSearchSync = cosmiconfigSync('foo').search;
// First pass, prime the cache ...
cachedSearchSync(firstSearchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = cachedSearchSync(secondSearchPath);
checkResult(readFileSpy, result);
});
});
describe('cache is not used when directly loading an unvisited file', () => {
const firstSearchPath = temp.absolutePath('a/b/c/d/e');
const loadPath = temp.absolutePath('a/b/package.json');
const checkResult = (readFileSpy: any, result: any) => {
expect(readFileSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/package.json'),
config: { foundInB: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo');
// First pass, prime the cache ...
await explorer.search(firstSearchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = await explorer.load(loadPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo');
// First pass, prime the cache ...
explorer.search(firstSearchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
const result = explorer.load(loadPath);
checkResult(readFileSpy, result);
});
});
describe('cache is not used in a new cosmiconfig instance', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
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);
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);
checkResult(readFileSpy, result);
});
});
describe('clears file cache on calling clearLoadCache', () => {
const loadPath = temp.absolutePath('a/b/c/d/.foorc');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/.foorc']);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo');
await explorer.load(loadPath);
readFileSpy.mockClear();
explorer.clearLoadCache();
const result = await explorer.load(loadPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo');
explorer.load(loadPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
explorer.clearLoadCache();
const result = explorer.load(loadPath);
checkResult(readFileSpy, result);
});
});
describe('clears file cache on calling clearCaches', () => {
const loadPath = temp.absolutePath('a/b/c/d/.foorc');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/.foorc']);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo');
await explorer.load(loadPath);
readFileSpy.mockClear();
explorer.clearCaches();
const result = await explorer.load(loadPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo');
explorer.load(loadPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
explorer.clearCaches();
const result = explorer.load(loadPath);
checkResult(readFileSpy, result);
});
});
describe('clears directory cache on calling clearSearchCache', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo');
await explorer.search(searchPath);
readFileSpy.mockClear();
explorer.clearSearchCache();
const result = await explorer.search(searchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo');
explorer.search(searchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
explorer.clearSearchCache();
const result = explorer.search(searchPath);
checkResult(readFileSpy, result);
});
});
describe('clears directory cache on calling clearCaches', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo');
await explorer.search(searchPath);
readFileSpy.mockClear();
explorer.clearCaches();
const result = await explorer.search(searchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo');
explorer.search(searchPath);
// Reset readFile mocks and search again.
readFileSpy.mockClear();
explorer.clearCaches();
const result = explorer.search(searchPath);
checkResult(readFileSpy, result);
});
});
describe('with cache disabled', () => {
const explorer = cosmiconfig('foo', { cache: false });
test('does not throw an error when clearFileCache is called', () => {
expect(() => explorer.clearLoadCache()).not.toThrow();
});
test('does not throw an error when clearDirectoryCache is called', () => {
expect(() => explorer.clearSearchCache()).not.toThrow();
});
test('does not throw an error when clearCaches is called', () => {
expect(() => explorer.clearCaches()).not.toThrow();
});
});
describe('with cache disabled, does not cache directory results', () => {
const searchPath = temp.absolutePath('a/b/c/d/e');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo', { cache: false });
await explorer.search(searchPath);
readFileSpy.mockClear();
const result = await explorer.search(searchPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo', { cache: false });
explorer.search(searchPath);
readFileSpy.mockClear();
const result = explorer.search(searchPath);
checkResult(readFileSpy, result);
});
});
describe('with cache disabled, does not cache file results', () => {
const loadPath = temp.absolutePath('a/b/c/d/.foorc');
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/.foorc']);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/.foorc'),
config: { foundInD: true },
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorer = cosmiconfig('foo', { cache: false });
await explorer.load(loadPath);
readFileSpy.mockClear();
const result = await explorer.load(loadPath);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const explorer = cosmiconfigSync('foo', { cache: false });
explorer.load(loadPath);
readFileSpy.mockClear();
const result = explorer.load(loadPath);
checkResult(readFileSpy, result);
});
});
cosmiconfig-7.1.0/test/failed-directories.test.ts 0000664 0000000 0000000 00000041622 14333725373 0022101 0 ustar 00root root 0000000 0000000 import fs from 'fs';
import { TempDir } from './util';
import {
cosmiconfig,
cosmiconfigSync,
defaultLoaders,
OptionsSync,
} from '../src';
const temp = new TempDir();
beforeEach(() => {
temp.clean();
temp.createDir('a/b/c/d/e/f/');
});
afterAll(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
describe('gives up if it cannot find the file', () => {
const startDir = temp.absolutePath('a/b');
const explorerOptions = { stopDir: temp.absolutePath('.') };
const checkResult = (statSpy: any, readFileSpy: any, result: any) => {
const statPath = temp.getSpyPathCalls(statSpy);
expect(statPath).toEqual(['a/b']);
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/package.json',
'a/b/.foorc',
'a/b/.foorc.json',
'a/b/.foorc.yaml',
'a/b/.foorc.yml',
'a/b/.foorc.js',
'a/b/.foorc.cjs',
'a/b/.config/foorc',
'a/b/.config/foorc.json',
'a/b/.config/foorc.yaml',
'a/b/.config/foorc.yml',
'a/b/.config/foorc.js',
'a/b/.config/foorc.cjs',
'a/b/foo.config.js',
'a/b/foo.config.cjs',
'a/package.json',
'a/.foorc',
'a/.foorc.json',
'a/.foorc.yaml',
'a/.foorc.yml',
'a/.foorc.js',
'a/.foorc.cjs',
'a/.config/foorc',
'a/.config/foorc.json',
'a/.config/foorc.yaml',
'a/.config/foorc.yml',
'a/.config/foorc.js',
'a/.config/foorc.cjs',
'a/foo.config.js',
'a/foo.config.cjs',
'package.json',
'.foorc',
'.foorc.json',
'.foorc.yaml',
'.foorc.yml',
'.foorc.js',
'.foorc.cjs',
'.config/foorc',
'.config/foorc.json',
'.config/foorc.yaml',
'.config/foorc.yml',
'.config/foorc.js',
'.config/foorc.cjs',
'foo.config.js',
'foo.config.cjs',
]);
expect(result).toBe(null);
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const statSpy = jest.spyOn(fs, 'stat');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(statSpy, readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const statSpy = jest.spyOn(fs, 'statSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(statSpy, readFileSpy, result);
});
});
describe('stops at stopDir and gives up', () => {
const startDir = temp.absolutePath('a/b');
const explorerOptions = { stopDir: temp.absolutePath('a') };
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/package.json',
'a/b/.foorc',
'a/b/.foorc.json',
'a/b/.foorc.yaml',
'a/b/.foorc.yml',
'a/b/.foorc.js',
'a/b/.foorc.cjs',
'a/b/.config/foorc',
'a/b/.config/foorc.json',
'a/b/.config/foorc.yaml',
'a/b/.config/foorc.yml',
'a/b/.config/foorc.js',
'a/b/.config/foorc.cjs',
'a/b/foo.config.js',
'a/b/foo.config.cjs',
'a/package.json',
'a/.foorc',
'a/.foorc.json',
'a/.foorc.yaml',
'a/.foorc.yml',
'a/.foorc.js',
'a/.foorc.cjs',
'a/.config/foorc',
'a/.config/foorc.json',
'a/.config/foorc.yaml',
'a/.config/foorc.yml',
'a/.config/foorc.js',
'a/.config/foorc.cjs',
'a/foo.config.js',
'a/foo.config.cjs',
]);
expect(result).toBe(null);
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('throws error for invalid YAML in rc file', () => {
beforeEach(() => {
temp.createFile('a/b/.foorc', 'found: true: broken');
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = { stopDir: temp.absolutePath('a') };
const expectedError = `YAML Error in ${temp.absolutePath(
'a/b/.foorc',
)}:\nNested mappings are not allowed in compact mappings at line 1, column 8:`;
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('throws error for invalid JSON in extensionless rc file loaded as JSON', () => {
beforeEach(() => {
temp.createFile('a/b/.foorc', 'found: true: broken');
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
loaders: {
noExt: defaultLoaders['.json'],
},
};
const expectedError = 'JSON Error in';
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('throws error for invalid package.json', () => {
beforeEach(() => {
temp.createFile('a/b/package.json', '{ "foo": "bar", }');
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = { stopDir: temp.absolutePath('a') };
const expectedError = 'JSON Error in';
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('throws error for invalid JS in .config.js file', () => {
beforeEach(() => {
temp.createFile(
'a/b/foo.config.js',
'module.exports = { found: true: false,',
);
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = { stopDir: temp.absolutePath('a') };
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow();
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow();
});
});
describe('throws error for invalid JSON in .foorc.json', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.json', '{ "found": true,, }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
const expectedError = 'JSON Error in';
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('throws error for invalid YAML in .foorc.yml', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.yml', 'found: thing: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
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:`;
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('searching for rc files with specified extensions, throws error for invalid JS in .foorc.js', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.js', 'module.exports = found: true };');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc',
'.foorc.json',
'.foorc.yaml',
'.foorc.yml',
'.foorc.js',
'foo.config.js',
],
};
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow();
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow();
});
});
describe('without ignoring empty files, returns an empty config result for an empty rc file', () => {
beforeEach(() => {
temp.createFile('a/b/.foorc', '');
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/.foorc'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('without ignoring empty files, returns an empty config result for an empty .config.js file', () => {
beforeEach(() => {
temp.createFile('a/b/foo.config.js', '');
});
const startDir = temp.absolutePath('a/b');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/foo.config.js'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('without ignoring empty files, returns an empty config result for an empty .json rc file', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.json', '');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.json'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('returns an empty config result for an empty .yaml rc file', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.yaml', '');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.yaml'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('without ignoring empty configs and searching for rc files with specified extensions, returns an empty config result for an empty .js rc file', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.js', '');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
searchPlaces: [
'package.json',
'.foorc',
'.foorc.json',
'.foorc.yaml',
'.foorc.yml',
'.foorc.js',
'foo.config.js',
],
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.js'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('without ignoring empty configs and searching for rc files with specified extensions, returns an empty config result for an empty .js rc file with whitespace', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.js', ' \t\r\v\n\f');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('a'),
ignoreEmptySearchPlaces: false,
searchPlaces: [
'package.json',
'.foorc',
'.foorc.json',
'.foorc.yaml',
'.foorc.yml',
'.foorc.js',
'foo.config.js',
],
};
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.js'),
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(result);
});
});
describe('throws error if a file in searchPlaces does not have a corresponding loader', () => {
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc',
'.foorc.things',
'.foorc.js',
'foo.config.js',
],
};
test('on instantiation', () => {
expect(() => cosmiconfigSync('foo', explorerOptions)).toThrow(
'No loader specified for extension ".things"',
);
});
});
describe('throws error if an extensionless file in searchPlaces does not have a corresponding loader', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc', '{ "foo": "bar" }');
});
const explorerOptions: OptionsSync = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['package.json', '.foorc'],
loaders: {
// @ts-ignore
noExt: undefined,
},
};
test('on instantiation', () => {
expect(() => cosmiconfigSync('foo', explorerOptions)).toThrow(
'No loader specified for files without extensions',
);
});
});
describe('does not swallow errors from custom loaders', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.js', 'module.exports = {};');
});
const expectedError = new Error('Failed to load JS');
const loadJs = () => {
throw expectedError;
};
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['package.json', '.foorc', '.foorc.js'],
loaders: {
'.js': loadJs,
},
};
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('foo', explorerOptions).search(startDir),
).toThrow(expectedError);
});
});
describe('errors not swallowed when async custom loader throws them', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
});
const expectedError = new Error('loadThingsAsync error');
const loadThingsAsync = () => {
throw expectedError;
};
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
loaders: {
'.things': loadThingsAsync,
},
};
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
});
describe('errors not swallowed when async custom loader rejects', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
});
const expectedError = new Error('loadThingsAsync error');
const loadThingsAsync = async () => {
throw expectedError;
};
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
loaders: {
'.things': loadThingsAsync,
},
};
test('async', async () => {
await expect(
cosmiconfig('foo', explorerOptions).search(startDir),
).rejects.toThrow(expectedError);
});
});
cosmiconfig-7.1.0/test/failed-files.test.ts 0000664 0000000 0000000 00000014701 14333725373 0020665 0 ustar 00root root 0000000 0000000 import { TempDir } from './util';
import { cosmiconfig, cosmiconfigSync } from '../src';
const temp = new TempDir();
beforeEach(() => {
temp.clean();
});
afterAll(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
describe('throws error if defined file does not exist', () => {
const file = temp.absolutePath('does/not/exist');
const expectedError = 'ENOENT: no such file or directory';
test('async', async () => {
await expect(cosmiconfig('failed-files-tests').load(file)).rejects.toThrow(
expectedError,
);
});
test('sync', () => {
expect(() => cosmiconfigSync('failed-files-tests').load(file)).toThrow(
expectedError,
);
});
});
describe('throws error if defined JSON file has syntax error', () => {
beforeEach(() => {
temp.createFile('foo-invalid.json', '{ "foo": true: }');
});
const file = temp.absolutePath('foo-invalid.json');
const expectedError = 'JSON Error in';
test('async', async () => {
await expect(cosmiconfig('failed-files-tests').load(file)).rejects.toThrow(
expectedError,
);
});
test('sync', () => {
expect(() => cosmiconfigSync('failed-files-tests').load(file)).toThrow(
expectedError,
);
});
});
describe('throws error if defined YAML file has syntax error', () => {
beforeEach(() => {
temp.createFile('foo-invalid.yaml', 'foo: true: false');
});
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:`;
test('async', async () => {
await expect(cosmiconfig('failed-files-tests').load(file)).rejects.toThrow(
expectedError,
);
});
test('sync', () => {
expect(() => cosmiconfigSync('failed-files-tests').load(file)).toThrow(
expectedError,
);
});
});
describe('throws error if defined JS file has syntax error', () => {
beforeEach(() => {
temp.createFile('foo-invalid.js', 'module.exports = { foo }');
});
const file = temp.absolutePath('foo-invalid.js');
const expectedError = 'foo is not defined';
test('async', async () => {
await expect(cosmiconfig('failed-files-tests').load(file)).rejects.toThrow(
expectedError,
);
});
test('sync', () => {
expect(() => cosmiconfigSync('failed-files-tests').load(file)).toThrow(
expectedError,
);
});
});
describe('returns an empty config result for empty file, format JS', () => {
beforeEach(() => {
temp.createFile('foo-empty.js', '');
});
const file = temp.absolutePath('foo-empty.js');
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: file,
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('failed-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('failed-files-tests').load(file);
checkResult(result);
});
});
describe('returns an empty config result for empty file, format JSON', () => {
beforeEach(() => {
temp.createFile('foo-empty.json', '');
});
const file = temp.absolutePath('foo-empty.json');
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: file,
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('failed-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('failed-files-tests').load(file);
checkResult(result);
});
});
describe('returns an empty config result for empty file, format YAML', () => {
beforeEach(() => {
temp.createFile('foo-empty.yaml', '');
});
const file = temp.absolutePath('foo-empty.yaml');
const checkResult = (result: any) => {
expect(result).toEqual({
config: undefined,
filepath: file,
isEmpty: true,
});
};
test('async', async () => {
const result = await cosmiconfig('failed-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('failed-files-tests').load(file);
checkResult(result);
});
});
describe('throws an error if no configPath was specified and load is called without an argument', () => {
const expectedError = 'load must pass a non-empty string';
test('async', async () => {
// @ts-ignore
await expect(cosmiconfig('not_exist_rc_name').load()).rejects.toThrow(
expectedError,
);
});
test('sync', () => {
// @ts-ignore
expect(() => cosmiconfigSync('not_exist_rc_name').load()).toThrow(
expectedError,
);
});
});
describe('errors not swallowed when async custom loader throws them', () => {
const file = temp.absolutePath('.foorc.things');
beforeEach(() => {
temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const expectedError = new Error('loadThingsAsync error');
const loadThingsAsync = async () => {
throw expectedError;
};
const explorerOptions = {
loaders: {
'.things': loadThingsAsync,
},
};
test('async', async () => {
await expect(
cosmiconfig('not_exist_rc_name', explorerOptions).load(file),
).rejects.toThrow(expectedError);
});
});
describe('errors not swallowed when async custom loader rejects', () => {
const file = temp.absolutePath('.foorc.things');
beforeEach(() => {
temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const expectedError = new Error('loadThingsAsync error');
const loadThingsAsync = async () => {
throw expectedError;
};
const explorerOptions = {
loaders: {
'.things': loadThingsAsync,
},
};
test('async', async () => {
await expect(
cosmiconfig('not_exist_rc_name', explorerOptions).load(file),
).rejects.toThrow(expectedError);
});
});
describe('errors if no loader is set for loaded file', () => {
const file = temp.absolutePath('.foorc.things');
beforeEach(() => {
temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const explorerOptions = {
loaders: {},
};
test('async', async () => {
await expect(
cosmiconfig('not_exist_rc_name', explorerOptions).load(file),
).rejects.toThrow('No loader specified for extension ".things"');
});
test('sync', () => {
expect(() =>
cosmiconfigSync('not_exist_rc_name', explorerOptions).load(file),
).toThrow('No loader specified for extension ".things"');
});
});
cosmiconfig-7.1.0/test/getDirectory.test.ts 0000664 0000000 0000000 00000004073 14333725373 0021006 0 ustar 00root root 0000000 0000000 import path from 'path';
import { getDirectory, getDirectorySync } from '../src/getDirectory';
describe('returns the searchPath if it is a directory', () => {
const subject = __dirname;
const checkResult = (result: any) => {
expect(result).toBe(subject);
};
test('async', async () => {
const result = await getDirectory(subject);
checkResult(result);
});
test('sync', () => {
const result = getDirectorySync(subject);
checkResult(result);
});
});
describe('returns the parent directory if it is a file', () => {
const subject = __filename;
const checkResult = (result: any) => {
expect(result).toBe(__dirname);
};
test('async', async () => {
const result = await getDirectory(subject);
checkResult(result);
});
test('sync', () => {
const result = getDirectorySync(subject);
checkResult(result);
});
});
// https://github.com/davidtheclark/cosmiconfig/issues/63
describe('handles process.cwd()/stdin', () => {
const subject = path.join(process.cwd(), 'stdin');
const checkResult = (result: string) => {
expect(result).toBe(process.cwd());
};
test('async', async () => {
const result = await getDirectory(subject);
checkResult(result);
});
test('sync', () => {
const result = getDirectorySync(subject);
checkResult(result);
});
});
test('returns a promise if sync is not true', async () => {
// Although we explicitly pass `false`, the result will be a promise even
// if a value was not passed, because it would be falsy and not exactly
// equal to `true`.
const result = getDirectory(__dirname);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
expect(result).toBeInstanceOf(Promise);
await result;
});
test('propagates error thrown by is-directory in sync', () => {
// @ts-ignore
expect(() => getDirectorySync(null)).toThrow('Expected a string, got object');
});
test('rejects with the error thrown by is-directory in async', async () => {
// @ts-ignore
await expect(getDirectory(null)).rejects.toThrow(
'Expected a string, got object',
);
});
cosmiconfig-7.1.0/test/getPropertyByPath.test.ts 0000664 0000000 0000000 00000004377 14333725373 0022005 0 ustar 00root root 0000000 0000000 import { getPropertyByPath } from '../src/getPropertyByPath';
const source = {
ant: {
beetle: {
cootie: {
flea: 'foo',
},
louse: {
vermin: 'bar',
},
},
'fancy.name': {
'another.fancy.name': 9,
},
},
'ant.beetle.cootie': 333,
};
describe('with a property name that includes a period', () => {
test('does not treat it as a period-delimited path', () => {
expect(getPropertyByPath(source, 'ant.beetle.cootie')).toBe(333);
});
});
describe('with period-delimited string path', () => {
test('returns a defined value', () => {
expect(getPropertyByPath(source, 'ant')).toBe(source.ant);
expect(getPropertyByPath(source, 'ant.beetle.cootie.flea')).toBe('foo');
expect(getPropertyByPath(source, 'ant.beetle.louse')).toBe(
source.ant.beetle.louse,
);
});
test('returns undefined', () => {
expect(getPropertyByPath(source, 'beetle')).toBeUndefined();
expect(
getPropertyByPath(source, 'ant.beetle.cootie.fleeee'),
).toBeUndefined();
expect(getPropertyByPath(source, 'ant.beetle.vermin')).toBeUndefined();
expect(getPropertyByPath(source, 'ant.fancy.name')).toBeUndefined();
});
});
describe('with array path', () => {
test('returns a defined value', () => {
expect(getPropertyByPath(source, ['ant'])).toBe(source.ant);
expect(getPropertyByPath(source, ['ant', 'beetle', 'cootie', 'flea'])).toBe(
'foo',
);
expect(getPropertyByPath(source, ['ant', 'beetle', 'louse'])).toBe(
source.ant.beetle.louse,
);
});
test('returns undefined', () => {
expect(getPropertyByPath(source, ['beetle'])).toBeUndefined();
expect(
getPropertyByPath(source, ['ant', 'beetle', 'cootie', 'fleeee']),
).toBeUndefined();
expect(
getPropertyByPath(source, ['ant', 'beetle', 'vermin']),
).toBeUndefined();
});
test('handles property names with periods', () => {
expect(
getPropertyByPath(source, ['ant', 'fancy.name', 'another.fancy.name']),
).toBe(9);
expect(
getPropertyByPath(source, [
'ant',
'fancy.name',
'another.fancy.name',
'foo',
]),
).toBeUndefined();
expect(getPropertyByPath(source, ['ant', 'fancy.namez'])).toBeUndefined();
});
});
cosmiconfig-7.1.0/test/index.test.ts 0000664 0000000 0000000 00000015770 14333725373 0017457 0 ustar 00root root 0000000 0000000 /* eslint-disable @typescript-eslint/no-extraneous-class,@typescript-eslint/explicit-member-accessibility,@typescript-eslint/no-empty-function */
import os from 'os';
import { TempDir } from './util';
import {
cosmiconfig as cosmiconfigModule,
cosmiconfigSync as cosmiconfigSyncModule,
defaultLoaders,
LoaderSync,
} from '../src';
import { Loaders } from '../src/types';
const temp = new TempDir();
function getLoaderFunctionsByName(loaders: Loaders) {
const loaderFunctionsByName = Object.entries(loaders).reduce(
(acc, [extension, loader]) => {
return {
...acc,
[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;
describe('cosmiconfig', () => {
const moduleName = 'foo';
beforeEach(() => {
temp.clean();
createExplorerMock = jest.fn();
jest.doMock('../src/Explorer', () => {
return {
Explorer: class FakeExplorer {
constructor(options: Parameters[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[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(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
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,
});
};
test('async', () => {
cosmiconfig(moduleName);
checkResult(createExplorerMock);
});
test('sync', () => {
cosmiconfigSync(moduleName);
checkResult(createExplorerSyncMock);
});
});
describe('defaults transform to sync identity function', () => {
const checkResult = (mock: jest.SpyInstance) => {
const explorerOptions = mock.mock.calls[0][0];
const x = {};
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-floating-promises
expect(explorerOptions.transform(x)).toBe(x);
};
test('async', () => {
cosmiconfig(moduleName);
checkResult(createExplorerMock);
});
test('sync', () => {
cosmiconfigSync(moduleName);
checkResult(createExplorerSyncMock);
});
});
describe('creates explorer with preference for given options over defaults', () => {
beforeEach(() => {
temp.createFile('foo.json', '{ "foo": true }');
});
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 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,
});
};
test('async', () => {
cosmiconfig(moduleName, options);
checkResult(createExplorerMock);
});
test('sync', () => {
cosmiconfigSync(moduleName, options);
checkResult(createExplorerSyncMock);
});
});
describe('errors if loader is not passed a function', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
});
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
loaders: {
'.things': 1,
},
};
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
cosmiconfig('foo', explorerOptions),
).toThrow(expectedError);
});
test('sync', () => {
expect(() =>
// @ts-ignore
cosmiconfigSync('foo', explorerOptions),
).toThrow(expectedError);
});
});
test('cannot mutate default loaders', () => {
const expectedError = "Cannot delete property '.js' of #";
// @ts-ignore
expect(() => delete defaultLoaders['.js']).toThrow(expectedError);
});
});
cosmiconfig-7.1.0/test/successful-directories.test.ts 0000664 0000000 0000000 00000130274 14333725373 0023036 0 ustar 00root root 0000000 0000000 import fs from 'fs';
import { TempDir } from './util';
import { cosmiconfig, cosmiconfigSync, defaultLoaders } from '../src';
const temp = new TempDir();
beforeEach(() => {
temp.clean();
temp.createDir('a/b/c/d/e/f/');
});
afterAll(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
describe('finds rc file in third searched dir, with a package.json lacking prop', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/package.json', '{ "false": "hope" }');
temp.createFile('a/b/c/d/.foorc', '{ "found": true }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/package.json',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/.foorc'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds package.json prop in second searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/package.json',
'{ "author": "Todd", "foo": { "found": true } }',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/package.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/package.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds package.json with nested packageProp in second searched dir', () => {
beforeEach(() => {
// First package.json exists but does not include the nested packageProp.
temp.createFile(
'a/b/c/d/e/f/package.json',
'{ "author": "Todd", "configs": { "notYourPkg": { "yes": "ofcourse" } } }',
);
temp.createFile(
'a/b/c/d/e/package.json',
'{ "author": "Todd", "configs": { "pkg": { "please": "no" } } }',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
packageProp: 'configs.pkg',
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/package.json',
]);
expect(result).toEqual({
config: { please: 'no' },
filepath: temp.absolutePath('a/b/c/d/e/package.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds JS file in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/foo.config.js',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/foo.config.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds CJS file in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/foo.config.cjs',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/foo.config.cjs'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds .foorc.js file in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.js',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds .foorc.cjs file in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.cjs',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.cjs'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.config/foorc', 'found: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc.json file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.config/foorc.json', '{ "found": true }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc.yaml file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.config/foorc.yaml', 'found: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc.yaml'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc.yml file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.config/foorc.yml', 'found: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc.yml'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc.js file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.config/foorc.js',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe("finds foorc.cjs file in first searched dir's .config subdir", () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.config/foorc.cjs',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.config/foorc.cjs'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('skips over empty file to find JS file in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/foo.config.js',
'module.exports = { found: true };',
);
temp.createFile('a/b/c/d/e/f/.foorc', '');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = { stopDir: temp.absolutePath('.') };
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/foo.config.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds package.json in second dir searched, with alternate names', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/package.json', '{ "heeha": { "found": true } }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
packageProp: 'heeha',
searchPlaces: ['package.json', '.wowza', 'wowzaConfig.js'],
};
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/.wowza',
'a/b/c/d/e/f/wowzaConfig.js',
'a/b/c/d/e/package.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/package.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds rc file in third searched dir, skipping packageProp, parsing extensionless files as JSON', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/.foorc', '{ "found": true }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
loaders: {
noExt: defaultLoaders['.json'],
},
searchPlaces: ['.foorc', 'foo.config.js'],
};
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/f/.foorc',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/foo.config.js',
'a/b/c/d/.foorc',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/.foorc'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds package.json file in second searched dir, skipping JS and RC files', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/package.json',
'{ "author": "Todd", "foo": { "found": true } }',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
searchPlaces: ['package.json'],
};
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/package.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/package.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds .foorc.json in second searched dir', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/.foorc.json', '{ "found": true }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/.foorc.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds .foorc.yaml in first searched dir', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.yaml', 'found: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.yaml'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds .foorc.yml in first searched dir', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/f/.foorc.yml', 'found: true');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.yml'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('adding myfooconfig.js to searchPlaces, finds it in first searched dir', () => {
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/myfooconfig.js',
'module.exports = { found: true };',
);
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc',
'.foorc.json',
'.foorc.yaml',
'.foorc.yml',
'.foorc.cjs',
'foo.config.cjs',
'.foorc.js',
'foo.config.js',
'myfooconfig.js',
],
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/myfooconfig.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/f/myfooconfig.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('finds JS file traversing from cwd', () => {
const originalCwd = process.cwd();
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/foo.config.js',
'module.exports = { found: true };',
);
process.chdir(temp.absolutePath('a/b/c/d/e/f'));
});
afterEach(() => {
process.chdir(originalCwd);
});
const explorerOptions = {
stopDir: temp.absolutePath('.'),
};
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',
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/.foorc.yaml',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.js',
'a/b/c/d/e/f/.foorc.cjs',
'a/b/c/d/e/f/.config/foorc',
'a/b/c/d/e/f/.config/foorc.json',
'a/b/c/d/e/f/.config/foorc.yaml',
'a/b/c/d/e/f/.config/foorc.yml',
'a/b/c/d/e/f/.config/foorc.js',
'a/b/c/d/e/f/.config/foorc.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yaml',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.js',
'a/b/c/d/e/.foorc.cjs',
'a/b/c/d/e/.config/foorc',
'a/b/c/d/e/.config/foorc.json',
'a/b/c/d/e/.config/foorc.yaml',
'a/b/c/d/e/.config/foorc.yml',
'a/b/c/d/e/.config/foorc.js',
'a/b/c/d/e/.config/foorc.cjs',
'a/b/c/d/e/foo.config.js',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/foo.config.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search();
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search();
checkResult(readFileSpy, result);
});
});
describe('searchPlaces can include subdirectories', () => {
beforeEach(() => {
temp.createFile('a/b/c/d/e/.config/.foorc.json', '{ "found": true }');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'.foorc.json',
'package.json',
'.config/.foorc.json',
'.config/foo/config.json',
],
};
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/c/d/e/f/.foorc.json',
'a/b/c/d/e/f/package.json',
'a/b/c/d/e/f/.config/.foorc.json',
'a/b/c/d/e/f/.config/foo/config.json',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.config/.foorc.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/b/c/d/e/.config/.foorc.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('directories with the same name as a search place are not treated as files', () => {
beforeEach(() => {
temp.createFile('a/.foorc.json', '{ "found": true }');
temp.createDir('a/b/package.json/c');
});
const startDir = temp.absolutePath('a/b/package.json/c');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['package.json', '.foorc.json'],
};
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual([
'a/b/package.json/c/package.json',
'a/b/package.json/c/.foorc.json',
'a/b/package.json/package.json',
'a/b/package.json/.foorc.json',
'a/b/package.json',
'a/b/.foorc.json',
'a/package.json',
'a/.foorc.json',
]);
expect(result).toEqual({
config: { found: true },
filepath: temp.absolutePath('a/.foorc.json'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('custom loaders allow non-default file types', () => {
const loadThings = (filepath: any, content: any) => {
return {
things: content
.split('\n')
.map((x: any) => x.trim())
.filter((x: any) => !!x),
};
};
const loadGrumbly = () => ({ grumbly: true });
beforeEach(() => {
temp.createFile('a/b/c/d/e/.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc.json',
'.foorc.yml',
'.foorc.things',
'.foorc.grumbly',
],
loaders: {
'.things': loadThings,
'.grumbly': loadGrumbly,
},
};
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.json',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.things',
'a/b/c/d/e/f/.foorc.grumbly',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.things',
]);
expect(result).toEqual({
config: { things: ['one', 'two', 'three', 'four'] },
filepath: temp.absolutePath('a/b/c/d/e/.foorc.things'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('adding custom loaders allows for default and non-default file types', () => {
const loadThings = (filepath: any, content: any) => {
return {
things: content
.split('\n')
.map((x: any) => x.trim())
.filter((x: any) => !!x),
};
};
const loadGrumbly = () => ({ grumbly: true });
beforeEach(() => {
temp.createFile('a/b/c/d/e/.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc.json',
'.foorc.yml',
'.foorc.things',
'.foorc.grumbly',
],
loaders: {
'.things': loadThings,
'.grumbly': loadGrumbly,
},
};
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.json',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/f/.foorc.things',
'a/b/c/d/e/f/.foorc.grumbly',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/.foorc.yml',
'a/b/c/d/e/.foorc.things',
]);
expect(result).toEqual({
config: { things: ['one', 'two', 'three', 'four'] },
filepath: temp.absolutePath('a/b/c/d/e/.foorc.things'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('defaults loaders can be overridden', () => {
const loadGrumbly = () => ({ grumbly: true });
beforeEach(() => {
temp.createFile('a/b/c/d/e/foo.config.js', 'foo');
});
const startDir = temp.absolutePath('a/b/c/d/e/f');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: [
'package.json',
'.foorc.json',
'foo.config.cjs',
'foo.config.js',
'.foorc.yml',
],
loaders: {
'.js': loadGrumbly,
},
};
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.json',
'a/b/c/d/e/f/foo.config.cjs',
'a/b/c/d/e/f/foo.config.js',
'a/b/c/d/e/f/.foorc.yml',
'a/b/c/d/e/package.json',
'a/b/c/d/e/.foorc.json',
'a/b/c/d/e/foo.config.cjs',
'a/b/c/d/e/foo.config.js',
]);
expect(result).toEqual({
config: { grumbly: true },
filepath: temp.absolutePath('a/b/c/d/e/foo.config.js'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('custom loaders can be async', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
let loadThingsSync: any;
let loadThingsAsync: any;
const baseOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
};
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
loadThingsSync = jest.fn(() => ({ things: true }));
loadThingsAsync = jest.fn(async () => ({ things: true }));
});
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/e/f/.foorc.things']);
expect(result).toEqual({
config: { things: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.things'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const explorerOptions = {
...baseOptions,
loaders: {
'.things': loadThingsAsync,
},
};
const result = await cosmiconfig('foo', explorerOptions).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 result = cosmiconfigSync('foo', explorerOptions).search(startDir);
expect(loadThingsSync).toHaveBeenCalled();
expect(loadThingsAsync).not.toHaveBeenCalled();
checkResult(readFileSpy, result);
});
});
describe('a custom loader entry can include just an async loader', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
});
const loadThingsAsync = async () => ({ things: true });
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
loaders: {
'.things': loadThingsAsync,
},
};
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/e/f/.foorc.things']);
expect(result).toEqual({
config: { things: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.things'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('a custom loader entry can include only a sync loader and work for both sync and async functions', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/.foorc.things',
'one\ntwo\nthree\t\t\n four\n',
);
});
const loadThingsSync = () => {
return { things: true };
};
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['.foorc.things'],
loaders: {
'.things': loadThingsSync,
},
};
const checkResult = (readFileSpy: any, result: any) => {
const filesChecked = temp.getSpyPathCalls(readFileSpy);
expect(filesChecked).toEqual(['a/b/c/d/e/f/.foorc.things']);
expect(result).toEqual({
config: { things: true },
filepath: temp.absolutePath('a/b/c/d/e/f/.foorc.things'),
});
};
test('async', async () => {
const readFileSpy = jest.spyOn(fs, 'readFile');
const result = await cosmiconfig('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
test('sync', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const result = cosmiconfigSync('foo', explorerOptions).search(startDir);
checkResult(readFileSpy, result);
});
});
describe('works fine if sync loader returns a Promise from a JS file', () => {
const startDir = temp.absolutePath('a/b/c/d/e/f');
beforeEach(() => {
temp.createFile(
'a/b/c/d/e/f/bar.config.js',
'module.exports = Promise.resolve({ a: 1 });',
);
});
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['bar.config.js'],
};
test('sync', async () => {
const result = cosmiconfigSync('bar', explorerOptions).search(startDir);
expect(result).toEqual({
filepath: temp.absolutePath('a/b/c/d/e/f/bar.config.js'),
config: expect.any(Promise),
});
if (result === null) {
throw new Error('is null');
}
const resolvedConfig = await result.config;
expect(resolvedConfig).toEqual({ a: 1 });
});
});
cosmiconfig-7.1.0/test/successful-files.test.ts 0000664 0000000 0000000 00000030377 14333725373 0021627 0 ustar 00root root 0000000 0000000 import { TempDir } from './util';
import { cosmiconfig, cosmiconfigSync } from '../src';
const temp = new TempDir();
beforeEach(() => {
temp.clean();
});
afterAll(() => {
// Remove temp.dir created for tests
temp.deleteTempDir();
});
describe('loads defined JSON config path', () => {
beforeEach(() => {
temp.createFile('foo.json', '{ "foo": true }');
});
const file = temp.absolutePath('foo.json');
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads defined YAML config path', () => {
beforeEach(() => {
temp.createFile('foo.yaml', 'foo: true');
});
const file = temp.absolutePath('foo.yaml');
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads defined JS config path', () => {
beforeEach(() => {
temp.createFile('foo.js', 'module.exports = { foo: true };');
});
const file = temp.absolutePath('foo.js');
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads modularized JS config path', () => {
beforeEach(() => {
temp.createFile('foo.js', 'module.exports = { foo: true };');
temp.createFile('foo-module.js', 'module.exports = require("./foo");');
});
const file = temp.absolutePath('foo-module.js');
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads yaml-like JS config path', () => {
beforeEach(() => {
temp.createFile('foo-yaml-like.js', 'module.exports = { foo: true };');
});
const file = temp.absolutePath('foo-yaml-like.js');
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads package prop when configPath is package.json', () => {
beforeEach(() => {
temp.createFile(
'package.json',
`{
"foo": {
"bar": "baz"
},
"otherPackage": {
"please": "no"
}
}`,
);
});
const configPath = temp.absolutePath('package.json');
const checkResult = (result: any, expectedConfig: any) => {
expect(result.config).toEqual(expectedConfig);
expect(result.filepath).toBe(configPath);
};
describe('default package prop', () => {
const expectedConfig = { bar: 'baz' };
test('async', async () => {
const result = await cosmiconfig('foo').load(configPath);
checkResult(result, expectedConfig);
});
test('sync', () => {
const result = cosmiconfigSync('foo').load(configPath);
checkResult(result, expectedConfig);
});
});
describe('specified packageProp', () => {
const expectedConfig = { please: 'no' };
const explorerOptions = { packageProp: 'otherPackage' };
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).load(configPath);
checkResult(result, expectedConfig);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).load(configPath);
checkResult(result, expectedConfig);
});
});
describe('nested packageProp', () => {
const expectedConfig = 'baz';
const explorerOptions = { packageProp: 'foo.bar' };
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).load(configPath);
checkResult(result, expectedConfig);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).load(configPath);
checkResult(result, expectedConfig);
});
});
describe('inaccurate packageProp returns undefined, does not error', () => {
const options = { packageProp: 'otherrrPackage' };
test('async', async () => {
const result = await cosmiconfig('foo', options).load(configPath);
expect(result).toBeNull();
});
test('sync', () => {
const result = cosmiconfigSync('foo', options).load(configPath);
expect(result).toBeNull();
});
});
describe('inaccurate nested packageProp returns undefined, does not error', () => {
const options = { packageProp: 'foo.baz' };
test('async', async () => {
const result = await cosmiconfig('foo', options).load(configPath);
expect(result).toBeNull();
});
test('sync', () => {
const result = cosmiconfigSync('foo', options).load(configPath);
expect(result).toBeNull();
});
});
});
describe('runs transform', () => {
beforeEach(() => {
temp.createFile('foo.json', '{ "foo": true }');
});
const configPath = temp.absolutePath('foo.json');
const transform = (result: any) => {
result.config.foo = [result.config.foo];
return result;
};
const checkResult = (result: any) => {
expect(result.config).toEqual({ foo: [true] });
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests', {
transform,
}).load(configPath);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests', {
transform,
}).load(configPath);
checkResult(result);
});
});
describe('does not swallow transform errors', () => {
beforeEach(() => {
temp.createFile('foo.json', '{ "foo": true }');
});
const configPath = temp.absolutePath('foo.json');
const expectedError = new Error('These pretzels are making me thirsty!');
const transform = () => {
throw expectedError;
};
test('async', async () => {
await expect(
cosmiconfig('successful-files-tests', { transform }).load(configPath),
).rejects.toThrow(expectedError);
});
test('sync', () => {
expect(() =>
cosmiconfigSync('successful-files-tests', { transform }).load(configPath),
).toThrow(expectedError);
});
});
describe('loads defined JSON file with no extension', () => {
beforeEach(() => {
temp.createFile('foo-valid-json', '{ "json": true }');
});
const file = temp.absolutePath('foo-valid-json');
const checkResult = (result: any) => {
expect(result.config).toEqual({ json: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('loads defined YAML file with no extension', () => {
beforeEach(() => {
temp.createFile('foo-valid-yaml', 'yaml: true');
});
const file = temp.absolutePath('foo-valid-yaml');
const checkResult = (result: any) => {
expect(result.config).toEqual({ yaml: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(file);
checkResult(result);
});
});
describe('custom loaders can be async', () => {
let loadThingsSync: any;
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 }));
});
const file = temp.absolutePath('.foorc.things');
const checkResult = (result: any) => {
expect(result.config).toEqual({ things: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const explorerOptions = {
loaders: {
'.things': loadThingsAsync,
},
};
const result = await cosmiconfig('foo', explorerOptions).load(file);
checkResult(result);
});
test('sync', () => {
const explorerOptions = {
loaders: {
'.things': loadThingsSync,
},
};
const result = cosmiconfigSync('foo', explorerOptions).load(file);
checkResult(result);
});
});
describe('a custom loader entry can include just an async loader', () => {
beforeEach(() => {
temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const loadThingsAsync = async () => ({ things: true });
const explorerOptions = {
loaders: {
'.things': loadThingsAsync,
},
};
const file = temp.absolutePath('.foorc.things');
const checkResult = (result: any) => {
expect(result.config).toEqual({ things: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).load(file);
checkResult(result);
});
});
describe('a custom loader entry can include only a sync loader and work for both sync and async functions', () => {
beforeEach(() => {
temp.createFile('.foorc.things', 'one\ntwo\nthree\t\t\n four\n');
});
const loadThingsSync = () => {
return { things: true };
};
const explorerOptions = {
loaders: {
'.things': loadThingsSync,
},
};
const file = temp.absolutePath('.foorc.things');
const checkResult = (result: any) => {
expect(result.config).toEqual({ things: true });
expect(result.filepath).toBe(file);
};
test('async', async () => {
const result = await cosmiconfig('foo', explorerOptions).load(file);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('foo', explorerOptions).load(file);
checkResult(result);
});
});
describe('works fine if sync loader returns a Promise from a JS file', () => {
beforeEach(() => {
temp.createFile(
'foo.config.js',
'module.exports = Promise.resolve({ a: 1 });',
);
});
const file = temp.absolutePath('foo.config.js');
const explorerOptions = {
stopDir: temp.absolutePath('.'),
searchPlaces: ['foo.config.js'],
};
test('sync', async () => {
const result = cosmiconfigSync('foo', explorerOptions).load(file);
expect(result).toEqual({
filepath: file,
config: expect.any(Promise),
});
if (result === null) {
throw new Error('test is broken');
}
const resolvedConfig = await result.config;
expect(resolvedConfig).toEqual({ a: 1 });
});
});
describe('loads defined JS config relative path', () => {
const currentDir = process.cwd();
beforeEach(() => {
temp.createFile('config/bar.js', 'module.exports = { bar: true };');
process.chdir(temp.dir);
});
afterEach(() => {
process.chdir(currentDir);
});
const relativeFile = './config/bar.js';
const absoluteFile = temp.absolutePath(relativeFile);
const checkResult = (result: any) => {
expect(result.config).toEqual({ bar: true });
expect(result.filepath).toBe(absoluteFile);
};
test('async', async () => {
const result = await cosmiconfig('successful-files-tests').load(
relativeFile,
);
checkResult(result);
});
test('sync', () => {
const result = cosmiconfigSync('successful-files-tests').load(relativeFile);
checkResult(result);
});
});
cosmiconfig-7.1.0/test/util.ts 0000664 0000000 0000000 00000006241 14333725373 0016340 0 ustar 00root root 0000000 0000000 import path from 'path';
import del from 'del';
import makeDir from 'make-dir';
import parentModule from 'parent-module';
import os from 'os';
const fs = jest.requireActual('fs');
function normalizeDirectorySlash(pathname: string): string {
const normalizeCrossPlatform = pathname.replace(/\\/g, '/');
return normalizeCrossPlatform;
}
class TempDir {
public dir: string;
public constructor() {
/**
* Get the actual path for temp directories that are symlinks (MacOS).
* Without the actual path, tests that use process.chdir will unexpectedly
* return the real path instead of symlink path.
*/
const tempDir = fs.realpathSync(os.tmpdir());
/**
* Get the pathname of the file that imported util.js.
* Used to create a unique directory name for each test suite.
*/
const parent = parentModule() || 'cosmiconfig';
const relativeParent = path.relative(process.cwd(), parent);
/**
* Each temp directory will be unique to the test file.
* This ensures that temp files/dirs won't cause side effects for other tests.
*/
this.dir = path.resolve(tempDir, 'cosmiconfig', `${relativeParent}-dir`);
// create directory
makeDir.sync(this.dir);
// re-enable once: https://github.com/typescript-eslint/typescript-eslint/issues/636
/* eslint-disable @typescript-eslint/unbound-method */
this.absolutePath = this.absolutePath.bind(this);
this.createDir = this.createDir.bind(this);
this.createFile = this.createFile.bind(this);
this.clean = this.clean.bind(this);
this.deleteTempDir = this.deleteTempDir.bind(this);
/* eslint-enable @typescript-eslint/unbound-method */
}
public absolutePath(dir: string): string {
// Use path.join to ensure dir is always inside the working temp directory
const absolutePath = path.join(this.dir, dir);
return absolutePath;
}
public createDir(dir: string): void {
const dirname = this.absolutePath(dir);
makeDir.sync(dirname);
}
public createFile(file: string, contents: string): void {
const filePath = this.absolutePath(file);
const fileDir = path.parse(filePath).dir;
makeDir.sync(fileDir);
fs.writeFileSync(filePath, `${contents}\n`);
}
public getSpyPathCalls(spy: jest.Mock | jest.SpyInstance): Array {
const calls = spy.mock.calls;
const result = calls.map((call): string => {
const filePath = call[0];
const relativePath = path.relative(this.dir, filePath);
/**
* Replace Windows backslash directory separators with forward slashes
* so expected paths will be consistent cross platform
*/
const normalizeCrossPlatform = normalizeDirectorySlash(relativePath);
return normalizeCrossPlatform;
});
return result;
}
public clean(): Array {
const cleanPattern = normalizeDirectorySlash(this.absolutePath('**/*'));
const removed = del.sync(cleanPattern, {
dot: true,
force: true,
});
return removed;
}
public deleteTempDir(): Array {
const removed = del.sync(normalizeDirectorySlash(this.dir), {
force: true,
dot: true,
});
return removed;
}
}
export { TempDir };
cosmiconfig-7.1.0/tsconfig.eslint.json 0000664 0000000 0000000 00000000250 14333725373 0020032 0 ustar 00root root 0000000 0000000 {
// extend your base config so you don't have to redefine your compilerOptions
"extends": "./tsconfig.json",
"include": ["**/*.ts", "**/*.js", ".eslintrc.js"]
}
cosmiconfig-7.1.0/tsconfig.json 0000664 0000000 0000000 00000001616 14333725373 0016544 0 ustar 00root root 0000000 0000000 {
"compilerOptions": {
// Target latest version of ECMAScript.
"target": "esnext",
// Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'.
"module": "esnext",
// Search under node_modules for non-relative imports.
"moduleResolution": "node",
// Process & infer types from .js files.
"allowJs": true,
// Don't emit; allow Babel to transform files.
"noEmit": true,
"pretty": true,
// Enable strictest settings like strictNullChecks & noImplicitAny.
"strict": true,
// Disallow features that require cross-file information for emit.
"isolatedModules": true,
// Import non-ES modules as default imports.
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src", "test", "global.d.ts"],
"exclude": ["dist", "node_modules"]
}
cosmiconfig-7.1.0/tsconfig.types.json 0000664 0000000 0000000 00000000430 14333725373 0017700 0 ustar 00root root 0000000 0000000 {
"extends": "./tsconfig",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"isolatedModules": false,
"noEmit": false,
"allowJs": false,
"emitDeclarationOnly": true
},
"exclude": ["**/*.test.ts", "./test/"]
}