cosmiconfig
Advanced tools
Comparing version 4.0.0 to 5.0.1
# Changelog | ||
## 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 | ||
@@ -22,3 +51,3 @@ | ||
- Removed: `argv` config option. | ||
- Removed: support for Node versions < 4. | ||
- Removed: support for Node versions < 4. | ||
- Added: `sync` option. | ||
@@ -65,3 +94,3 @@ - Fixed: Throw a clear error on getting empty config file. | ||
- Added: caching (enabled by the change above). | ||
- Removed: support for Node versions <4. | ||
- Removed: support for Node versions <4. | ||
@@ -85,2 +114,3 @@ ## 1.1.0 | ||
[parse-json-pr-12]: https://github.com/sindresorhus/parse-json/pull/12 | ||
[pr-101]: https://github.com/davidtheclark/cosmiconfig/pull/101 |
@@ -5,156 +5,322 @@ // | ||
const path = require('path'); | ||
const loadPackageProp = require('./loadPackageProp'); | ||
const loadRc = require('./loadRc'); | ||
const loadJs = require('./loadJs'); | ||
const loadDefinedFile = require('./loadDefinedFile'); | ||
const funcRunner = require('./funcRunner'); | ||
const loaders = require('./loaders'); | ||
const readFile = require('./readFile'); | ||
const cacheWrapper = require('./cacheWrapper'); | ||
const getDirectory = require('./getDirectory'); | ||
module.exports = function createExplorer(options | ||
) { | ||
// When `options.sync` is `false` (default), | ||
// these cache Promises that resolve with results, not the results themselves. | ||
const fileCache = options.cache ? new Map() : null; | ||
const directoryCache = options.cache ? new Map() : null; | ||
const transform = options.transform || identity; | ||
const packageProp = options.packageProp; | ||
const MODE_SYNC = 'sync'; | ||
function clearFileCache() { | ||
if (fileCache) fileCache.clear(); | ||
} | ||
// 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. | ||
function clearDirectoryCache() { | ||
if (directoryCache) directoryCache.clear(); | ||
class Explorer { | ||
constructor(options ) { | ||
this.loadCache = options.cache ? new Map() : null; | ||
this.loadSyncCache = options.cache ? new Map() : null; | ||
this.searchCache = options.cache ? new Map() : null; | ||
this.searchSyncCache = options.cache ? new Map() : null; | ||
this.config = options; | ||
this.validateConfig(); | ||
} | ||
function clearCaches() { | ||
clearFileCache(); | ||
clearDirectoryCache(); | ||
clearLoadCache() { | ||
if (this.loadCache) { | ||
this.loadCache.clear(); | ||
} | ||
if (this.loadSyncCache) { | ||
this.loadSyncCache.clear(); | ||
} | ||
} | ||
function throwError(error) { | ||
if (options.sync) { | ||
throw error; | ||
} else { | ||
return Promise.reject(error); | ||
clearSearchCache() { | ||
if (this.searchCache) { | ||
this.searchCache.clear(); | ||
} | ||
if (this.searchSyncCache) { | ||
this.searchSyncCache.clear(); | ||
} | ||
} | ||
function load( | ||
searchPath , | ||
configPath | ||
) { | ||
if (!searchPath) searchPath = process.cwd(); | ||
if (!configPath && options.configPath) configPath = options.configPath; | ||
clearCaches() { | ||
this.clearLoadCache(); | ||
this.clearSearchCache(); | ||
} | ||
if (configPath) { | ||
const absoluteConfigPath = path.resolve(process.cwd(), configPath); | ||
if (fileCache && fileCache.has(absoluteConfigPath)) { | ||
return fileCache.get(absoluteConfigPath); | ||
validateConfig() { | ||
const config = this.config; | ||
config.searchPlaces.forEach(place => { | ||
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` | ||
); | ||
} | ||
}); | ||
} | ||
let load; | ||
if (path.basename(absoluteConfigPath) === 'package.json') { | ||
if (!packageProp) { | ||
return throwError( | ||
new Error( | ||
'Please specify the packageProp option. The configPath argument cannot point to a package.json file if packageProp is false.' | ||
) | ||
); | ||
search(searchFrom ) { | ||
searchFrom = searchFrom || process.cwd(); | ||
return getDirectory(searchFrom).then(dir => { | ||
return this.searchFromDirectory(dir); | ||
}); | ||
} | ||
searchFromDirectory(dir ) { | ||
const absoluteDir = path.resolve(process.cwd(), dir); | ||
const run = () => { | ||
return this.searchDirectory(absoluteDir).then(result => { | ||
const nextDir = this.nextDirectoryToSearch(absoluteDir, result); | ||
if (nextDir) { | ||
return this.searchFromDirectory(nextDir); | ||
} | ||
load = () => | ||
loadPackageProp(path.dirname(absoluteConfigPath), { | ||
packageProp, | ||
sync: options.sync, | ||
}); | ||
} else { | ||
load = () => | ||
loadDefinedFile(absoluteConfigPath, { | ||
sync: options.sync, | ||
format: options.format, | ||
}); | ||
return this.config.transform(result); | ||
}); | ||
}; | ||
if (this.searchCache) { | ||
return cacheWrapper(this.searchCache, absoluteDir, run); | ||
} | ||
return run(); | ||
} | ||
searchSync(searchFrom ) { | ||
searchFrom = searchFrom || process.cwd(); | ||
const dir = getDirectory.sync(searchFrom); | ||
return this.searchFromDirectorySync(dir); | ||
} | ||
searchFromDirectorySync(dir ) { | ||
const absoluteDir = path.resolve(process.cwd(), dir); | ||
const run = () => { | ||
const result = this.searchDirectorySync(absoluteDir); | ||
const nextDir = this.nextDirectoryToSearch(absoluteDir, result); | ||
if (nextDir) { | ||
return this.searchFromDirectorySync(nextDir); | ||
} | ||
return this.config.transform(result); | ||
}; | ||
const loadResult = load(); | ||
const result = | ||
loadResult instanceof Promise | ||
? loadResult.then(transform) | ||
: transform(loadResult); | ||
if (fileCache) fileCache.set(absoluteConfigPath, result); | ||
return result; | ||
if (this.searchSyncCache) { | ||
return cacheWrapper(this.searchSyncCache, absoluteDir, run); | ||
} | ||
return run(); | ||
} | ||
const absoluteSearchPath = path.resolve(process.cwd(), searchPath); | ||
const searchPathDir = getDirectory(absoluteSearchPath, options.sync); | ||
searchDirectory(dir ) { | ||
return this.config.searchPlaces.reduce((prevResultPromise, place) => { | ||
return prevResultPromise.then(prevResult => { | ||
if (this.shouldSearchStopWithResult(prevResult)) { | ||
return prevResult; | ||
} | ||
return this.loadSearchPlace(dir, place); | ||
}); | ||
}, Promise.resolve(null)); | ||
} | ||
return searchPathDir instanceof Promise | ||
? searchPathDir.then(searchDirectory) | ||
: searchDirectory(searchPathDir); | ||
searchDirectorySync(dir ) { | ||
let result = null; | ||
for (const place of this.config.searchPlaces) { | ||
result = this.loadSearchPlaceSync(dir, place); | ||
if (this.shouldSearchStopWithResult(result)) break; | ||
} | ||
return result; | ||
} | ||
function searchDirectory( | ||
directory | ||
) { | ||
if (directoryCache && directoryCache.has(directory)) { | ||
return directoryCache.get(directory); | ||
shouldSearchStopWithResult(result ) { | ||
if (result === null) return false; | ||
if (result.isEmpty && this.config.ignoreEmptySearchPlaces) return false; | ||
return true; | ||
} | ||
loadSearchPlace(dir , place ) { | ||
const filepath = path.join(dir, place); | ||
return readFile(filepath).then(content => { | ||
return this.createCosmiconfigResult(filepath, content); | ||
}); | ||
} | ||
loadSearchPlaceSync(dir , place ) { | ||
const filepath = path.join(dir, place); | ||
const content = readFile.sync(filepath); | ||
return this.createCosmiconfigResultSync(filepath, content); | ||
} | ||
nextDirectoryToSearch( | ||
currentDir , | ||
currentResult | ||
) { | ||
if (this.shouldSearchStopWithResult(currentResult)) { | ||
return null; | ||
} | ||
const nextDir = nextDirUp(currentDir); | ||
if (nextDir === currentDir || currentDir === this.config.stopDir) { | ||
return null; | ||
} | ||
return nextDir; | ||
} | ||
const result = funcRunner(!options.sync ? Promise.resolve() : undefined, [ | ||
() => { | ||
if (!packageProp) return; | ||
return loadPackageProp(directory, { | ||
packageProp, | ||
sync: options.sync, | ||
}); | ||
}, | ||
result => { | ||
if (result || !options.rc) return result; | ||
return loadRc(path.join(directory, options.rc), { | ||
sync: options.sync, | ||
rcStrictJson: options.rcStrictJson, | ||
rcExtensions: options.rcExtensions, | ||
}); | ||
}, | ||
result => { | ||
if (result || !options.js) return result; | ||
return loadJs(path.join(directory, options.js), { sync: options.sync }); | ||
}, | ||
result => { | ||
if (result) return result; | ||
loadPackageProp(filepath , content ) { | ||
const parsedContent = loaders.loadJson(filepath, content); | ||
const packagePropValue = parsedContent[this.config.packageProp]; | ||
return packagePropValue || null; | ||
} | ||
const nextDirectory = path.dirname(directory); | ||
getLoaderEntryForFile(filepath ) { | ||
if (path.basename(filepath) === 'package.json') { | ||
const loader = this.loadPackageProp.bind(this); | ||
return { sync: loader, async: loader }; | ||
} | ||
if (nextDirectory === directory || directory === options.stopDir) | ||
return null; | ||
const loaderKey = path.extname(filepath) || 'noExt'; | ||
return this.config.loaders[loaderKey]; | ||
} | ||
return searchDirectory(nextDirectory); | ||
}, | ||
transform, | ||
]); | ||
getSyncLoaderForFile(filepath ) { | ||
const entry = this.getLoaderEntryForFile(filepath); | ||
if (!entry.sync) { | ||
throw new Error( | ||
`No sync loader specified for ${getExtensionDescription(filepath)}` | ||
); | ||
} | ||
return entry.sync; | ||
} | ||
if (directoryCache) directoryCache.set(directory, result); | ||
return result; | ||
getAsyncLoaderForFile(filepath ) { | ||
const entry = this.getLoaderEntryForFile(filepath); | ||
const loader = entry.async || entry.sync; | ||
if (!loader) { | ||
throw new Error( | ||
`No async loader specified for ${getExtensionDescription(filepath)}` | ||
); | ||
} | ||
return loader; | ||
} | ||
loadFileContent( | ||
mode , | ||
filepath , | ||
content | ||
) { | ||
if (content === null) { | ||
return null; | ||
} | ||
if (content.trim() === '') { | ||
return undefined; | ||
} | ||
const loader = | ||
mode === MODE_SYNC | ||
? this.getSyncLoaderForFile(filepath) | ||
: this.getAsyncLoaderForFile(filepath); | ||
const loadedContent = loader(filepath, content); | ||
if (mode === MODE_SYNC && loadedContent instanceof Promise) { | ||
throw new Error( | ||
`The sync loader for "${path.basename( | ||
filepath | ||
)}" returned a Promise. Sync loaders need to be synchronous.` | ||
); | ||
} | ||
return loadedContent; | ||
} | ||
loadedContentToCosmiconfigResult( | ||
filepath , | ||
loadedContent | ||
) { | ||
if (loadedContent === null) { | ||
return null; | ||
} | ||
if (loadedContent === undefined) { | ||
return { filepath, config: undefined, isEmpty: true }; | ||
} | ||
return { config: loadedContent, filepath }; | ||
} | ||
createCosmiconfigResult( | ||
filepath , | ||
content | ||
) { | ||
return Promise.resolve() | ||
.then(() => { | ||
return this.loadFileContent('async', filepath, content); | ||
}) | ||
.then(loaderResult => { | ||
return this.loadedContentToCosmiconfigResult(filepath, loaderResult); | ||
}); | ||
} | ||
createCosmiconfigResultSync( | ||
filepath , | ||
content | ||
) { | ||
const loaderResult = this.loadFileContent('sync', filepath, content); | ||
return this.loadedContentToCosmiconfigResult(filepath, loaderResult); | ||
} | ||
validateFilePath(filepath ) { | ||
if (!filepath) { | ||
throw new Error('load and loadSync must be pass a non-empty string'); | ||
} | ||
} | ||
load(filepath ) { | ||
return Promise.resolve().then(() => { | ||
this.validateFilePath(filepath); | ||
const absoluteFilePath = path.resolve(process.cwd(), filepath); | ||
return cacheWrapper(this.loadCache, absoluteFilePath, () => { | ||
return readFile(absoluteFilePath, { throwNotFound: true }) | ||
.then(content => { | ||
return this.createCosmiconfigResult(filepath, content); | ||
}) | ||
.then(this.config.transform); | ||
}); | ||
}); | ||
} | ||
loadSync(filepath ) { | ||
this.validateFilePath(filepath); | ||
const absoluteFilePath = path.resolve(process.cwd(), filepath); | ||
return cacheWrapper(this.loadSyncCache, absoluteFilePath, () => { | ||
const content = readFile.sync(absoluteFilePath, { throwNotFound: true }); | ||
const result = this.createCosmiconfigResultSync(filepath, content); | ||
return this.config.transform(result); | ||
}); | ||
} | ||
} | ||
module.exports = function createExplorer(options ) { | ||
const explorer = new Explorer(options); | ||
return { | ||
load, | ||
clearFileCache, | ||
clearDirectoryCache, | ||
clearCaches, | ||
search: explorer.search.bind(explorer), | ||
searchSync: explorer.searchSync.bind(explorer), | ||
load: explorer.load.bind(explorer), | ||
loadSync: explorer.loadSync.bind(explorer), | ||
clearLoadCache: explorer.clearLoadCache.bind(explorer), | ||
clearSearchCache: explorer.clearSearchCache.bind(explorer), | ||
clearCaches: explorer.clearCaches.bind(explorer), | ||
}; | ||
}; | ||
function identity(x) { | ||
return x; | ||
function nextDirUp(dir ) { | ||
return path.dirname(dir); | ||
} | ||
function getExtensionDescription(filepath ) { | ||
const ext = path.extname(filepath); | ||
return ext ? `extension "${ext}"` : 'files without extensions'; | ||
} |
@@ -7,10 +7,3 @@ // | ||
module.exports = function getDirectory( | ||
filepath , | ||
sync | ||
) { | ||
if (sync === true) { | ||
return isDirectory.sync(filepath) ? filepath : path.dirname(filepath); | ||
} | ||
function getDirectory(filepath ) { | ||
return new Promise((resolve, reject) => { | ||
@@ -24,2 +17,8 @@ return isDirectory(filepath, (err, filepathIsDirectory) => { | ||
}); | ||
} | ||
getDirectory.sync = function getDirectorySync(filepath ) { | ||
return isDirectory.sync(filepath) ? filepath : path.dirname(filepath); | ||
}; | ||
module.exports = getDirectory; |
@@ -6,36 +6,76 @@ // | ||
const createExplorer = require('./createExplorer'); | ||
const loaders = require('./loaders'); | ||
const homedir = os.homedir(); | ||
module.exports = cosmiconfig; | ||
module.exports = function cosmiconfig( | ||
function cosmiconfig( | ||
moduleName , | ||
options | ||
) { | ||
options = Object.assign( | ||
options = options || {}; | ||
const defaults = { | ||
packageProp: moduleName, | ||
searchPlaces: [ | ||
'package.json', | ||
`.${moduleName}rc`, | ||
`.${moduleName}rc.json`, | ||
`.${moduleName}rc.yaml`, | ||
`.${moduleName}rc.yml`, | ||
`${moduleName}.config.js`, | ||
], | ||
ignoreEmptySearchPlaces: true, | ||
stopDir: os.homedir(), | ||
cache: true, | ||
transform: identity, | ||
}; | ||
const normalizedOptions = Object.assign( | ||
{}, | ||
defaults, | ||
options, | ||
{ | ||
packageProp: moduleName, | ||
rc: `.${moduleName}rc`, | ||
js: `${moduleName}.config.js`, | ||
rcStrictJson: false, | ||
stopDir: homedir, | ||
cache: true, | ||
sync: false, | ||
}, | ||
options | ||
loaders: normalizeLoaders(options.loaders), | ||
} | ||
); | ||
return createExplorer(options); | ||
}; | ||
return createExplorer(normalizedOptions); | ||
} | ||
cosmiconfig.loadJs = loaders.loadJs; | ||
cosmiconfig.loadJson = loaders.loadJson; | ||
cosmiconfig.loadYaml = loaders.loadYaml; | ||
function normalizeLoaders(rawLoaders ) { | ||
const defaults = { | ||
'.js': { sync: loaders.loadJs, async: loaders.loadJs }, | ||
'.json': { sync: loaders.loadJson, async: loaders.loadJson }, | ||
'.yaml': { sync: loaders.loadYaml, async: loaders.loadYaml }, | ||
'.yml': { sync: loaders.loadYaml, async: loaders.loadYaml }, | ||
noExt: { sync: loaders.loadYaml, async: loaders.loadYaml }, | ||
}; | ||
if (!rawLoaders) { | ||
return defaults; | ||
} | ||
return Object.keys(rawLoaders).reduce((result, ext) => { | ||
const entry = rawLoaders && rawLoaders[ext]; | ||
if (typeof entry === 'function') { | ||
result[ext] = { sync: entry, async: entry }; | ||
} else { | ||
result[ext] = entry; | ||
} | ||
return result; | ||
}, defaults); | ||
} | ||
function identity(x) { | ||
return x; | ||
} |
@@ -10,3 +10,3 @@ // | ||
function readFile(filepath , options ) { | ||
function readFile(filepath , options ) { | ||
options = options || {}; | ||
@@ -29,3 +29,3 @@ const throwNotFound = options.throwNotFound || false; | ||
options | ||
) { | ||
) { | ||
options = options || {}; | ||
@@ -32,0 +32,0 @@ const throwNotFound = options.throwNotFound || false; |
{ | ||
"name": "cosmiconfig", | ||
"version": "4.0.0", | ||
"version": "5.0.1", | ||
"description": "Find and load configuration from a package.json property, rc file, or CommonJS module", | ||
@@ -11,4 +11,6 @@ "main": "dist/index.js", | ||
"precommit": "lint-staged && jest && flow check", | ||
"lint": "eslint .", | ||
"lint:md-partial": "remark -u remark-preset-davidtheclark --frail --quiet --no-stdout --output --", | ||
"lint:md": "npm run lint:md-partial -- *.md", | ||
"lint:fix": "eslint . --fix", | ||
"lint": "eslint . && npm run lint:md", | ||
"format": "prettier --write \"{src/*.js,test/*.js}\"", | ||
@@ -27,2 +29,6 @@ "pretest": "npm run lint && flow check", | ||
"git add" | ||
], | ||
"*.md": [ | ||
"npm run lint:md-partial", | ||
"git add" | ||
] | ||
@@ -60,2 +66,6 @@ }, | ||
], | ||
"coverageReporters": [ | ||
"text", | ||
"html" | ||
], | ||
"coverageThreshold": { | ||
@@ -68,3 +78,5 @@ "global": { | ||
} | ||
} | ||
}, | ||
"resetModules": true, | ||
"resetMocks": true | ||
}, | ||
@@ -79,4 +91,3 @@ "babel": { | ||
"js-yaml": "^3.9.0", | ||
"parse-json": "^4.0.0", | ||
"require-from-string": "^2.0.1" | ||
"parse-json": "^4.0.0" | ||
}, | ||
@@ -86,2 +97,3 @@ "devDependencies": { | ||
"babel-plugin-transform-flow-strip-types": "^6.22.0", | ||
"del": "^3.0.0", | ||
"eslint": "^4.12.1", | ||
@@ -92,3 +104,3 @@ "eslint-config-davidtheclark-node": "^0.2.2", | ||
"eslint-plugin-node": "^5.2.1", | ||
"flow-bin": "^0.54.1", | ||
"flow-bin": "^0.68.0", | ||
"flow-remove-types": "^1.2.3", | ||
@@ -98,3 +110,7 @@ "husky": "^0.14.3", | ||
"lint-staged": "^6.0.0", | ||
"prettier": "^1.8.2" | ||
"make-dir": "^1.2.0", | ||
"parent-module": "^0.1.0", | ||
"prettier": "^1.8.2", | ||
"remark-cli": "^5.0.0", | ||
"remark-preset-davidtheclark": "^0.7.0" | ||
}, | ||
@@ -101,0 +117,0 @@ "engines": { |
498
README.md
@@ -5,19 +5,56 @@ # cosmiconfig | ||
Find and load a configuration object from | ||
- a `package.json` property (anywhere up the directory tree) | ||
- a JSON or YAML "rc file" (anywhere up the directory tree) | ||
- a `.config.js` CommonJS module (anywhere up the directory tree) | ||
Cosmiconfig searches for and loads configuration for your program. | ||
For example, if your module's name is "soursocks," cosmiconfig will search out configuration in the following places: | ||
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`, or `.yml`. | ||
- a `.config.js` CommonJS module | ||
For example, if your module's name is "soursocks", cosmiconfig will search for configuration in the following places: | ||
- a `soursocks` property in `package.json` (anywhere up the directory tree) | ||
- a `.soursocksrc` file in JSON or YAML format (anywhere up the directory tree) | ||
- a `.soursocksrc.json` file | ||
- a `.soursocksrc.yaml` or `.soursocksrc.yml` file | ||
- a `soursocks.config.js` file exporting a JS object (anywhere up the directory tree) | ||
cosmiconfig continues to search in these places all the way up the directory tree until it finds acceptable configuration (or hits the home directory). | ||
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). | ||
Additionally, all of these search locations are configurable: you can customize filenames or turn off any location. | ||
👀 **Looking for the v4 docs?** | ||
v5 involves significant revisions to Cosmiconfig's API, allowing for much greater flexibility and clarifying some things. | ||
If you have trouble switching from v4 to v5, please file an issue. | ||
If you are still using v4, those v4 docs are available [in the `4.0.0` tag](https://github.com/davidtheclark/cosmiconfig/tree/4.0.0). | ||
You can also look for rc files with extensions, e.g. `.soursocksrc.json` or `.soursocksrc.yaml`. | ||
You may like extensions on your rc files because you'll get syntax highlighting and linting in text editors. | ||
## Table of contents | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Result](#result) | ||
- [cosmiconfig()](#cosmiconfig-1) | ||
- [moduleName](#modulename) | ||
- [explorer.search()](#explorersearch) | ||
- [searchFrom](#searchfrom) | ||
- [explorer.searchSync()](#explorersearchsync) | ||
- [explorer.load()](#explorerload) | ||
- [explorer.loadSync()](#explorerloadsync) | ||
- [explorer.clearLoadCache()](#explorerclearloadcache) | ||
- [explorer.clearSearchCache()](#explorerclearsearchcache) | ||
- [explorer.clearCaches()](#explorerclearcaches) | ||
- [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 | ||
@@ -33,199 +70,382 @@ | ||
Create a Cosmiconfig explorer, then either `search` for or directly `load` a configuration file. | ||
```js | ||
var cosmiconfig = require('cosmiconfig'); | ||
const cosmiconfig = require('cosmiconfig'); | ||
// ... | ||
const explorer = cosmiconfig(moduleName); | ||
var explorer = cosmiconfig(yourModuleName[, options]); | ||
explorer.load() | ||
// 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.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((parsingError) => { | ||
// do something constructive | ||
.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 searchedFor = explorer.searchSync(); | ||
const loaded = explorer.loadSync(pathToConfig); | ||
``` | ||
The function `cosmiconfig()` searches for a configuration object and returns a Promise, | ||
which resolves with an object containing the information you're looking for. | ||
## Result | ||
You can also pass option `sync: true` to load the config synchronously, returning the config itself. | ||
The result object you get from `search` or `load` has the following properties: | ||
So let's say `var yourModuleName = 'goldengrahams'` — here's how cosmiconfig will work: | ||
- **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. | ||
- Starting from `process.cwd()` (or some other directory defined by the `searchPath` argument to `load()`), it looks for configuration objects in three places, in this order: | ||
1. A `goldengrahams` property in a `package.json` file (or some other property defined by `options.packageProp`); | ||
2. A `.goldengrahamsrc` file with JSON or YAML syntax (or some other filename defined by `options.rc`); | ||
3. A `goldengrahams.config.js` JS file exporting the object (or some other filename defined by `options.js`). | ||
- If none of those searches reveal a configuration object, it moves up one directory level and tries again. So the search continues in `./`, `../`, `../../`, `../../../`, etc., checking those three locations in each directory. | ||
- It continues searching until it arrives at your home directory (or some other directory defined by `options.stopDir`). | ||
- If at any point a parseable configuration is found, the `cosmiconfig()` Promise resolves with its result object. | ||
- If no configuration object is found, the `cosmiconfig()` Promise resolves with `null`. | ||
- If a configuration object is found *but is malformed* (causing a parsing error), the `cosmiconfig()` Promise rejects and shares that error (so you should `.catch()` it). | ||
## cosmiconfig() | ||
All this searching can be short-circuited by passing `options.configPath` to specify a file. | ||
cosmiconfig will read that file and try parsing it as JSON, YAML, or JS. | ||
```js | ||
const explorer = cosmiconfig(moduleName[, cosmiconfigOptions]) | ||
``` | ||
## Caching | ||
Creates a cosmiconfig instance ("explorer") configured according to the arguments, and initializes its caches. | ||
As of v2, cosmiconfig uses a few caches to reduce the need for repetitious reading of the filesystem. Every new cosmiconfig instance (created with `cosmiconfig()`) has its own caches. | ||
### moduleName | ||
To avoid or work around caching, you can | ||
- create separate instances of cosmiconfig, or | ||
- set `cache: false` in your options. | ||
- use the cache clearing methods documented below. | ||
Type: `string`. **Required.** | ||
## API | ||
Your module name. This is used to create the default [`searchPlaces`] and [`packageProp`]. | ||
### `var explorer = cosmiconfig(moduleName[, options])` | ||
**[`cosmiconfigOptions`] are documented below.** | ||
You may not need them, and should first read about the functions you'll use. | ||
Creates a cosmiconfig instance (i.e. explorer) configured according to the arguments, and initializes its caches. | ||
## explorer.search() | ||
#### moduleName | ||
```js | ||
explorer.search([searchFrom]).then(result => {..}) | ||
``` | ||
Type: `string` | ||
Searches for a configuration file. Returns a Promise that resolves with a [result] or with `null`, if no configuration file is found. | ||
You module name. This is used to create the default filenames that cosmiconfig will look for. | ||
You can do the same thing synchronously with [`searchSync()`]. | ||
#### Options | ||
Let's say your module name is `goldengrahams` so you initialized with `const explorer = cosmiconfig('goldengrahams');`. | ||
Here's how your default [`search()`] will work: | ||
##### packageProp | ||
- 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` file. | ||
4. A `.goldengrahamsrc.yaml` or `.goldengrahamsrc.yml` file. | ||
5. A `goldengrahams.config.js` JS file 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 parseable configuration is found, the [`search()`] Promise resolves with its [result] \(or, with [`searchSync()`], the [result] is returned). | ||
- If no configuration object is found, the [`search()`] Promise resolves with `null` (or, with [`searchSync()`], `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 [`searchSync()`], the error is thrown.) | ||
Type: `string` or `false` | ||
Default: `'[moduleName]'` | ||
**If you know exactly where your configuration file should be, you can use [`load()`], instead.** | ||
Name of the property in `package.json` to look for. | ||
**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. | ||
If `false`, cosmiconfig will not look in `package.json` files. | ||
### searchFrom | ||
##### rc | ||
Type: `string`. | ||
Default: `process.cwd()`. | ||
Type: `string` or `false` | ||
Default: `'.[moduleName]rc'` | ||
A filename. | ||
[`search()`] will start its search here. | ||
Name of the "rc file" to look for, which can be formatted as JSON or YAML. | ||
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. | ||
If `false`, cosmiconfig will not look for an rc file. | ||
## explorer.searchSync() | ||
If `rcExtensions: true`, the rc file can also have extensions that specify the syntax, e.g. `.[moduleName]rc.json`. | ||
You may like extensions on your rc files because you'll get syntax highlighting and linting in text editors. | ||
Also, with `rcExtensions: true`, you can use JS modules as rc files, e.g. `.[moduleName]rc.js`. | ||
```js | ||
const result = explorer.search([searchFrom]); | ||
``` | ||
##### js | ||
Synchronous version of [`search()`]. | ||
Type: `string` or `false` | ||
Default: `'[moduleName].config.js'` | ||
Returns a [result] or `null`. | ||
Name of a JS file to look for, which must export the configuration object. | ||
## explorer.load() | ||
If `false`, cosmiconfig will not look for a JS file. | ||
```js | ||
explorer.load([loadPath]).then(result => {..}) | ||
``` | ||
##### rcStrictJson | ||
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). | ||
Type: `boolean` | ||
Default: `false` | ||
Use `load` if you already know where the configuration file is and you just need to load it. | ||
If `true`, cosmiconfig will expect rc files to be strict JSON. No YAML permitted, and no sloppy JSON. | ||
```js | ||
explorer.load('load/this/file.json'); // Tries to load load/this/file.json. | ||
``` | ||
By default, rc files are parsed with [js-yaml](https://github.com/nodeca/js-yaml), which is | ||
more permissive with punctuation than standard strict JSON. | ||
If you load a `package.json` file, the result will be derived from whatever property is specified as your [`packageProp`]. | ||
##### rcExtensions | ||
## explorer.loadSync() | ||
Type: `boolean` | ||
Default: `false` | ||
```js | ||
const result = explorer.load([loadPath]); | ||
``` | ||
If `true`, cosmiconfig will look for rc files with extensions, in addition to rc files without. | ||
Synchronous version of [`load()`]. | ||
This adds a few steps to the search process. | ||
Instead of *just* looking for `.goldengrahamsrc` (no extension), it will also look for the following, in this order: | ||
Returns a [result]. | ||
- `.goldengrahamsrc.json` | ||
- `.goldengrahamsrc.yaml` | ||
- `.goldengrahamsrc.yml` | ||
- `.goldengrahamsrc.js` | ||
## explorer.clearLoadCache() | ||
##### stopDir | ||
Clears the cache used in [`load()`]. | ||
Type: `string` | ||
Default: Absolute path to your home directory | ||
## explorer.clearSearchCache() | ||
Directory where the search will stop. | ||
Clears the cache used in [`search()`]. | ||
##### cache | ||
## explorer.clearCaches() | ||
Type: `boolean` | ||
Default: `true` | ||
Performs both [`clearLoadCache()`] and [`clearSearchCache()`]. | ||
If `false`, no caches will be used. | ||
## cosmiconfigOptions | ||
##### sync | ||
### searchPlaces | ||
Type: `boolean` | ||
Default: `false` | ||
Type: `Array<string>`. | ||
Default: See below. | ||
If `true`, config will be loaded synchronously. | ||
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. | ||
##### transform | ||
**Default `searchPlaces`:** | ||
Type: `Function` | ||
```js | ||
[ | ||
'package.json', | ||
`.${moduleName}rc`, | ||
`.${moduleName}rc.json`, | ||
`.${moduleName}rc.yaml`, | ||
`.${moduleName}rc.yml`, | ||
`${moduleName}.config.js`, | ||
] | ||
``` | ||
A function that transforms the parsed configuration. Receives the result object with `config` and `filepath` properties. | ||
Create your own array to search more, fewer, or altogether different places. | ||
If the option `sync` is `false` (default), the function must return a Promise that resolves with the transformed result. | ||
If the option `sync` is `true`, though, `transform` should be a synchronous function which returns the transformed result. | ||
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. | ||
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 loaded. | ||
`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. | ||
##### configPath | ||
Examples, with a module named `porgy`: | ||
Type: `string` | ||
```js | ||
// Disallow extensions on rc files: | ||
[ | ||
'package.json', | ||
'.porgyrc', | ||
'porgy.config.js' | ||
] | ||
If provided, cosmiconfig will load and parse a config from this path, and will not perform its usual search. | ||
// ESLint searches for configuration in these places: | ||
[ | ||
'.eslintrc.js', | ||
'.eslintrc.yaml', | ||
'.eslintrc.yml', | ||
'.eslintrc.json', | ||
'.eslintrc', | ||
'package.json' | ||
] | ||
##### format | ||
// Babel looks in fewer places: | ||
[ | ||
'package.json', | ||
'.babelrc' | ||
] | ||
Type: `'json' | 'yaml' | 'js'` | ||
// 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. | ||
The expected file format for the config loaded from `configPath`. | ||
// Look within a .config/ subdirectory of every searched directory: | ||
[ | ||
'package.json', | ||
'.porgyrc', | ||
'.config/.porgyrc', | ||
'.porgyrc.json', | ||
'.config/.porgyrc.json' | ||
] | ||
``` | ||
If not specified, cosmiconfig will try to infer the format using the extension name (if it has one). | ||
In the event that the file does not have an extension or the extension is unrecognized, cosmiconfig will try to parse it as a JSON, YAML, or JS file. | ||
### loaders | ||
### Instance methods (on `explorer`) | ||
Type: `Object`. | ||
Default: See below. | ||
#### `load([searchPath, configPath])` | ||
An object that maps extensions to the loader functions responsible for loading and parsing files with those extensions. | ||
Find and load a configuration file. Returns a Promise that resolves with `null`, if nothing is found, or an object with two properties: | ||
- `config`: The loaded and parsed configuration. | ||
- `filepath`: The filepath where this configuration was found. | ||
Cosmiconfig exposes its default loaders for `.js`, `.json`, and `.yaml` as `cosmiconfig.loadJs`, `cosmiconfig.loadJson`, and `cosmiconfig.loadYaml`, respectively. | ||
You should provide *either* `searchPath` *or* `configPath`. Use `configPath` if you know the path of the configuration file you want to load. Note that `configPath` takes priority over `searchPath` if both parameters are specified. | ||
**Default `loaders`:** | ||
```js | ||
explorer.load() | ||
{ | ||
'.json': cosmiconfig.loadJson, | ||
'.yaml': cosmiconfig.loadYaml, | ||
'.yml': cosmiconfig.loadYaml, | ||
'.js': cosmiconfig.loadJs, | ||
noExt: cosmiconfig.loadYaml | ||
} | ||
``` | ||
explorer.load('start/search/here'); | ||
explorer.load('start/search/at/this/file.css'); | ||
(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.) | ||
explorer.load(null, 'load/this/file.json'); | ||
**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 `.soursocksrc`. | ||
**Values in `loaders`** are either a loader function (described below) or an object with `sync` and/or `async` properties, 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: cosmiconfig.loadJson | ||
} | ||
``` | ||
If you provide `searchPath`, cosmiconfig will start its search at `searchPath` and continue to search up the directory tree, as documented above. | ||
By default, `searchPath` is set to `process.cwd()`. | ||
If you want to load files that are not handled by the loader functions Cosmiconfig exposes, you can write a custom loader function. | ||
If you provide `configPath` (i.e. you already know where the configuration is that you want to load), cosmiconfig will try to read and parse that file. Note that the [`format` option](#format) is applicable for this as well. | ||
**Use cases for custom loader function:** | ||
#### `clearFileCache()` | ||
- 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. | ||
Clears the cache used when you provide a `configPath` argument to `load`. | ||
**Custom loader functions** have the following signature: | ||
#### `clearDirectoryCache()` | ||
```js | ||
// Sync | ||
(filepath: string, content: string) => Object | null | ||
Clears the cache used when you provide a `searchPath` argument to `load`. | ||
// Async | ||
(filepath: string, content: string) => Object | null | Promise<Object | null> | ||
``` | ||
#### `clearCaches()` | ||
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. | ||
Performs both `clearFileCache()` and `clearDirectoryCache()`. | ||
It's easiest if you make your custom loader function synchronous. | ||
Then it can be used regardless of whether you end up calling [`search()`] or [`searchSync()`], [`load()`] or [`loadSync()`]. | ||
If you want or need to provide an async-only loader, you can do so by making the value of `loaders` an object with an `async` property whose value is the async loader. | ||
You can also add a `sync` property to designate a sync loader, if you want to use both async and sync search and load functions. | ||
If an extension has *only* an async loader but you try to use [`searchSync()`] or [`loadSync()`], an error will be thrown. | ||
Note that **special JS syntax can also be handled by using a `require` hook**, because `cosmiconfig.loadJs` just uses `require`. | ||
Whether you use custom loaders or a `require` hook is up to you. | ||
Examples: | ||
```js | ||
// Allow JSON5 syntax: | ||
{ | ||
'.json': json5Loader | ||
} | ||
// Allow XML, and treat sync and async separately: | ||
{ | ||
'.xml': { async: asyncXmlLoader, sync: syncXmlLoader } | ||
} | ||
// 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': cosmiconfig.loadJs, | ||
'.ts': cosmiconfig.loadJs, | ||
'.coffee': cosmiconfig.loadJs | ||
} | ||
``` | ||
### packageProp | ||
Type: `string`. | ||
Default: `` `${moduleName}` ``. | ||
Name of the property in `package.json` to look for. | ||
### 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> | 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 [`searchSync()`] or [`loadSync()`], 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) | ||
@@ -243,4 +463,32 @@ | ||
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. | ||
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 | ||
[`loadsync()`]: #explorerloadsync | ||
[`search()`]: #explorersearch | ||
[`searchsync()`]: #explorersearchsync | ||
[`clearloadcache()`]: #explorerclearloadcache | ||
[`clearsearchcache()`]: #explorerclearsearchcache | ||
[`clearcaches()`]: #explorerclearcaches | ||
[`packageprop`]: #packageprop | ||
[`cache`]: #cache | ||
[`stopdir`]: #stopdir | ||
[`searchplaces`]: #searchplaces | ||
[`loaders`]: #loaders | ||
[`cosmiconfigoptions`]: #cosmiconfigoptions |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
40134
3
492
18
10
431
2
1
- Removedrequire-from-string@^2.0.1
- Removedrequire-from-string@2.0.2(transitive)