import path from 'path'; import 'resolve-global'; import resolveFrom from 'resolve-from'; import merge from 'lodash/merge'; import mergeWith from 'lodash/mergeWith'; const importFresh = require('import-fresh'); export interface ResolvedConfig { parserPreset?: unknown; [key: string]: unknown; } export interface ResolveExtendsConfig { parserPreset?: unknown; extends?: string | string[]; [key: string]: unknown; } export interface ResolveExtendsContext { cwd?: string; parserPreset?: unknown; prefix?: string; resolve?(id: string, ctx?: {prefix?: string; cwd?: string}): string; resolveGlobal?: (id: string) => string; require?<T>(id: string): T; } export default function resolveExtends( config: ResolveExtendsConfig = {}, context: ResolveExtendsContext = {} ) { const {extends: e} = config; const extended = loadExtends(config, context).reduce( (r, {extends: _, ...c}) => mergeWith(r, c, (objValue, srcValue) => { if (Array.isArray(objValue)) { return srcValue; } }), e ? {extends: e} : {} ); return merge({}, extended, config); } function loadExtends( config: ResolveExtendsConfig = {}, context: ResolveExtendsContext = {} ): ResolvedConfig[] { const {extends: e} = config; const ext = e ? (Array.isArray(e) ? e : [e]) : []; return ext.reduce<ResolvedConfig[]>((configs, raw) => { const load = context.require || require; const resolved = resolveConfig(raw, context); const c = load(resolved); const cwd = path.dirname(resolved); const ctx = merge({}, context, {cwd}); // Resolve parser preset if none was present before if ( !context.parserPreset && typeof c === 'object' && typeof c.parserPreset === 'string' ) { const resolvedParserPreset = resolveFrom(cwd, c.parserPreset); const parserPreset = { name: c.parserPreset, path: `./${path.relative(process.cwd(), resolvedParserPreset)}` .split(path.sep) .join('/'), parserOpts: require(resolvedParserPreset), }; ctx.parserPreset = parserPreset; config.parserPreset = parserPreset; } return [...configs, ...loadExtends(c, ctx), c]; }, []); } function getId(raw: string = '', prefix: string = ''): string { const first = raw.charAt(0); const scoped = first === '@'; const relative = first === '.'; const absolute = path.isAbsolute(raw); if (scoped) { return raw.includes('/') ? raw : [raw, prefix].filter(String).join('/'); } return relative || absolute ? raw : [prefix, raw].filter(String).join('-'); } function resolveConfig( raw: string, context: ResolveExtendsContext = {} ): string { const resolve = context.resolve || resolveId; const id = getId(raw, context.prefix); try { return resolve(id, context); } catch (err) { const legacy = getId(raw, 'conventional-changelog-lint-config'); const resolved = resolve(legacy, context); console.warn( `Resolving ${raw} to legacy config ${legacy}. To silence this warning raise an issue at 'npm repo ${legacy}' to rename to ${id}.` ); return resolved; } } function resolveId( id: string, context: {cwd?: string; resolveGlobal?: (id: string) => string | void} = {} ): string { const cwd = context.cwd || process.cwd(); const localPath = resolveFromSilent(cwd, id); if (typeof localPath === 'string') { return localPath; } const resolveGlobal = context.resolveGlobal || resolveGlobalSilent; const globalPath = resolveGlobal(id); if (typeof globalPath === 'string') { return globalPath; } const err = new Error(`Cannot find module "${id}" from "${cwd}"`); (err as any).code = 'MODULE_NOT_FOUND'; throw err; } function resolveFromSilent(cwd: string, id: string): string | void { try { return resolveFrom(cwd, id); } catch (err) {} } function resolveGlobalSilent(id: string): string | void { try { const resolveGlobal = importFresh('resolve-global'); return resolveGlobal(id); } catch (err) {} }