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

View File

@ -31,94 +31,94 @@ describe('@jsdoc/core/lib/config', () => {
expect(config).toBeObject(); expect(config).toBeObject();
}); });
describe('loadSync', () => { describe('load', () => {
it('is a function', () => { 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({ mockFs({
'conf.json': '{}', 'conf.json': '{}',
}); });
const conf = config.loadSync('conf.json'); const conf = await config.load('conf.json');
expect(conf.config).toBeObject(); expect(conf.config).toBeObject();
expect(conf.filepath).toEndWith('conf.json'); 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({ mockFs({
'conf.json': '{"foo":"bar"}', 'conf.json': '{"foo":"bar"}',
}); });
const conf = config.loadSync('conf.json'); const conf = await config.load('conf.json');
expect(conf.config.foo).toBe('bar'); 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({ mockFs({
'package.json': '{"jsdoc":{"foo":"bar"}}', 'package.json': '{"jsdoc":{"foo":"bar"}}',
}); });
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config.foo).toBe('bar'); 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({ mockFs({
'.jsdocrc.json': '// comment\n{"foo":"bar"}', '.jsdocrc.json': '// comment\n{"foo":"bar"}',
}); });
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config.foo).toBe('bar'); 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({ mockFs({
'.jsdocrc.json': '\uFEFF{"foo":"bar"}', '.jsdocrc.json': '\uFEFF{"foo":"bar"}',
}); });
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config.foo).toBe('bar'); 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({ mockFs({
'.jsdocrc.yaml': '\uFEFF{"foo":"bar"}', '.jsdocrc.yaml': '\uFEFF{"foo":"bar"}',
}); });
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config.foo).toBe('bar'); 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({ mockFs({
'.jsdocrc.json': '{}', '.jsdocrc.json': '{}',
}); });
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config).toEqual(config.defaults); expect(conf.config).toEqual(config.defaults);
}); });
it('provides the default config if there is no user config', () => { it('provides the default config if there is no user config', async () => {
const conf = config.loadSync(); const conf = await config.load();
expect(conf.config).toEqual(config.defaults); 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({ mockFs({
'.jsdocrc.json': '{"tags":{"foo":"bar"}}', '.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.allowUnknownTags).toBe(config.defaults.tags.allowUnknownTags);
expect(conf.config.tags.foo).toBe('bar'); expect(conf.config.tags.foo).toBe('bar');

View File

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