index.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.lilconfigSync = exports.lilconfig = exports.defaultLoaders = void 0;
  4. const path = require("path");
  5. const fs = require("fs");
  6. const os = require("os");
  7. const fsReadFileAsync = fs.promises.readFile;
  8. function getDefaultSearchPlaces(name) {
  9. return [
  10. 'package.json',
  11. `.${name}rc.json`,
  12. `.${name}rc.js`,
  13. `${name}.config.js`,
  14. `.${name}rc.cjs`,
  15. `${name}.config.cjs`,
  16. ];
  17. }
  18. function getSearchPaths(startDir, stopDir) {
  19. return startDir
  20. .split(path.sep)
  21. .reduceRight((acc, _, ind, arr) => {
  22. const currentPath = arr.slice(0, ind + 1).join(path.sep);
  23. if (!acc.passedStopDir)
  24. acc.searchPlaces.push(currentPath || path.sep);
  25. if (currentPath === stopDir)
  26. acc.passedStopDir = true;
  27. return acc;
  28. }, { searchPlaces: [], passedStopDir: false }).searchPlaces;
  29. }
  30. exports.defaultLoaders = Object.freeze({
  31. '.js': require,
  32. '.json': require,
  33. '.cjs': require,
  34. noExt(_, content) {
  35. return JSON.parse(content);
  36. },
  37. });
  38. function getExtDesc(ext) {
  39. return ext === 'noExt' ? 'files without extensions' : `extension "${ext}"`;
  40. }
  41. function getOptions(name, options = {}) {
  42. const conf = {
  43. stopDir: os.homedir(),
  44. searchPlaces: getDefaultSearchPlaces(name),
  45. ignoreEmptySearchPlaces: true,
  46. transform: (x) => x,
  47. packageProp: [name],
  48. ...options,
  49. loaders: { ...exports.defaultLoaders, ...options.loaders },
  50. };
  51. conf.searchPlaces.forEach(place => {
  52. const key = path.extname(place) || 'noExt';
  53. const loader = conf.loaders[key];
  54. if (!loader) {
  55. throw new Error(`No loader specified for ${getExtDesc(key)}, so searchPlaces item "${place}" is invalid`);
  56. }
  57. if (typeof loader !== 'function') {
  58. throw new Error(`loader for ${getExtDesc(key)} is not a function (type provided: "${typeof loader}"), so searchPlaces item "${place}" is invalid`);
  59. }
  60. });
  61. return conf;
  62. }
  63. function getPackageProp(props, obj) {
  64. if (typeof props === 'string' && props in obj)
  65. return obj[props];
  66. return ((Array.isArray(props) ? props : props.split('.')).reduce((acc, prop) => (acc === undefined ? acc : acc[prop]), obj) || null);
  67. }
  68. function getSearchItems(searchPlaces, searchPaths) {
  69. return searchPaths.reduce((acc, searchPath) => {
  70. searchPlaces.forEach(fileName => acc.push({
  71. fileName,
  72. filepath: path.join(searchPath, fileName),
  73. loaderKey: path.extname(fileName) || 'noExt',
  74. }));
  75. return acc;
  76. }, []);
  77. }
  78. function validateFilePath(filepath) {
  79. if (!filepath)
  80. throw new Error('load must pass a non-empty string');
  81. }
  82. function validateLoader(loader, ext) {
  83. if (!loader)
  84. throw new Error(`No loader specified for extension "${ext}"`);
  85. if (typeof loader !== 'function')
  86. throw new Error('loader is not a function');
  87. }
  88. function lilconfig(name, options) {
  89. const { ignoreEmptySearchPlaces, loaders, packageProp, searchPlaces, stopDir, transform, } = getOptions(name, options);
  90. return {
  91. async search(searchFrom = process.cwd()) {
  92. const searchPaths = getSearchPaths(searchFrom, stopDir);
  93. const result = {
  94. config: null,
  95. filepath: '',
  96. };
  97. const searchItems = getSearchItems(searchPlaces, searchPaths);
  98. for (const { fileName, filepath, loaderKey } of searchItems) {
  99. try {
  100. await fs.promises.access(filepath);
  101. }
  102. catch (_a) {
  103. continue;
  104. }
  105. const content = String(await fsReadFileAsync(filepath));
  106. const loader = loaders[loaderKey];
  107. if (fileName === 'package.json') {
  108. const pkg = loader(filepath, content);
  109. const maybeConfig = getPackageProp(packageProp, pkg);
  110. if (maybeConfig != null) {
  111. result.config = maybeConfig;
  112. result.filepath = filepath;
  113. break;
  114. }
  115. continue;
  116. }
  117. const isEmpty = content.trim() === '';
  118. if (isEmpty && ignoreEmptySearchPlaces)
  119. continue;
  120. if (isEmpty) {
  121. result.isEmpty = true;
  122. result.config = undefined;
  123. }
  124. else {
  125. validateLoader(loader, loaderKey);
  126. result.config = loader(filepath, content);
  127. }
  128. result.filepath = filepath;
  129. break;
  130. }
  131. if (result.filepath === '' && result.config === null)
  132. return transform(null);
  133. return transform(result);
  134. },
  135. async load(filepath) {
  136. validateFilePath(filepath);
  137. const { base, ext } = path.parse(filepath);
  138. const loaderKey = ext || 'noExt';
  139. const loader = loaders[loaderKey];
  140. validateLoader(loader, loaderKey);
  141. const content = String(await fsReadFileAsync(filepath));
  142. if (base === 'package.json') {
  143. const pkg = await loader(filepath, content);
  144. return transform({
  145. config: getPackageProp(packageProp, pkg),
  146. filepath,
  147. });
  148. }
  149. const result = {
  150. config: null,
  151. filepath,
  152. };
  153. const isEmpty = content.trim() === '';
  154. if (isEmpty && ignoreEmptySearchPlaces)
  155. return transform({
  156. config: undefined,
  157. filepath,
  158. isEmpty: true,
  159. });
  160. result.config = isEmpty
  161. ? undefined
  162. : await loader(filepath, content);
  163. return transform(isEmpty ? { ...result, isEmpty, config: undefined } : result);
  164. },
  165. };
  166. }
  167. exports.lilconfig = lilconfig;
  168. function lilconfigSync(name, options) {
  169. const { ignoreEmptySearchPlaces, loaders, packageProp, searchPlaces, stopDir, transform, } = getOptions(name, options);
  170. return {
  171. search(searchFrom = process.cwd()) {
  172. const searchPaths = getSearchPaths(searchFrom, stopDir);
  173. const result = {
  174. config: null,
  175. filepath: '',
  176. };
  177. const searchItems = getSearchItems(searchPlaces, searchPaths);
  178. for (const { fileName, filepath, loaderKey } of searchItems) {
  179. try {
  180. fs.accessSync(filepath);
  181. }
  182. catch (_a) {
  183. continue;
  184. }
  185. const loader = loaders[loaderKey];
  186. const content = String(fs.readFileSync(filepath));
  187. if (fileName === 'package.json') {
  188. const pkg = loader(filepath, content);
  189. const maybeConfig = getPackageProp(packageProp, pkg);
  190. if (maybeConfig != null) {
  191. result.config = maybeConfig;
  192. result.filepath = filepath;
  193. break;
  194. }
  195. continue;
  196. }
  197. const isEmpty = content.trim() === '';
  198. if (isEmpty && ignoreEmptySearchPlaces)
  199. continue;
  200. if (isEmpty) {
  201. result.isEmpty = true;
  202. result.config = undefined;
  203. }
  204. else {
  205. validateLoader(loader, loaderKey);
  206. result.config = loader(filepath, content);
  207. }
  208. result.filepath = filepath;
  209. break;
  210. }
  211. if (result.filepath === '' && result.config === null)
  212. return transform(null);
  213. return transform(result);
  214. },
  215. load(filepath) {
  216. validateFilePath(filepath);
  217. const { base, ext } = path.parse(filepath);
  218. const loaderKey = ext || 'noExt';
  219. const loader = loaders[loaderKey];
  220. validateLoader(loader, loaderKey);
  221. const content = String(fs.readFileSync(filepath));
  222. if (base === 'package.json') {
  223. const pkg = loader(filepath, content);
  224. return transform({
  225. config: getPackageProp(packageProp, pkg),
  226. filepath,
  227. });
  228. }
  229. const result = {
  230. config: null,
  231. filepath,
  232. };
  233. const isEmpty = content.trim() === '';
  234. if (isEmpty && ignoreEmptySearchPlaces)
  235. return transform({
  236. filepath,
  237. config: undefined,
  238. isEmpty: true,
  239. });
  240. result.config = isEmpty ? undefined : loader(filepath, content);
  241. return transform(isEmpty ? { ...result, isEmpty, config: undefined } : result);
  242. },
  243. };
  244. }
  245. exports.lilconfigSync = lilconfigSync;