import Path from 'path'; import merge from 'lodash/merge'; import mergeWith from 'lodash/mergeWith'; import pick from 'lodash/pick'; import union from 'lodash/union'; import resolveFrom from 'resolve-from'; import executeRule from '@commitlint/execute-rule'; import resolveExtends from '@commitlint/resolve-extends'; import { UserConfig, LoadOptions, QualifiedConfig, UserPreset, QualifiedRules, ParserPreset, } from '@commitlint/types'; import loadPlugin from './utils/load-plugin'; import {loadConfig} from './utils/load-config'; import {loadParserOpts} from './utils/load-parser-opts'; import {pickConfig} from './utils/pick-config'; const w = <T>(_: unknown, b: ArrayLike<T> | null | undefined | false) => Array.isArray(b) ? b : undefined; export default async function load( seed: UserConfig = {}, options: LoadOptions = {} ): Promise<QualifiedConfig> { const cwd = typeof options.cwd === 'undefined' ? process.cwd() : options.cwd; const loaded = await loadConfig(cwd, options.file); const base = loaded && loaded.filepath ? Path.dirname(loaded.filepath) : cwd; // TODO: validate loaded.config against UserConfig type // Might amount to breaking changes, defer until 9.0.0 // Merge passed config with file based options const config = pickConfig(merge({}, loaded ? loaded.config : null, seed)); const opts = merge( {extends: [], rules: {}, formatter: '@commitlint/format'}, pick(config, 'extends', 'plugins', 'ignores', 'defaultIgnores') ); // Resolve parserPreset key if (typeof config.parserPreset === 'string') { const resolvedParserPreset = resolveFrom(base, config.parserPreset); config.parserPreset = { name: config.parserPreset, path: resolvedParserPreset, parserOpts: require(resolvedParserPreset), }; } // Resolve extends key const extended = resolveExtends(opts, { prefix: 'commitlint-config', cwd: base, parserPreset: config.parserPreset, }); const preset = (pickConfig( mergeWith(extended, config, w) ) as unknown) as UserPreset; preset.plugins = {}; // TODO: check if this is still necessary with the new factory based conventional changelog parsers // config.extends = Array.isArray(config.extends) ? config.extends : []; // Resolve parser-opts from preset if (typeof preset.parserPreset === 'object') { preset.parserPreset.parserOpts = await loadParserOpts( preset.parserPreset.name, // TODO: fix the types for factory based conventional changelog parsers preset.parserPreset as any ); } // Resolve config-relative formatter module if (typeof config.formatter === 'string') { preset.formatter = resolveFrom.silent(base, config.formatter) || config.formatter; } // Read plugins from extends if (Array.isArray(extended.plugins)) { config.plugins = union(config.plugins, extended.plugins || []); } // resolve plugins if (Array.isArray(config.plugins)) { config.plugins.forEach((plugin) => { if (typeof plugin === 'string') { loadPlugin(preset.plugins, plugin, process.env.DEBUG === 'true'); } else { preset.plugins.local = plugin; } }); } const rules = preset.rules ? preset.rules : {}; const qualifiedRules = ( await Promise.all( Object.entries(rules || {}).map((entry) => executeRule<any>(entry)) ) ).reduce<QualifiedRules>((registry, item) => { const [key, value] = item as any; (registry as any)[key] = value; return registry; }, {}); const helpUrl = typeof config.helpUrl === 'string' ? config.helpUrl : 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint'; return { extends: preset.extends!, formatter: preset.formatter!, parserPreset: preset.parserPreset! as ParserPreset, ignores: preset.ignores!, defaultIgnores: preset.defaultIgnores!, plugins: preset.plugins!, rules: qualifiedRules, helpUrl, }; }