Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and enhance config file handling logic #39

Merged
merged 2 commits into from
Aug 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 35 additions & 26 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,21 @@

/**
* An array containing all known configuration file's extension names.
*
* @type {Readonly<Array<('.js' | '.mjs' | '.cjs' | '.json')>>}
* @readonly
* @default
* @package
* @since 1.0.0
*/
const KNOWN_CONFIG_EXTS = [ '.js', '.mjs', '.cjs', '.json' ];
/**
* An array containing all known configuration options.
*
* @type {Readonly<Array<('downloadOptions' | 'audioConverterOptions')>>}
* @readonly
* @default
* @package
* @since 1.0.0
*/
const KNOWN_OPTIONS = [ 'downloadOptions', 'audioConverterOptions' ];
Expand All @@ -114,7 +122,10 @@
* as the directory name of configuration file, and the third one as the base name
* of the configuration file.
*
* @type {string}
* @constant
* @default '%s\n\tat \x1b[90m%s\n\x1b[1;91m%s\x1b[0m\n'
* @package
* @since 1.0.0
*/
const ERR_FORMAT = '%s\n\tat \x1b[90m%s\n\x1b[1;91m%s\x1b[0m\n';
Expand All @@ -137,12 +148,13 @@
*
* @throws {TypeError} If any known options is not object type.
*
* @public
* @package
* @since 1.0.0
*/
function resolveConfig({ config, file }) {
if (isNullOrUndefined(config) || !isObject(config)) return {};
if (!file || typeof file === 'string') file = 'unknown';
// Set file to 'unknown' if given file is null or not a string type
if (isNullOrUndefined(file) || typeof file !== 'string') file = 'unknown';

// Check and validate the configuration
configChecker({ config, file });
Expand All @@ -152,16 +164,16 @@
let downloadOptions = config.downloadOptions || {};
let audioConverterOptions;
if ('converterOptions' in downloadOptions) {
audioConverterOptions = downloadOptions.converterOptions || {};
audioConverterOptions = downloadOptions.converterOptions;

Check warning on line 167 in lib/config.js

View check run for this annotation

Codecov / codecov/patch

lib/config.js#L167

Added line #L167 was not covered by tests
} else if ('audioConverterOptions' in config) {
audioConverterOptions = config.audioConverterOptions || {};
audioConverterOptions = config.audioConverterOptions;
}

// Resolve the download options
downloadOptions = ytmp3.resolveDlOptions({ downloadOptions });
// Resolve the audio converter options, but all unspecified options will
// fallback to undefined value instead their default value
audioConverterOptions = resolveACOptions(audioConverterOptions || {}, false);
audioConverterOptions = resolveACOptions(audioConverterOptions, false);

// Assign the `audioConverterOptions` to `downloadOptions`
Object.assign(downloadOptions, {
Expand All @@ -186,7 +198,7 @@
* @throws {TypeError} If any known options is not object type.
* @throws {UnknownOptionError} If there are unknown fields in the configuration.
*
* @private
* @package
* @since 1.0.0
*/
function configChecker({ config, file }) {
Expand Down Expand Up @@ -224,11 +236,11 @@
* When importing an ES module, it returns a `Promise` that resolves to the configuration
* object. It also supports optional resolution of the configuration.
*
* @param {!string} configFile - The path or URL to the configuration file.
* @param {!string} configFile - A string path refers to the configuration file.
* @param {boolean} [resolve=true] - Determines whether to resolve the configuration object.
* If set to `false`, will validate the configuration only.
*
* @returns {YTMP3Config | DownloadOptions | Promise<YTMP3Config | DownloadOptions>}
* @returns {YTMP3Config | DownloadOptions | Promise<(YTMP3Config | DownloadOptions)>}
* The configuration object or a `Promise` that fullfilled with the
* configuration object if an ES module is imported. The returned configuration
* object will be automatically resolved if `resolve` is set to `true`.
Expand All @@ -247,9 +259,11 @@
* console.error('Failed to load config:', error);
* });
*
* @public
* @package
* @since 1.0.0
* @see {@link module:config~resolveConfig `resolveConfig({ config, file })`}
* @see {@link module:config~resolveConfig resolveConfig}
* @see {@link module:config~importConfig importConfig}
* (alias for <code>parseConfig(config, true)</code>)
*/
function parseConfig(configFile, resolve=true) {
function resolveOrCheckOnly(config) {
Expand All @@ -260,7 +274,6 @@
if (isObject(config) && Object.keys(config).length === 1 && 'default' in config) {
config = config.default; // Extract the default export
}

// Resolve the configuration object if `resolve` is set to true
if (resolve) config = resolveConfig({ config, file }); // Return {} if null
// Otherwise, only validate the configuration
Expand All @@ -282,20 +295,15 @@
}

// Resolve the configuration file path
configFile = path.resolve(((configFile instanceof URL) ? configFile.href : configFile));
configFile = path.resolve(configFile);

// Import the configuration file
let config = null;
try {
// Only include '.cjs' and '.json' to use require()
if (KNOWN_CONFIG_EXTS.slice(2).includes(path.extname(configFile))) {
config = require(configFile);
} catch (importErr) {
if ('code' in importErr && importErr.code === 'ERR_REQUIRE_ESM') {
// This error occurred due to attempting to import ESM module with `require()`
config = import(configFile);
} else {
// Otherwise, throw back any error
throw importErr;
}
} else {
config = import(configFile);

Check warning on line 306 in lib/config.js

View check run for this annotation

Codecov / codecov/patch

lib/config.js#L306

Added line #L306 was not covered by tests
}

if (config instanceof Promise) {
Expand All @@ -310,20 +318,21 @@

/**
* An alias for {@link module:config~parseConfig `parseConfig`} function,
* with `resolve` argument is set to true.
* with `resolve` parameter is set to `true`.
*
* @param {string} file - A string path refers to configuration file to import and resolve.
* @function
* @param {!string} file - A string path refers to configuration file to import and resolve.
* @returns {YTMP3Config | DownloadOptions | Promise<(YTMP3Config | DownloadOptions)>}
*
* @public
* @function
* @package
* @since 1.0.0
* @see {@link module:config~parseConfig `parseConfig`}
* @see {@link module:config~parseConfig parseConfig}
*/
const importConfig = (file) => parseConfig(file, true);


module.exports = Object.freeze({
configChecker,
resolveConfig,
parseConfig,
importConfig
Expand Down