You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
9.3 KiB

2 years ago
  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 = await 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 = await 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 absPath = path.resolve(process.cwd(), filepath);
  138. const { base, ext } = path.parse(absPath);
  139. const loaderKey = ext || 'noExt';
  140. const loader = loaders[loaderKey];
  141. validateLoader(loader, loaderKey);
  142. const content = String(await fsReadFileAsync(absPath));
  143. if (base === 'package.json') {
  144. const pkg = await loader(absPath, content);
  145. return transform({
  146. config: getPackageProp(packageProp, pkg),
  147. filepath: absPath,
  148. });
  149. }
  150. const result = {
  151. config: null,
  152. filepath: absPath,
  153. };
  154. const isEmpty = content.trim() === '';
  155. if (isEmpty && ignoreEmptySearchPlaces)
  156. return transform({
  157. config: undefined,
  158. filepath: absPath,
  159. isEmpty: true,
  160. });
  161. result.config = isEmpty
  162. ? undefined
  163. : await loader(absPath, content);
  164. return transform(isEmpty ? { ...result, isEmpty, config: undefined } : result);
  165. },
  166. };
  167. }
  168. exports.lilconfig = lilconfig;
  169. function lilconfigSync(name, options) {
  170. const { ignoreEmptySearchPlaces, loaders, packageProp, searchPlaces, stopDir, transform, } = getOptions(name, options);
  171. return {
  172. search(searchFrom = process.cwd()) {
  173. const searchPaths = getSearchPaths(searchFrom, stopDir);
  174. const result = {
  175. config: null,
  176. filepath: '',
  177. };
  178. const searchItems = getSearchItems(searchPlaces, searchPaths);
  179. for (const { fileName, filepath, loaderKey } of searchItems) {
  180. try {
  181. fs.accessSync(filepath);
  182. }
  183. catch (_a) {
  184. continue;
  185. }
  186. const loader = loaders[loaderKey];
  187. const content = String(fs.readFileSync(filepath));
  188. if (fileName === 'package.json') {
  189. const pkg = loader(filepath, content);
  190. const maybeConfig = getPackageProp(packageProp, pkg);
  191. if (maybeConfig != null) {
  192. result.config = maybeConfig;
  193. result.filepath = filepath;
  194. break;
  195. }
  196. continue;
  197. }
  198. const isEmpty = content.trim() === '';
  199. if (isEmpty && ignoreEmptySearchPlaces)
  200. continue;
  201. if (isEmpty) {
  202. result.isEmpty = true;
  203. result.config = undefined;
  204. }
  205. else {
  206. validateLoader(loader, loaderKey);
  207. result.config = loader(filepath, content);
  208. }
  209. result.filepath = filepath;
  210. break;
  211. }
  212. if (result.filepath === '' && result.config === null)
  213. return transform(null);
  214. return transform(result);
  215. },
  216. load(filepath) {
  217. validateFilePath(filepath);
  218. const absPath = path.resolve(process.cwd(), filepath);
  219. const { base, ext } = path.parse(absPath);
  220. const loaderKey = ext || 'noExt';
  221. const loader = loaders[loaderKey];
  222. validateLoader(loader, loaderKey);
  223. const content = String(fs.readFileSync(absPath));
  224. if (base === 'package.json') {
  225. const pkg = loader(absPath, content);
  226. return transform({
  227. config: getPackageProp(packageProp, pkg),
  228. filepath: absPath,
  229. });
  230. }
  231. const result = {
  232. config: null,
  233. filepath: absPath,
  234. };
  235. const isEmpty = content.trim() === '';
  236. if (isEmpty && ignoreEmptySearchPlaces)
  237. return transform({
  238. filepath: absPath,
  239. config: undefined,
  240. isEmpty: true,
  241. });
  242. result.config = isEmpty ? undefined : loader(absPath, content);
  243. return transform(isEmpty ? { ...result, isEmpty, config: undefined } : result);
  244. },
  245. };
  246. }
  247. exports.lilconfigSync = lilconfigSync;