UNPKG

@redocly/cli

Version:

[@Redocly](https://redocly.com) CLI is your all-in-one API documentation utility. It builds, manages, improves, and quality-checks your API descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make

119 lines 5.67 kB
import { BaseResolver, logger } from '@redocly/openapi-core'; import { blue, bold, cyan, gray, green, white } from 'colorette'; import { AbortFlowError, exitWithError } from '../../utils/error.js'; import { formatPath, getExecutionTime, getFallbackApisOrExit, getAliasOrPath, } from '../../utils/miscellaneous.js'; import { handleLoginAndFetchToken } from './auth/login-handler.js'; import { printScorecardResultsAsCheckstyle } from './formatters/checkstyle-formatter.js'; import { printScorecardResultsAsJson } from './formatters/json-formatter.js'; import { printScorecardResults } from './formatters/stylish-formatter.js'; import { fetchRemoteScorecardAndPlugins } from './remote/fetch-scorecard.js'; import { getTarget } from './targets-handler/targets-handler.js'; import { isAllowedScorecardProjectUrl } from './validation/project-url.js'; import { validateScorecard } from './validation/validate-scorecard.js'; export async function handleScorecardClassic({ argv, config, version, collectSpecData, }) { const startedAt = performance.now(); const apis = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config); if (!apis.length) { exitWithError('No APIs were provided.'); } const projectUrl = argv['project-url'] || config.resolvedConfig.scorecardClassic?.fromProjectUrl || config.resolvedConfig.scorecard?.fromProjectUrl; if (!projectUrl) { exitWithError('Scorecard is not configured. Please provide it via --project-url flag or configure it in redocly.yaml. Learn more: https://redocly.com/docs/realm/config/scorecard#fromprojecturl-example'); } if (!isAllowedScorecardProjectUrl(projectUrl)) { exitWithError(`Project URL must be from the .redocly.com domain. Received: ${projectUrl}`); } const apiKey = process.env.REDOCLY_AUTHORIZATION; if (isNonInteractiveEnvironment() && !apiKey) { exitWithError('Please provide an API key using the REDOCLY_AUTHORIZATION environment variable.\n'); } const auth = apiKey || (await handleLoginAndFetchToken(config, argv.verbose)); if (!auth) { exitWithError('Failed to obtain access token or API key.'); } const { scorecard, plugins } = await fetchRemoteScorecardAndPlugins({ projectUrl, auth, isApiKey: !!apiKey, verbose: argv.verbose, }); const { path, alias } = apis[0]; const matchedEntry = getAliasOrPath(config, path); const matchedAlias = matchedEntry.alias || alias; const apiConfigMetadata = matchedAlias ? config.resolvedConfig.apis?.[matchedAlias]?.metadata : undefined; if (argv.verbose && matchedAlias && apiConfigMetadata) { logger.info(`\n✓ Matched API "${cyan(matchedAlias)}" from config\n`); } if (argv.verbose) { logger.info(`Processing API: ${cyan(matchedAlias || 'default')}\n`); logger.info(`Path: ${formatPath(path)}\n`); if (apiConfigMetadata) { logger.info(`Config Metadata: ${JSON.stringify(apiConfigMetadata, null, 2)}\n`); } logger.info(`Project URL: ${projectUrl}\n`); } const externalRefResolver = new BaseResolver(config.resolve); const document = (await externalRefResolver.resolveDocument(null, path, true)); collectSpecData?.(document.parsed); const documentInfo = document.parsed?.info; const builtInMetadata = documentInfo?.['x-metadata'] || {}; const metadata = { title: documentInfo?.title, version: documentInfo?.version, ...builtInMetadata, ...apiConfigMetadata, }; if (argv.verbose) { logger.info(`Combined Metadata for target matching: ${JSON.stringify(metadata, null, 2)}\n`); } const targetLevel = argv['target-level'] || getTarget(scorecard?.targets, metadata)?.minimumLevel; logger.info(gray(`\nRunning scorecard for ${formatPath(path)}...\n`)); const { problems: result, achievedLevel, targetLevelAchieved, } = await validateScorecard({ apiPath: path, document, externalRefResolver, scorecardConfig: scorecard, configPath: config.configPath, pluginsCodeOrPlugins: plugins, targetLevel, metadata, verbose: argv.verbose, }); if (result.length === 0) { logger.output(white(bold(`\n ☑️ Achieved Level: ${cyan(achievedLevel)}\n`))); logger.output(green(`✅ No issues found for ${blue(formatPath(path))}. Your API meets all scorecard requirements.\n`)); return; } if (targetLevel && !targetLevelAchieved) { logger.error(`\n❌ Your API specification does not satisfy the target scorecard level "${targetLevel}".\n`); } if (argv.format === 'json') { printScorecardResultsAsJson(result, achievedLevel, targetLevelAchieved, version); } else if (argv.format === 'checkstyle') { printScorecardResultsAsCheckstyle(path, result, achievedLevel, targetLevelAchieved); } else { printScorecardResults(result, achievedLevel, targetLevelAchieved); } const elapsed = getExecutionTime(startedAt); logger.info(`📊 Scorecard results for ${blue(formatPath(path))} at ${blue(path || 'stdout')} ${green(elapsed)}.\n`); if (targetLevel && !targetLevelAchieved) { throw new AbortFlowError('Target scorecard level not achieved.'); } else if (achievedLevel !== 'Non Conformant') { return; } throw new AbortFlowError('Scorecard validation failed.'); } function isNonInteractiveEnvironment() { if (process.env.CI || !process.stdin.isTTY) { return true; } return false; } //# sourceMappingURL=index.js.map