UNPKG

@redocly/openapi-core

Version:

See https://github.com/Redocly/redocly-cli

214 lines (193 loc) 5.91 kB
import * as fs from 'fs'; import * as path from 'path'; import { RedoclyClient } from '../redocly'; import { isEmptyObject } from '../utils'; import { parseYaml } from '../js-yaml'; import { ConfigValidationError, transformConfig, deepCloneMapWithJSON } from './utils'; import { resolveConfig, resolveConfigFileAndRefs } from './config-resolvers'; import { bundleConfig } from '../bundle'; import { BaseResolver } from '../resolve'; import { isBrowser } from '../env'; import { DOMAINS } from '../redocly/domains'; import type { Config } from './config'; import type { Document, ResolvedRefMap } from '../resolve'; import type { RegionalToken, RegionalTokenWithValidity } from '../redocly/redocly-client-types'; import type { RawConfig, RawUniversalConfig, Region } from './types'; async function addConfigMetadata({ rawConfig, customExtends, configPath, tokens, files, region, externalRefResolver, }: { rawConfig: RawConfig; customExtends?: string[]; configPath?: string; tokens?: RegionalToken[]; files?: string[]; region?: Region; externalRefResolver?: BaseResolver; }): Promise<Config> { if (customExtends !== undefined) { rawConfig.styleguide = rawConfig.styleguide || {}; rawConfig.styleguide.extends = customExtends; } else if (isEmptyObject(rawConfig)) { rawConfig.styleguide = { extends: ['recommended'], recommendedFallback: true }; } if (tokens?.length) { if (!rawConfig.resolve) rawConfig.resolve = {}; if (!rawConfig.resolve.http) rawConfig.resolve.http = {}; rawConfig.resolve.http.headers = [...(rawConfig.resolve.http.headers ?? [])]; for (const item of tokens) { const domain = DOMAINS[item.region as Region]; rawConfig.resolve.http.headers.push( { matches: `https://api.${domain}/registry/**`, name: 'Authorization', envVariable: undefined, value: item.token, }, //support redocly.com domain for future compatibility ...(item.region === 'us' ? [ { matches: `https://api.redoc.ly/registry/**`, name: 'Authorization', envVariable: undefined, value: item.token, }, ] : []) ); } } return resolveConfig({ rawConfig: { ...rawConfig, files: files ?? rawConfig.files, region: region ?? rawConfig.region, }, configPath, externalRefResolver, }); } export type RawConfigProcessor = (params: { document: Document; resolvedRefMap: ResolvedRefMap; config: Config; parsed: Document['parsed']; }) => void | Promise<void>; export async function loadConfig( options: { configPath?: string; customExtends?: string[]; processRawConfig?: RawConfigProcessor; externalRefResolver?: BaseResolver; files?: string[]; region?: Region; } = {} ): Promise<Config> { const { configPath = findConfig(), customExtends, processRawConfig, files, region, externalRefResolver, } = options; const { rawConfig, document, parsed, resolvedRefMap } = await getConfig({ configPath, externalRefResolver, }); const redoclyClient = isBrowser ? undefined : new RedoclyClient(); const tokens = redoclyClient && redoclyClient.hasTokens() ? redoclyClient.getAllTokens() : []; const config = await addConfigMetadata({ rawConfig, customExtends, configPath, tokens, files, region, externalRefResolver, }); if (document && parsed && resolvedRefMap && typeof processRawConfig === 'function') { try { await processRawConfig({ document, resolvedRefMap, config, parsed, }); } catch (e) { if (e instanceof ConfigValidationError) { throw e; } throw new Error(`Error parsing config file at '${configPath}': ${e.message}`); } } return config; } export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml']; export function findConfig(dir?: string): string | undefined { if (!fs?.hasOwnProperty?.('existsSync')) return; const existingConfigFiles = CONFIG_FILE_NAMES.map((name) => dir ? path.resolve(dir, name) : name ).filter(fs.existsSync); if (existingConfigFiles.length > 1) { throw new Error(` Multiple configuration files are not allowed. Found the following files: ${existingConfigFiles.join(', ')}. Please use 'redocly.yaml' instead. `); } return existingConfigFiles[0]; } export async function getConfig( options: { configPath?: string; externalRefResolver?: BaseResolver; } = {} ): Promise<{ rawConfig: RawConfig; document?: Document; parsed?: Document['parsed']; resolvedRefMap?: ResolvedRefMap; }> { const { configPath = findConfig(), externalRefResolver = new BaseResolver() } = options; if (!configPath) return { rawConfig: {} }; try { const { document, resolvedRefMap } = await resolveConfigFileAndRefs({ configPath, externalRefResolver, }); const bundledRefMap = deepCloneMapWithJSON(resolvedRefMap); const parsed = await bundleConfig(JSON.parse(JSON.stringify(document)), bundledRefMap); return { rawConfig: transformConfig(parsed), document, parsed, resolvedRefMap, }; } catch (e) { throw new Error(`Error parsing config file at '${configPath}': ${e.message}`); } } type CreateConfigOptions = { extends?: string[]; tokens?: RegionalTokenWithValidity[]; configPath?: string; externalRefResolver?: BaseResolver; }; export async function createConfig( config: string | RawUniversalConfig, options?: CreateConfigOptions ): Promise<Config> { return addConfigMetadata({ rawConfig: transformConfig( typeof config === 'string' ? (parseYaml(config) as RawConfig) : config ), ...options, }); }