diff --git a/.changeset/cuddly-glasses-add.md b/.changeset/cuddly-glasses-add.md new file mode 100644 index 000000000..de3de40e4 --- /dev/null +++ b/.changeset/cuddly-glasses-add.md @@ -0,0 +1,5 @@ +--- +"@redocly/cli": patch +--- + +Fixed an issue where `apis`' root in `redocly.yaml` was not resolved properly when the value of `root` was a URL. diff --git a/__tests__/commands.test.ts b/__tests__/commands.test.ts index 7ad263ce0..739496a24 100644 --- a/__tests__/commands.test.ts +++ b/__tests__/commands.test.ts @@ -627,7 +627,17 @@ describe('E2E', () => { const testPath = join(folderPath, 'apply-per-api-decorators'); const args = getParams('../../../packages/cli/src/index.ts', 'bundle', [ '--config=nested/redocly.yaml', - 'test@v1', + 'test@fs', + ]); + const result = getCommandOutput(args, testPath); + (expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js')); + }); + + test('lint a specific api (when the api is specified as an alias and it points to an external URL)', () => { + const testPath = join(folderPath, 'apply-per-api-decorators'); + const args = getParams('../../../packages/cli/src/index.ts', 'lint', [ + '--config=nested/redocly.yaml', + 'test@external-url', ]); const result = getCommandOutput(args, testPath); (expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js')); diff --git a/__tests__/miscellaneous/apply-per-api-decorators/nested/redocly.yaml b/__tests__/miscellaneous/apply-per-api-decorators/nested/redocly.yaml index 0e86ab03d..16ef57893 100644 --- a/__tests__/miscellaneous/apply-per-api-decorators/nested/redocly.yaml +++ b/__tests__/miscellaneous/apply-per-api-decorators/nested/redocly.yaml @@ -1,8 +1,12 @@ apis: - test@v1: + test@fs: root: openapi/main.yaml decorators: test/version: on remove-unused-components: on + test@external-url: + root: https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml + rules: + info-contact: error plugins: - plugins/test.js diff --git a/__tests__/miscellaneous/apply-per-api-decorators/snapshot.js b/__tests__/miscellaneous/apply-per-api-decorators/snapshot.js index 8394a6189..d89fa83f4 100644 --- a/__tests__/miscellaneous/apply-per-api-decorators/snapshot.js +++ b/__tests__/miscellaneous/apply-per-api-decorators/snapshot.js @@ -22,6 +22,14 @@ info: title: Test version: 1.0.0 paths: {} +components: + schemas: + Unused: + type: string +openapi: 3.1.0 +info: + title: Test +paths: {} components: schemas: Unused: @@ -29,5 +37,31 @@ components: bundling nested/openapi/main.yaml... 📦 Created a bundle for nested/openapi/main.yaml at stdout ms. +bundling https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml... +📦 Created a bundle for https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml at stdout ms. + +`; + +exports[`E2E miscellaneous lint a specific api (when the api is specified as an alias and it points to an external URL) 1`] = ` + +validating https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml... +[1] https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml:2:1 at #/info/contact + +Info object should contain \`contact\` field. + +1 | openapi: 3.1.0 +2 | info: + | ^^^^ +3 | title: Test +4 | paths: {} + +Error was generated by the info-contact rule. + + +https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/__tests__/miscellaneous/apply-per-api-decorators/nested/openapi/main.yaml: validated in ms + +❌ Validation failed with 1 error. +run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file. + `; diff --git a/packages/cli/src/commands/bundle.ts b/packages/cli/src/commands/bundle.ts index 2432429ac..30f6c522e 100644 --- a/packages/cli/src/commands/bundle.ts +++ b/packages/cli/src/commands/bundle.ts @@ -1,4 +1,3 @@ -import { relative } from 'path'; import { performance } from 'perf_hooks'; import { blue, gray, green, yellow } from 'colorette'; import { writeFileSync } from 'fs'; @@ -13,6 +12,7 @@ import { saveBundle, sortTopLevelKeysForOas, checkForDeprecatedOptions, + formatPath, } from '../utils/miscellaneous'; import type { OutputExtensions, Skips, Totals, VerifyConfigOptions } from '../types'; @@ -55,7 +55,7 @@ export async function handleBundle({ styleguide.skipPreprocessors(argv['skip-preprocessor']); styleguide.skipDecorators(argv['skip-decorator']); - process.stderr.write(gray(`bundling ${relative(process.cwd(), path)}...\n`)); + process.stderr.write(gray(`bundling ${formatPath(path)}...\n`)); const { bundle: result, @@ -118,20 +118,20 @@ export async function handleBundle({ if (fileTotals.errors > 0) { if (argv.force) { process.stderr.write( - `❓ Created a bundle for ${blue(relative(process.cwd(), path))} at ${blue( + `❓ Created a bundle for ${blue(formatPath(path))} at ${blue( outputFile || 'stdout' )} with errors ${green(elapsed)}.\n${yellow('Errors ignored because of --force')}.\n` ); } else { process.stderr.write( `❌ Errors encountered while bundling ${blue( - relative(process.cwd(), path) + formatPath(path) )}: bundle not created (use --force to ignore errors).\n` ); } } else { process.stderr.write( - `📦 Created a bundle for ${blue(relative(process.cwd(), path))} at ${blue( + `📦 Created a bundle for ${blue(formatPath(path))} at ${blue( outputFile || 'stdout' )} ${green(elapsed)}.\n` ); diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index fa33428a1..919db544f 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -1,4 +1,3 @@ -import { relative } from 'path'; import { blue, gray } from 'colorette'; import { performance } from 'perf_hooks'; import { @@ -13,6 +12,7 @@ import { pluralize } from '@redocly/openapi-core/lib/utils'; import { checkIfRulesetExist, exitWithError, + formatPath, getExecutionTime, getFallbackApisOrExit, handleError, @@ -75,7 +75,7 @@ export async function handleLint({ )} configuration by default.\n\n` ); } - process.stderr.write(gray(`validating ${relative(process.cwd(), path)}...\n`)); + process.stderr.write(gray(`validating ${formatPath(path)}...\n`)); const results = await lint({ ref: path, config: resolvedConfig, @@ -102,7 +102,7 @@ export async function handleLint({ } const elapsed = getExecutionTime(startedAt); - process.stderr.write(gray(`${relative(process.cwd(), path)}: validated in ${elapsed}\n\n`)); + process.stderr.write(gray(`${formatPath(path)}: validated in ${elapsed}\n\n`)); } catch (e) { handleError(e, path); } diff --git a/packages/cli/src/utils/__mocks__/miscellaneous.ts b/packages/cli/src/utils/__mocks__/miscellaneous.ts index 4325204e1..d6c5a70d9 100644 --- a/packages/cli/src/utils/__mocks__/miscellaneous.ts +++ b/packages/cli/src/utils/__mocks__/miscellaneous.ts @@ -21,3 +21,4 @@ export const getAndValidateFileExtension = jest.fn((fileName: string) => fileNam export const writeToFileByExtension = jest.fn(); export const checkForDeprecatedOptions = jest.fn(); export const saveBundle = jest.fn(); +export const formatPath = jest.fn((path: string) => path); diff --git a/packages/cli/src/utils/miscellaneous.ts b/packages/cli/src/utils/miscellaneous.ts index 50ea19f0b..a551a55ca 100644 --- a/packages/cli/src/utils/miscellaneous.ts +++ b/packages/cli/src/utils/miscellaneous.ts @@ -57,7 +57,7 @@ export async function getFallbackApisOrExit( if (isNotEmptyArray(filteredInvalidEntrypoints)) { for (const { path } of filteredInvalidEntrypoints) { process.stderr.write( - yellow(`\n${relative(process.cwd(), path)} ${red(`does not exist or is invalid.\n\n`)}`) + yellow(`\n${formatPath(path)} ${red(`does not exist or is invalid.\n\n`)}`) ); } exitWithError('Please provide a valid path.'); @@ -92,7 +92,7 @@ function getAliasOrPath(config: ConfigApis, aliasOrPath: string): Entrypoint { const aliasApi = config.apis[aliasOrPath]; return aliasApi ? { - path: isAbsolute(aliasApi.root) + path: isAbsoluteUrl(aliasApi.root) ? aliasApi.root : resolve(getConfigDirectory(config), aliasApi.root), alias: aliasOrPath, @@ -702,3 +702,10 @@ export function notifyAboutIncompatibleConfigOptions( } } } + +export function formatPath(path: string) { + if (isAbsoluteUrl(path)) { + return path; + } + return relative(process.cwd(), path); +}