123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- //
- 'use strict';
- const path = require('path');
- const loaders = require('./loaders');
- const readFile = require('./readFile');
- const cacheWrapper = require('./cacheWrapper');
- const getDirectory = require('./getDirectory');
- const getPropertyByPath = require('./getPropertyByPath');
- const MODE_SYNC = 'sync';
- // 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.
-
- 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();
- }
- clearLoadCache() {
- if (this.loadCache) {
- this.loadCache.clear();
- }
- if (this.loadSyncCache) {
- this.loadSyncCache.clear();
- }
- }
- clearSearchCache() {
- if (this.searchCache) {
- this.searchCache.clear();
- }
- if (this.searchSyncCache) {
- this.searchSyncCache.clear();
- }
- }
- clearCaches() {
- this.clearLoadCache();
- this.clearSearchCache();
- }
- 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`
- );
- }
- });
- }
- 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);
- }
- 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);
- };
- if (this.searchSyncCache) {
- return cacheWrapper(this.searchSyncCache, absoluteDir, run);
- }
- return run();
- }
- 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));
- }
- searchDirectorySync(dir ) {
- let result = null;
- for (const place of this.config.searchPlaces) {
- result = this.loadSearchPlaceSync(dir, place);
- if (this.shouldSearchStopWithResult(result)) break;
- }
- return result;
- }
- 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;
- }
- loadPackageProp(filepath , content ) {
- const parsedContent = loaders.loadJson(filepath, content);
- const packagePropValue = getPropertyByPath(
- parsedContent,
- this.config.packageProp
- );
- return packagePropValue || null;
- }
- getLoaderEntryForFile(filepath ) {
- if (path.basename(filepath) === 'package.json') {
- const loader = this.loadPackageProp.bind(this);
- return { sync: loader, async: loader };
- }
- const loaderKey = path.extname(filepath) || 'noExt';
- return this.config.loaders[loaderKey] || {};
- }
- getSyncLoaderForFile(filepath ) {
- const entry = this.getLoaderEntryForFile(filepath);
- if (!entry.sync) {
- throw new Error(
- `No sync loader specified for ${getExtensionDescription(filepath)}`
- );
- }
- return entry.sync;
- }
- 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);
- return loader(filepath, content);
- }
- 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 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(absoluteFilePath, 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(
- absoluteFilePath,
- content
- );
- return this.config.transform(result);
- });
- }
- }
- module.exports = function createExplorer(options ) {
- const explorer = new Explorer(options);
- return {
- 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 nextDirUp(dir ) {
- return path.dirname(dir);
- }
- function getExtensionDescription(filepath ) {
- const ext = path.extname(filepath);
- return ext ? `extension "${ext}"` : 'files without extensions';
- }
|