I am creating an npm package that needs to be able to read config files from the project root. I'm not sure how to do this.
For example,
- Next.js is able to read
./pages/
and./next.config.js
from the project root - Jest is able to read
./jest.config.js
from the project root - ESLint is able to read
./.eslintrc.json
from the project root - Prettier is able to read
./.prettierrc.js
from the project root - Typescript is able to read
./tsconfig.json
from the project root - Babel is able to read
./.babelrc
from the project root
I've tried looking at their source code to see how they do it but the projects are so large that I can't find the relevant section.
How do they achieve this?
I am creating an npm package that needs to be able to read config files from the project root. I'm not sure how to do this.
For example,
- Next.js is able to read
./pages/
and./next.config.js
from the project root - Jest is able to read
./jest.config.js
from the project root - ESLint is able to read
./.eslintrc.json
from the project root - Prettier is able to read
./.prettierrc.js
from the project root - Typescript is able to read
./tsconfig.json
from the project root - Babel is able to read
./.babelrc
from the project root
I've tried looking at their source code to see how they do it but the projects are so large that I can't find the relevant section.
How do they achieve this?
Share Improve this question edited Jun 26, 2019 at 5:40 Jake asked Jun 24, 2019 at 3:25 JakeJake 4,2556 gold badges33 silver badges64 bronze badges 2 |5 Answers
Reset to default 4First search in path.dirname(process.mainModule.filename)
then go up the directory tree ../, ../../, ../../../
and so on until you find the configuration file.
Here is the code I use stolen from rc (https://github.com/dominictarr/rc) package, it will read and json parse configuration from a file named .projectrc
:
const fs = require('fs');
const path = require('path');
// Utils shamefully stolen from
// https://github.com/dominictarr/rc/blob/master/lib/utils.js
find(...args) {
const rel = path.join.apply(null, [].slice.call(args));
return findStartingWith(path.dirname(process.mainModule.filename), rel);
}
findStartingWith(start, rel) {
const file = path.join(start, rel);
try {
fs.statSync(file);
return file;
} catch (err) {
// They are equal for root dir
if (path.dirname(start) !== start) {
return findStartingWith(path.dirname(start), rel);
}
}
}
parse(content) {
if (/^\s*{/.test(content)) {
return JSON.parse(content);
}
return undefined;
}
file(...args) {
const nonNullArgs = [].slice.call(args).filter(arg => arg != null);
// path.join breaks if it's a not a string, so just skip this.
for (let i = 0; i < nonNullArgs.length; i++) {
if (typeof nonNullArgs[i] !== 'string') {
return;
}
}
const file = path.join.apply(null, nonNullArgs);
try {
return fs.readFileSync(file, 'utf-8');
} catch (err) {
return undefined;
}
}
json(...args) {
const content = file.apply(null, args);
return content ? parse(content) : null;
}
// Find the rc file path
const rcPath = find('.projectrc');
// Or
// const rcPath = find('/.config', '.projectrc');
// Read the contents as json
const rcObject = json(rcPath);
console.log(rcObject);
You can also use the rc package as a dependency npm i rc
, then in your code:
var configuration = require('rc')(appname, {
// Default configuration goes here
port: 2468
});
This will read config from a file named .${appname}rc
.
Ran into this problem when I made my first npm
package. The findup-sync
library solves this nicely:
const findup = require('findup-sync');
const filePath = findup('filename');
https://www.npmjs.com/package/findup-sync
They start from the directory the file lives in and recursively looks upwards in the file system tree until it finds the file it's looks for.
Something like this:
const FILE_NAME = 'target-file.json';
const fsp = require('fs').promises,
path = require('path');
let find = async (dir=__dirname) => {
let ls = await fsp.readdir(dir);
if(ls.includes(FILE_NAME))
return path.join(dir,FILE_NAME);
else if(dir == '/')
throw new Error(`Could not find ${FILE_NAME}`);
else
return find(path.resolve(dir,'..'));
}
Or if you were looking for a standard node "project root" you might want to recurse up and find a directory containing a directory names 'node_modules' like this:
const fsp = require('fs').promises,
path = require('path');
let find = async (dir=__dirname) => {
let ls = await fsp.readdir(dir);
if(ls.includes('node_modules'))
return dir;
else if(dir == '/')
throw new Error(`Could not find project root`);
else
return find(path.resolve(dir,'..'));
}
I was also looking to solve similar problem with my npm package and found out these 2 modules. I have used lilconfig as its smaller in size and satisfies my use case.
lilconfig (16.6 kB)
const {lilconfigSync} = require('lilconfig');
const configSync = lilconfigSync("filename");
const config = configSync.search();
cosmiconfig (111 kB)
const { cosmiconfigSync } = require('cosmiconfig');
const explorerSync = cosmiconfigSync("filename");
const searchedFor = explorerSync.search();
There are multiple ways to do it. I've created a test-package, and a demo project node-package-test to test it.
For ready reference providing the main code here:
project-main\node_modules\test-package\index.js :
const path = require('path');
const fs = require('fs');
const CONFIG_NAME = 'cfg.json';
function init(rootDir = null) {
console.log(`test-package: process.cwd(): ${process.cwd()}`);
console.log(`test-package: path.resolve('./'): ${path.resolve('./')}`);
if (!rootDir) {
//rootDir = path.resolve('./');
// OR
rootDir = process.cwd();
}
//const configPath = path.resolve('./', CONFIG_NAME);
// OR
const configPath = path.join(rootDir, CONFIG_NAME);
if (fs.existsSync(configPath)) {
console.log(`test-package: Reading config from: ${configPath}`);
try {
//const data = fs.readFileSync(configPath, 'utf8');
//const config = JSON.parse(data);
// OR
const config = require(configPath);
console.log(config);
} catch (err) {
console.error(err)
}
} else {
console.log(`test-package: Couldn't find config file ${configPath}. Using default.`)
}
console.log('\n')
}
//init()
const features = {
init: init,
message: `Hello from test-package!
require()
,fs.readFile()
, orfs.readFileSync()
. If either of the last two methods are used they subsequentlyJSON.parse()
content if config file is JSON. For instance eslintrc.json utilizesfs.readFileSync
. – RobC Commented Jun 30, 2019 at 16:47.eslintrc
files starting from the current directory (or from another directory provided by the user) up to the file system root. npm does something similar when searching forpackage.json
, except that it stops on the first match. – GOTO 0 Commented Feb 10, 2022 at 15:16