diff --git a/.changeset/config-problems-output.md b/.changeset/config-problems-output.md new file mode 100644 index 0000000000..9df31c649c --- /dev/null +++ b/.changeset/config-problems-output.md @@ -0,0 +1,10 @@ +--- +'@redocly/cli': patch +'@redocly/openapi-core': patch +--- + +Improved the consistency of how configuration file problems are reported. +Commands with machine-readable output formats, such as `json`, `junit`, `checkstyle`, and `sarif` also report configuration file problems instead of hiding them. +A configuration file that fails to load displays a specific error message instead of a generic one. + +**Note**: Configuration file problems are printed to `stderr` in the `codeframe` format for every command, regardless of the `--format` option. diff --git a/docs/@v2/commands/lint.md b/docs/@v2/commands/lint.md index 3c01fc028a..469b2c34d4 100644 --- a/docs/@v2/commands/lint.md +++ b/docs/@v2/commands/lint.md @@ -493,6 +493,8 @@ The `lint` command also validates the configuration file. You can set severity level by using the `--lint-config` option. This option accepts one of the following values: `warn`,`error`, or `off`. Default value is `warn`. +Configuration file problems are printed to `stderr` in the `codeframe` format. +The `--format` option does not apply to them, so machine-readable output such as `json` stays intact. ```bash redocly lint --lint-config=off diff --git a/packages/cli/src/__tests__/commands/bundle.test.ts b/packages/cli/src/__tests__/commands/bundle.test.ts index 572995adf0..92663e6a41 100644 --- a/packages/cli/src/__tests__/commands/bundle.test.ts +++ b/packages/cli/src/__tests__/commands/bundle.test.ts @@ -10,12 +10,12 @@ import { type MockInstance } from 'vitest'; import { type Arguments } from 'yargs'; import { type BundleArgv, bundleTelemetry, handleBundle } from '../../commands/bundle.js'; +import { loadAndCheckConfig } from '../../commands/check-config.js'; import { dumpBundle, getFallbackApisOrExit, getOutputFileName, handleError, - loadConfigAndHandleErrors, saveBundle, } from '../../utils/miscellaneous.js'; import { commandWrapper } from '../../wrapper.js'; @@ -51,7 +51,8 @@ describe('bundle', () => { }); vi.mock('../../utils/miscellaneous.js'); - vi.mocked(loadConfigAndHandleErrors).mockResolvedValue(configFixture); + vi.mock('../../commands/check-config.js'); + vi.mocked(loadAndCheckConfig).mockResolvedValue(configFixture); vi.mocked(getFallbackApisOrExit).mockImplementation( async (entrypoints) => entrypoints?.map((path: string) => ({ path })) ?? [] ); diff --git a/packages/cli/src/__tests__/commands/lint.test.ts b/packages/cli/src/__tests__/commands/lint.test.ts index a856db0f6e..f301d7059f 100644 --- a/packages/cli/src/__tests__/commands/lint.test.ts +++ b/packages/cli/src/__tests__/commands/lint.test.ts @@ -1,6 +1,5 @@ import { lint, - lintConfig, getTotals, formatProblems, logger, @@ -14,14 +13,14 @@ import { performance } from 'perf_hooks'; import { type MockInstance } from 'vitest'; import { type Arguments } from 'yargs'; -import { handleLint, handleLintConfig, type LintArgv } from '../../commands/lint.js'; +import { loadAndCheckConfig } from '../../commands/check-config.js'; +import { handleLint, type LintArgv } from '../../commands/lint.js'; import type { VerifyConfigOptions } from '../../types.js'; import { getFallbackApisOrExit, getExecutionTime, printUnusedWarnings, handleError, - loadConfigAndHandleErrors, checkIfRulesetExist, } from '../../utils/miscellaneous.js'; import { commandWrapper } from '../../wrapper.js'; @@ -67,7 +66,8 @@ describe('handleLint', () => { }); vi.mock('../../utils/miscellaneous.js'); - vi.mocked(loadConfigAndHandleErrors).mockResolvedValue(configFixture); + vi.mock('../../commands/check-config.js'); + vi.mocked(loadAndCheckConfig).mockResolvedValue(configFixture); vi.mocked(getFallbackApisOrExit).mockImplementation( async (entrypoints) => entrypoints?.map((path: string) => ({ path })) ?? [] ); @@ -85,9 +85,9 @@ describe('handleLint', () => { ); }); - it('should call loadConfigAndHandleErrors and getFallbackApisOrExit', async () => { + it('should call loadAndCheckConfig and getFallbackApisOrExit', async () => { await commandWrapper(handleLint)(argvMock); - expect(loadConfigAndHandleErrors).toHaveBeenCalledWith( + expect(loadAndCheckConfig).toHaveBeenCalledWith( { apis: ['openapi.yaml'], format: 'codeframe', @@ -105,7 +105,7 @@ describe('handleLint', () => { config: 'redocly.yaml', extends: ['some/path'], }); - expect(loadConfigAndHandleErrors).toHaveBeenCalledWith( + expect(loadAndCheckConfig).toHaveBeenCalledWith( { apis: ['openapi.yaml'], config: 'redocly.yaml', @@ -267,7 +267,7 @@ describe('handleLint', () => { }); it('should call exit with 0 if no errors', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return configFixture; }); await commandWrapper(handleLint)(argvMock); @@ -283,7 +283,7 @@ describe('handleLint', () => { }); it('should suggest recommended fallback if there is no config', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return await loadConfig({}); }); await commandWrapper(handleLint)(argvMock); @@ -295,7 +295,7 @@ describe('handleLint', () => { }); it('should not suggest recommended fallback if --extends is provided', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return await loadConfig({}); }); await commandWrapper(handleLint)({ ...argvMock, extends: ['some/path'] }); @@ -307,36 +307,3 @@ describe('handleLint', () => { }); }); }); - -describe('handleLintConfig', () => { - const configWithDocument = { ...configFixture, document: { parsed: {} } } as any; - - it.each(['json', 'junit', 'checkstyle'] as const)( - 'should not print config lint results for the single-document %s format', - async (format) => { - vi.mocked(lintConfig).mockResolvedValue(['config-problem'] as any); - vi.mocked(formatProblems).mockClear(); - - await handleLintConfig( - { ...argvMock, 'lint-config': 'warn', format } as any, - '2.0.0', - configWithDocument - ); - - expect(formatProblems).not.toHaveBeenCalled(); - } - ); - - it('should print config lint results for text formats', async () => { - vi.mocked(lintConfig).mockResolvedValue([] as any); - vi.mocked(formatProblems).mockClear(); - - await handleLintConfig( - { ...argvMock, 'lint-config': 'warn', format: 'stylish' } as any, - '2.0.0', - configWithDocument - ); - - expect(formatProblems).toHaveBeenCalledWith([], expect.objectContaining({ format: 'stylish' })); - }); -}); diff --git a/packages/cli/src/__tests__/utils.test.ts b/packages/cli/src/__tests__/utils.test.ts index 5a46dea054..df29690b1f 100644 --- a/packages/cli/src/__tests__/utils.test.ts +++ b/packages/cli/src/__tests__/utils.test.ts @@ -1,5 +1,4 @@ import { - type Totals, ResolveError, YamlParseError, HandledError, @@ -19,7 +18,6 @@ import { fileURLToPath } from 'node:url'; import { getFallbackApisOrExit, pathToFilename, - printConfigLintTotals, langToExt, checkIfRulesetExist, handleError, @@ -63,43 +61,6 @@ describe('pathToFilename', () => { }); }); -describe('printConfigLintTotals', () => { - const totalProblemsMock: Totals = { - errors: 1, - warnings: 0, - ignored: 0, - }; - - const redColoretteMocks = vi.mocked(red); - const yellowColoretteMocks = vi.mocked(yellow); - - beforeEach(() => { - yellowColoretteMocks.mockImplementation((text) => text as string); - redColoretteMocks.mockImplementation((text) => text as string); - vi.spyOn(process.stderr, 'write').mockImplementation(() => true); - }); - - it('should print errors if such exist', () => { - printConfigLintTotals(totalProblemsMock); - expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.\n'); - expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.\n'); - }); - - it('should print warning if no error', () => { - printConfigLintTotals({ ...totalProblemsMock, errors: 0, warnings: 2 }); - expect(process.stderr.write).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n'); - expect(yellowColoretteMocks).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n'); - }); - - it('should print nothing if no error and no warnings', () => { - const result = printConfigLintTotals({ ...totalProblemsMock, errors: 0 }); - expect(result).toBeUndefined(); - expect(process.stderr.write).toHaveBeenCalledTimes(0); - expect(yellowColoretteMocks).toHaveBeenCalledTimes(0); - expect(redColoretteMocks).toHaveBeenCalledTimes(0); - }); -}); - describe('getFallbackApisOrExit', async () => { const redColoretteMocks = vi.mocked(red); const yellowColoretteMocks = vi.mocked(yellow); diff --git a/packages/cli/src/__tests__/wrapper.test.ts b/packages/cli/src/__tests__/wrapper.test.ts index 6e9cc21537..4609c86ab4 100644 --- a/packages/cli/src/__tests__/wrapper.test.ts +++ b/packages/cli/src/__tests__/wrapper.test.ts @@ -6,8 +6,8 @@ import { } from '@redocly/openapi-core'; import * as process from 'node:process'; +import { loadAndCheckConfig } from '../commands/check-config.js'; import { handleLint } from '../commands/lint.js'; -import { loadConfigAndHandleErrors } from '../utils/miscellaneous.js'; import { sendTelemetry } from '../utils/telemetry.js'; import { commandWrapper } from '../wrapper.js'; @@ -26,6 +26,7 @@ describe('commandWrapper', () => { vi.mock('../utils/miscellaneous.js'); vi.mock('../utils/telemetry.js'); vi.mock('../commands/lint.js'); + vi.mock('../commands/check-config.js'); }); afterEach(() => { global.fetch = originalFetch; @@ -33,7 +34,7 @@ describe('commandWrapper', () => { }); it('should send telemetry if there is "telemetry: on" in the config', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'on' } } as Config; }); vi.mocked(detectSpec).mockImplementationOnce(() => { @@ -69,7 +70,7 @@ describe('commandWrapper', () => { }); it('should not collect spec version if the file is not parsed to json (except for graphql)', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'on' } } as Config; }); vi.mocked(handleLint).mockImplementation(async ({ collectSpecData }) => { @@ -102,7 +103,7 @@ describe('commandWrapper', () => { }); it('should collect the spec version of a GraphQL document', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'on' } } as Config; }); vi.mocked(handleLint).mockImplementation(async ({ collectSpecData }) => { @@ -122,7 +123,7 @@ describe('commandWrapper', () => { }); it('should not keep the spec keyword of a previously linted document', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'on' } } as Config; }); vi.mocked(detectSpec).mockImplementationOnce(() => { @@ -146,7 +147,7 @@ describe('commandWrapper', () => { }); it('should collect the names of lint rules that reported problems', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'on' } } as Config; }); vi.mocked(handleLint).mockImplementation(async ({ collectResults: collectLintResults }) => { @@ -170,7 +171,7 @@ describe('commandWrapper', () => { }); it('should NOT send telemetry if there is "telemetry: off" in the config', async () => { - vi.mocked(loadConfigAndHandleErrors).mockImplementation(async () => { + vi.mocked(loadAndCheckConfig).mockImplementation(async () => { return { resolvedConfig: { telemetry: 'off' } } as Config; }); process.env.REDOCLY_TELEMETRY = 'on'; diff --git a/packages/cli/src/commands/__tests__/check-config.test.ts b/packages/cli/src/commands/__tests__/check-config.test.ts new file mode 100644 index 0000000000..fef17eb56e --- /dev/null +++ b/packages/cli/src/commands/__tests__/check-config.test.ts @@ -0,0 +1,79 @@ +import { + formatProblems, + lintConfig, + loadConfig, + type Config, + type Exact, + type Totals, +} from '@redocly/openapi-core'; +import { red, yellow } from 'colorette'; + +import type { CommandArgv } from '../../types.js'; +import { loadAndCheckConfig, printConfigLintTotals } from '../check-config.js'; + +vi.mock('colorette'); +vi.mock('@redocly/openapi-core', async () => { + const actual = await vi.importActual('@redocly/openapi-core'); + return { ...actual, loadConfig: vi.fn(), lintConfig: vi.fn(), formatProblems: vi.fn() }; +}); + +beforeEach(() => { + vi.spyOn(process.stderr, 'write').mockImplementation(() => true); +}); + +describe('loadAndCheckConfig', () => { + it('should print config problems in the check-config format regardless of --format', async () => { + const config = { document: { parsed: {} } } as Config; + vi.mocked(loadConfig).mockResolvedValue(config); + vi.mocked(lintConfig).mockResolvedValue([]); + + const result = await loadAndCheckConfig( + { 'lint-config': 'warn', format: 'json' } as Exact, + '2.0.0' + ); + + expect(result).toBe(config); + expect(formatProblems).toHaveBeenCalledWith([], { + maxProblems: undefined, + totals: { errors: 0, warnings: 0, ignored: 0 }, + version: '2.0.0', + command: 'check-config', + }); + }); +}); + +describe('printConfigLintTotals', () => { + const totalProblemsMock: Totals = { + errors: 1, + warnings: 0, + ignored: 0, + }; + + const redColoretteMocks = vi.mocked(red); + const yellowColoretteMocks = vi.mocked(yellow); + + beforeEach(() => { + yellowColoretteMocks.mockImplementation((text) => text as string); + redColoretteMocks.mockImplementation((text) => text as string); + }); + + it('should print errors if such exist', () => { + printConfigLintTotals(totalProblemsMock); + expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.\n'); + expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.\n'); + }); + + it('should print warning if no error', () => { + printConfigLintTotals({ ...totalProblemsMock, errors: 0, warnings: 2 }); + expect(process.stderr.write).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n'); + expect(yellowColoretteMocks).toHaveBeenCalledWith('⚠️ Your config has 2 warnings.\n'); + }); + + it('should print nothing if no error and no warnings', () => { + const result = printConfigLintTotals({ ...totalProblemsMock, errors: 0 }); + expect(result).toBeUndefined(); + expect(process.stderr.write).toHaveBeenCalledTimes(0); + expect(yellowColoretteMocks).toHaveBeenCalledTimes(0); + expect(redColoretteMocks).toHaveBeenCalledTimes(0); + }); +}); diff --git a/packages/cli/src/commands/check-config.ts b/packages/cli/src/commands/check-config.ts new file mode 100644 index 0000000000..35e2892850 --- /dev/null +++ b/packages/cli/src/commands/check-config.ts @@ -0,0 +1,75 @@ +import { + AbortFlowError, + formatProblems, + getTotals, + HandledError, + lintConfig, + loadConfig, + logger, + pluralize, + type Config, + type Exact, + type Plugin, +} from '@redocly/openapi-core'; +import { green } from 'colorette'; +import type { Arguments } from 'yargs'; + +import type { CommandArgv, Totals } from '../types.js'; +import { getCommandNameFromArgs } from '../utils/get-command-name-from-args.js'; + +// Every command loads and checks the config before its handler, so the `check-config` command itself has no handler. +export async function loadAndCheckConfig( + argv: Exact, + version: string, + plugins?: Plugin[] +): Promise { + let config: Config; + try { + config = await loadConfig({ + configPath: argv.config, + customExtends: argv.extends, + plugins, + }); + } catch (error) { + throw new HandledError(`Failed to load the configuration file:\n\n - ${error.message}`); + } + + if (argv['lint-config'] === 'off' || config.document === undefined) { + return config; + } + + const problems = await lintConfig({ + config, + severity: argv['lint-config'] || 'warn', + }); + + const totals = getTotals(problems); + + // Config problems go to stderr in one fixed format, so the `--format` output of the command stays intact. + formatProblems(problems, { + maxProblems: argv['max-problems'], + totals, + version, + command: 'check-config', + }); + + printConfigLintTotals(totals, getCommandNameFromArgs(argv as Arguments)); + + if (totals.errors > 0) { + throw new AbortFlowError('Config check failed.'); + } + + return config; +} + +export function printConfigLintTotals(totals: Totals, command?: string | number): void { + if (totals.errors > 0) { + logger.error(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.\n`); + } else if (totals.warnings > 0) { + logger.warn( + `⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n` + ); + } else if (command === 'check-config') { + logger.info(green('✅ Your config is valid.\n')); + } +} diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 89d1b275e1..f9e3cc0cb4 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -2,28 +2,21 @@ import { formatProblems, getTotals, lint, - lintConfig, pluralize, - ConfigValidationError, logger, - type Config, - type Exact, type OutputFormat, AbortFlowError, } from '@redocly/openapi-core'; import { blue, gray } from 'colorette'; import { performance } from 'perf_hooks'; -import type { Arguments } from 'yargs'; -import type { CommandArgv, Totals } from '../types.js'; -import { getCommandNameFromArgs } from '../utils/get-command-name-from-args.js'; +import type { Totals } from '../types.js'; import { checkIfRulesetExist, formatPath, getExecutionTime, getFallbackApisOrExit, handleError, - printConfigLintTotals, printLintTotals, printUnusedWarnings, } from '../utils/miscellaneous.js'; @@ -140,43 +133,3 @@ export async function handleLint({ throw new AbortFlowError('Lint failed.'); } } - -export async function handleLintConfig(argv: Exact, version: string, config: Config) { - if (argv['lint-config'] === 'off' || config.document === undefined) { - return; - } - - if ( - argv.format === 'json' || - argv.format === 'junit' || - argv.format === 'checkstyle' || - argv.format === 'sarif' - ) { - // these are single-document formats, so a separate config-lint document would break the output - return; - } - - const command = argv ? getCommandNameFromArgs(argv as Arguments) : undefined; - - const problems = await lintConfig({ - config, - severity: argv['lint-config'] || 'warn', - }); - - const fileTotals = getTotals(problems); - - formatProblems(problems, { - // These are config problems, and config output has no `table` format (the `recheck` default). - format: argv.format === 'table' ? undefined : argv.format, - maxProblems: argv['max-problems'], - totals: fileTotals, - version, - command: 'check-config', - }); - - printConfigLintTotals(fileTotals, command); - - if (fileTotals.errors > 0) { - throw new ConfigValidationError(); - } -} diff --git a/packages/cli/src/utils/miscellaneous.ts b/packages/cli/src/utils/miscellaneous.ts index f8fac06622..8c04459822 100644 --- a/packages/cli/src/utils/miscellaneous.ts +++ b/packages/cli/src/utils/miscellaneous.ts @@ -4,21 +4,17 @@ import { parseYaml, stringifyYaml, isAbsoluteUrl, - loadConfig, isEmptyObject, isNotEmptyArray, isNotEmptyObject, pluralize, - ConfigValidationError, logger, HandledError, type Config, type Oas3Definition, type Oas2Definition, - type Exact, type Async3Definition, type Async2Definition, - type Plugin, } from '@redocly/openapi-core'; import { blue, gray, green, red, yellow } from 'colorette'; import * as fs from 'node:fs'; @@ -28,14 +24,7 @@ import * as readline from 'node:readline'; import { Writable } from 'node:stream'; import { performance } from 'perf_hooks'; -import { handleLintConfig } from '../commands/lint.js'; -import { - outputExtensions, - type Totals, - type Entrypoint, - type OutputExtension, - type CommandArgv, -} from '../types.js'; +import { outputExtensions, type Totals, type Entrypoint, type OutputExtension } from '../types.js'; export type ExitCode = 0 | 1 | 2; @@ -310,8 +299,6 @@ export function handleError(e: Error, ref: string): never { } case SyntaxError: throw new HandledError(`Syntax error: ${e.message} ${e.stack?.split('\n\n')?.[0]}`); - case ConfigValidationError: - throw new HandledError(e.message); default: { throw new HandledError(`Something went wrong when processing ${ref}:\n\n - ${e.message}`); } @@ -355,18 +342,6 @@ export function printLintTotals(totals: Totals, definitionsCount: number) { logger.info('\n'); } -export function printConfigLintTotals(totals: Totals, command?: string | number): void { - if (totals.errors > 0) { - logger.error(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.\n`); - } else if (totals.warnings > 0) { - logger.warn( - `⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n` - ); - } else if (command === 'check-config') { - logger.info(green('✅ Your config is valid.\n')); - } -} - export function getOutputFileName({ entrypoint, output, @@ -431,24 +406,6 @@ export function printUnusedWarnings(config: Config) { } } -export async function loadConfigAndHandleErrors( - argv: Exact, - version: string, - plugins?: Plugin[] -): Promise { - try { - const config = await loadConfig({ - configPath: argv.config, - customExtends: argv.extends as string[] | undefined, - plugins, - }); - await handleLintConfig(argv, version, config); - return config; - } catch (e) { - handleError(e, ''); - } -} - function isAsync2Definition(doc: Async2Definition | Async3Definition): doc is Async2Definition { return doc.asyncapi?.startsWith('2'); } diff --git a/packages/cli/src/wrapper.ts b/packages/cli/src/wrapper.ts index 73fd60f794..675cf49de1 100644 --- a/packages/cli/src/wrapper.ts +++ b/packages/cli/src/wrapper.ts @@ -17,12 +17,13 @@ import { import type { Arguments } from 'yargs'; import { bundleTelemetry } from './commands/bundle.js'; +import { loadAndCheckConfig } from './commands/check-config.js'; import type { CommandArgv, VerifyConfigOptions } from './types.js'; import { ejectGeneratorTelemetry, generateClientTelemetry, } from './utils/client-generator-telemetry.js'; -import { loadConfigAndHandleErrors, type ExitCode } from './utils/miscellaneous.js'; +import type { ExitCode } from './utils/miscellaneous.js'; import { version } from './utils/package.js'; import { sendTelemetry, @@ -113,7 +114,7 @@ export function commandWrapper( if (argv.config && !doesYamlFileExist(argv.config)) { throw new HandledError('Please provide a valid path to the configuration file.'); } - config = await loadConfigAndHandleErrors(argv as Exact, version, options.plugins); + config = await loadAndCheckConfig(argv as Exact, version, options.plugins); telemetry = config.resolvedConfig.telemetry; code = 1; if (typeof commandHandler === 'function') { diff --git a/packages/core/src/format/format.ts b/packages/core/src/format/format.ts index cf356f0e08..708a6bdfe5 100644 --- a/packages/core/src/format/format.ts +++ b/packages/core/src/format/format.ts @@ -121,7 +121,7 @@ export function formatProblems( case 'codeframe': for (let i = 0; i < problems.length; i++) { const problem = problems[i]; - if (command === 'bundle') { + if (command === 'bundle' || command === 'check-config') { logger.info(`${formatCodeframe(problem, i)}\n`); } else { logger.output(`${formatCodeframe(problem, i)}\n`); diff --git a/tests/e2e/check-config/empty-config-null/snapshot.txt b/tests/e2e/check-config/empty-config-null/snapshot.txt index 9f244ea32f..b6e0a02ab5 100644 --- a/tests/e2e/check-config/empty-config-null/snapshot.txt +++ b/tests/e2e/check-config/empty-config-null/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:1:1 at #/ Expected type `ConfigRoot` (object) but got `null` @@ -9,7 +10,4 @@ Expected type `ConfigRoot` (object) but got `null` Error was generated by the configuration struct rule. - ❌ Your config has 1 error. - - diff --git a/tests/e2e/check-config/invalid-config--lint-config-error/snapshot.txt b/tests/e2e/check-config/invalid-config--lint-config-error/snapshot.txt index 37a243cc3f..2d2d39e3a6 100644 --- a/tests/e2e/check-config/invalid-config--lint-config-error/snapshot.txt +++ b/tests/e2e/check-config/invalid-config--lint-config-error/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:6:3 at #/rules/context Property `context` is not expected here. @@ -11,7 +12,4 @@ Property `context` is not expected here. Error was generated by the configuration struct rule. - ❌ Your config has 1 error. - - diff --git a/tests/e2e/check-config/invalid-config--lint-config-warn/snapshot.txt b/tests/e2e/check-config/invalid-config--lint-config-warn/snapshot.txt index 3cd9d2e9b0..cf9bf84e5b 100644 --- a/tests/e2e/check-config/invalid-config--lint-config-warn/snapshot.txt +++ b/tests/e2e/check-config/invalid-config--lint-config-warn/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:6:3 at #/rules/context Property `context` is not expected here. @@ -11,5 +12,4 @@ Property `context` is not expected here. Warning was generated by the configuration struct rule. - ⚠️ Your config has 1 warning. diff --git a/tests/e2e/check-config/invalid-config--no-option/snapshot.txt b/tests/e2e/check-config/invalid-config--no-option/snapshot.txt index 37a243cc3f..2d2d39e3a6 100644 --- a/tests/e2e/check-config/invalid-config--no-option/snapshot.txt +++ b/tests/e2e/check-config/invalid-config--no-option/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:6:3 at #/rules/context Property `context` is not expected here. @@ -11,7 +12,4 @@ Property `context` is not expected here. Error was generated by the configuration struct rule. - ❌ Your config has 1 error. - - diff --git a/tests/e2e/check-config/wrong-config-type-extensions-in-assertions/snapshot.txt b/tests/e2e/check-config/wrong-config-type-extensions-in-assertions/snapshot.txt index 64b90ab710..8165e16c73 100644 --- a/tests/e2e/check-config/wrong-config-type-extensions-in-assertions/snapshot.txt +++ b/tests/e2e/check-config/wrong-config-type-extensions-in-assertions/snapshot.txt @@ -1,3 +1,5 @@ + +Deprecated plugin format detected: type-extension [1] redocly.yaml:10:13 at #/rules/rule~1metadata-lifecycle/subject/type `type` "WrongXMetaData" is not a valid value. See the supported values: https://redocly.com/docs/cli/rules/configurable-rules#subject-object. @@ -12,8 +14,4 @@ Error was generated by the configuration struct rule. - -Deprecated plugin format detected: type-extension ❌ Your config has 1 error. - - diff --git a/tests/e2e/lint-config/config-structure/snapshot.txt b/tests/e2e/lint-config/config-structure/snapshot.txt index c4d311e838..acdd97b57e 100644 --- a/tests/e2e/lint-config/config-structure/snapshot.txt +++ b/tests/e2e/lint-config/config-structure/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:1:13 at #/licenseKey Expected type `string` but got `integer`. @@ -642,7 +643,4 @@ referenced from redocly.yaml:142:5 at #/env/referenced-env Error was generated by the configuration struct rule. - ❌ Your config has 45 errors. - - diff --git a/tests/e2e/lint-config/config-with-refs-extended/snapshot.txt b/tests/e2e/lint-config/config-with-refs-extended/snapshot.txt index 1f06046e27..a096486c6b 100644 --- a/tests/e2e/lint-config/config-with-refs-extended/snapshot.txt +++ b/tests/e2e/lint-config/config-with-refs-extended/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:1:1 at #/unexpected-property Property `unexpected-property` is not expected here. @@ -77,7 +78,4 @@ Expected type `string` but got `integer`. Error was generated by the configuration struct rule. - ❌ Your config has 6 errors. - - diff --git a/tests/e2e/lint-config/config-with-refs/snapshot.txt b/tests/e2e/lint-config/config-with-refs/snapshot.txt index 91ed8e354b..7d00be9984 100644 --- a/tests/e2e/lint-config/config-with-refs/snapshot.txt +++ b/tests/e2e/lint-config/config-with-refs/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:7:1 at #/non-existing-root-property Property `non-existing-root-property` is not expected here. @@ -39,7 +40,6 @@ Expected type `string` but got `integer`. Warning was generated by the configuration struct rule. - ⚠️ Your config has 3 warnings. ⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/ diff --git a/tests/e2e/lint-config/invalid-config--lint-config-error/snapshot.txt b/tests/e2e/lint-config/invalid-config--lint-config-error/snapshot.txt index 46a99ce876..5eca34e3b4 100644 --- a/tests/e2e/lint-config/invalid-config--lint-config-error/snapshot.txt +++ b/tests/e2e/lint-config/invalid-config--lint-config-error/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:2:3 at #/rules/context Property `context` is not expected here. @@ -10,7 +11,4 @@ Property `context` is not expected here. Error was generated by the configuration struct rule. - ❌ Your config has 1 error. - - diff --git a/tests/e2e/lint-config/invalid-config--lint-config-warn/snapshot.txt b/tests/e2e/lint-config/invalid-config--lint-config-warn/snapshot.txt index c916f70c61..366e7a8ddc 100644 --- a/tests/e2e/lint-config/invalid-config--lint-config-warn/snapshot.txt +++ b/tests/e2e/lint-config/invalid-config--lint-config-warn/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:2:3 at #/rules/context Property `context` is not expected here. @@ -10,7 +11,6 @@ Property `context` is not expected here. Warning was generated by the configuration struct rule. - ⚠️ Your config has 1 warning. validating ../__fixtures__/valid-openapi.yaml... ../__fixtures__/valid-openapi.yaml: validated in ms diff --git a/tests/e2e/lint-config/invalid-config--no-option/snapshot.txt b/tests/e2e/lint-config/invalid-config--no-option/snapshot.txt index 6fbc475ef1..74e7266e45 100644 --- a/tests/e2e/lint-config/invalid-config--no-option/snapshot.txt +++ b/tests/e2e/lint-config/invalid-config--no-option/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:6:3 at #/rules/context Property `context` is not expected here. @@ -11,7 +12,6 @@ Property `context` is not expected here. Warning was generated by the configuration struct rule. - ⚠️ Your config has 1 warning. validating ../__fixtures__/valid-openapi.yaml... ../__fixtures__/valid-openapi.yaml: validated in ms diff --git a/tests/e2e/lint-config/invalid-config-assertation-config-type/snapshot.txt b/tests/e2e/lint-config/invalid-config-assertation-config-type/snapshot.txt index ae90149ae1..2de08d9eb5 100644 --- a/tests/e2e/lint-config/invalid-config-assertation-config-type/snapshot.txt +++ b/tests/e2e/lint-config/invalid-config-assertation-config-type/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:5:17 at #/rules/rule~1path-item-mutually-required/where/0/subject/type `type` "Invalid-type" is not a valid value. See the supported values: https://redocly.com/docs/cli/rules/configurable-rules#subject-object. @@ -12,7 +13,6 @@ Warning was generated by the configuration struct rule. - ⚠️ Your config has 1 warning. validating ../__fixtures__/valid-openapi.yaml... ../__fixtures__/valid-openapi.yaml: validated in ms diff --git a/tests/e2e/lint-config/invalid-config-format-json/snapshot.txt b/tests/e2e/lint-config/invalid-config-format-json/snapshot.txt index 5a91baf053..17b4d92e7f 100644 --- a/tests/e2e/lint-config/invalid-config-format-json/snapshot.txt +++ b/tests/e2e/lint-config/invalid-config-format-json/snapshot.txt @@ -7,6 +7,21 @@ "version": "", "problems": [] } +[1] redocly.yaml:1:1 at #/badKey + +Property `badKey` is not expected here. + +Did you mean: banner ? + +1 | badKey: 1 + | ^^^^^^ +2 | +3 | extends: + +Warning was generated by the configuration struct rule. + + +⚠️ Your config has 1 warning. validating ../__fixtures__/valid-openapi.yaml... ../__fixtures__/valid-openapi.yaml: validated in ms diff --git a/tests/e2e/lint-config/invalid-custom-rule-config/snapshot.txt b/tests/e2e/lint-config/invalid-custom-rule-config/snapshot.txt index 00c698989c..276a24e8d3 100644 --- a/tests/e2e/lint-config/invalid-custom-rule-config/snapshot.txt +++ b/tests/e2e/lint-config/invalid-custom-rule-config/snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:2:3 at #/rules/my-plugin~1lacks-severity The field `severity` must be present on this level. @@ -11,7 +12,6 @@ The field `severity` must be present on this level. Warning was generated by the configuration struct rule. - ⚠️ Your config has 1 warning. validating ../__fixtures__/valid-openapi.yaml... ../__fixtures__/valid-openapi.yaml: validated in ms diff --git a/tests/e2e/lint-config/invalid-definition-and-config/config-with-error.snapshot.txt b/tests/e2e/lint-config/invalid-definition-and-config/config-with-error.snapshot.txt index 55fffaed1e..34b666f544 100644 --- a/tests/e2e/lint-config/invalid-definition-and-config/config-with-error.snapshot.txt +++ b/tests/e2e/lint-config/invalid-definition-and-config/config-with-error.snapshot.txt @@ -1,3 +1,4 @@ + [1] redocly.yaml:2:3 at #/rules/context Property `context` is not expected here. @@ -11,7 +12,4 @@ Property `context` is not expected here. Error was generated by the configuration struct rule. - ❌ Your config has 1 error. - - diff --git a/tests/e2e/lint-config/invalid-definition-and-config/config-with-warn.snapshot.txt b/tests/e2e/lint-config/invalid-definition-and-config/config-with-warn.snapshot.txt index 4915c8280b..b423dfce7f 100644 --- a/tests/e2e/lint-config/invalid-definition-and-config/config-with-warn.snapshot.txt +++ b/tests/e2e/lint-config/invalid-definition-and-config/config-with-warn.snapshot.txt @@ -1,16 +1,3 @@ -[1] redocly.yaml:2:3 at #/rules/context - -Property `context` is not expected here. - -1 | rules: -2 | context: - | ^^^^^^^^ -3 | -4 | extends: - -Warning was generated by the configuration struct rule. - - [1] ../__fixtures__/invalid-openapi.yaml:4:1 at #/info The field `title` must be present on this level. @@ -87,6 +74,19 @@ Reference: https://redocly.com/docs/cli/rules/oas/operation-operationId +[1] redocly.yaml:2:3 at #/rules/context + +Property `context` is not expected here. + +1 | rules: +2 | context: + | ^^^^^^^^ +3 | +4 | extends: + +Warning was generated by the configuration struct rule. + + ⚠️ Your config has 1 warning. validating ../__fixtures__/invalid-openapi.yaml... ../__fixtures__/invalid-openapi.yaml: validated in ms