refactor: make config loading asynchronous

Workaround for https://github.com/tschaub/mock-fs/issues/377, which causes `fs.readFileSync()` to fail on Node.js >=20.5.0.

Fixes #2097.
This commit is contained in:
Jeff Williams 2023-12-03 15:42:56 -08:00
parent 3e4f5fc557
commit a82263f925
No known key found for this signature in database
4 changed files with 30 additions and 28 deletions

View File

@ -18,7 +18,7 @@
*
* @alias module:@jsdoc/core.config
*/
import { cosmiconfigSync, defaultLoaders } from 'cosmiconfig';
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
import _ from 'lodash';
import stripBom from 'strip-bom';
import stripJsonComments from 'strip-json-comments';
@ -94,7 +94,7 @@ function loadYaml(filepath, content) {
return defaultLoaders['.yaml'](filepath, stripBom(content));
}
const explorerSync = cosmiconfigSync(MODULE_NAME, {
const explorer = cosmiconfig(MODULE_NAME, {
cache: false,
loaders: {
'.json': loadJson,
@ -113,13 +113,13 @@ const explorerSync = cosmiconfigSync(MODULE_NAME, {
],
});
export function loadSync(filepath) {
export async function load(filepath) {
let loaded;
if (filepath) {
loaded = explorerSync.load(filepath);
loaded = await explorer.load(filepath);
} else {
loaded = explorerSync.search() || {};
loaded = (await explorer.search()) ?? {};
}
return new Config(loaded.filepath, _.defaultsDeep({}, loaded.config, defaults));

View File

@ -31,94 +31,94 @@ describe('@jsdoc/core/lib/config', () => {
expect(config).toBeObject();
});
describe('loadSync', () => {
describe('load', () => {
it('is a function', () => {
expect(config.loadSync).toBeFunction();
expect(config.load).toBeFunction();
});
it('returns an object with `config` and `filepath` properties', () => {
it('returns an object with `config` and `filepath` properties', async () => {
mockFs({
'conf.json': '{}',
});
const conf = config.loadSync('conf.json');
const conf = await config.load('conf.json');
expect(conf.config).toBeObject();
expect(conf.filepath).toEndWith('conf.json');
});
it('loads settings from the specified filepath if there is one', () => {
it('loads settings from the specified filepath if there is one', async () => {
mockFs({
'conf.json': '{"foo":"bar"}',
});
const conf = config.loadSync('conf.json');
const conf = await config.load('conf.json');
expect(conf.config.foo).toBe('bar');
});
it('finds the config file when no filepath is specified', () => {
it('finds the config file when no filepath is specified', async () => {
mockFs({
'package.json': '{"jsdoc":{"foo":"bar"}}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON config files that have an extension and contain comments', () => {
it('parses JSON config files that have an extension and contain comments', async () => {
mockFs({
'.jsdocrc.json': '// comment\n{"foo":"bar"}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config.foo).toBe('bar');
});
it('parses JSON files that start with a BOM', () => {
it('parses JSON files that start with a BOM', async () => {
mockFs({
'.jsdocrc.json': '\uFEFF{"foo":"bar"}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config.foo).toBe('bar');
});
it('parses YAML files that start with a BOM', () => {
it('parses YAML files that start with a BOM', async () => {
mockFs({
'.jsdocrc.yaml': '\uFEFF{"foo":"bar"}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config.foo).toBe('bar');
});
it('provides the default config if the user config is an empty object', () => {
it('provides the default config if the user config is an empty object', async () => {
mockFs({
'.jsdocrc.json': '{}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config).toEqual(config.defaults);
});
it('provides the default config if there is no user config', () => {
const conf = config.loadSync();
it('provides the default config if there is no user config', async () => {
const conf = await config.load();
expect(conf.config).toEqual(config.defaults);
});
it('merges nested defaults with nested user settings as expected', () => {
it('merges nested defaults with nested user settings as expected', async () => {
mockFs({
'.jsdocrc.json': '{"tags":{"foo":"bar"}}',
});
const conf = config.loadSync();
const conf = await config.load();
expect(conf.config.tags.allowUnknownTags).toBe(config.defaults.tags.allowUnknownTags);
expect(conf.config.tags.foo).toBe('bar');

View File

@ -85,7 +85,7 @@ export default (() => {
};
// TODO: docs
cli.loadConfig = () => {
cli.loadConfig = async () => {
const env = dependencies.get('env');
try {
@ -98,7 +98,7 @@ export default (() => {
}
try {
env.conf = config.loadSync(env.opts.configure).config;
env.conf = (await config.load(env.opts.configure)).config;
} catch (e) {
cli.exit(1, `Cannot parse the config file: ${e}\n${FATAL_ERROR_MESSAGE}`);

View File

@ -20,7 +20,9 @@ import cli from './cli.js';
(async () => {
env.args = process.argv.slice(2);
cli.setEnv(env).setVersionInfo().loadConfig().configureLogger().logStart();
cli.setEnv(env).setVersionInfo();
await cli.loadConfig();
cli.configureLogger().logStart();
await cli.runCommand();
})();