Repository: Redocly/redocly-cli
Pull request #1552 (2024-05-09 10:04:19)

fix: prevent loadConfig from failing in browser env and do not sent auth requests

## What/Why/How?

This PR addresses two issues:
- prevents the `loadConfig` function from failing in browser environment
- removes auth requests to the registry api when loading a config file (which cause perf issues)


## Reference

Partially related to: 
- https://github.com/Redocly/redocly/pull/7883
- https://github.com/Redocly/redocly/issues/8381

## Testing

## Screenshots (optional)

## Check yourself

- [x] Code changed? - Tested with redoc/reference-docs/workflows (internal)
- [x] All new/updated code is covered with tests
- [ ] New package installed? - Tested in different environments (browser/node)

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 7d19f450652e914049f089c5d4c3001ca8b680c3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/Redocly/redocly-cli/new/fix/prevent-failing-in-browser?filename=.changeset/twelve-radios-juggle.md&value=---%0A%22%40redocly%2Fopenapi-core%22%3A%20patch%0A---%0A%0Afix%3A%20prevent%20loadConfig%20from%20failing%20in%20browser%20env%20and%20do%20not%20sent%20auth%20requests%0A)


github-actions[bot]: | Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `redocly lint packages/core/src/benchmark/benches/rebilly.yaml` | 957.8 ± 14.4 | 939.7 | 986.3 | 1.00 |
| `redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml` | 961.5 ± 17.0 | 928.2 | 987.4 | 1.00 ± 0.02 |

<!-- thollander/actions-comment-pull-request "latest-vs-next-comparison" -->
github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 



| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 77.03%     |    4450/5777    |
|                                                                           🟡                                                                           | Branches   | 67.52%     |    2463/3648    |
|                                                                           🟡                                                                           | Functions  | 70.64%     |     741/1049    |
|                                                                           🟡                                                                           | Lines      | 77.23%     |    4185/5419    |


<details><summary>Show files with reduced coverage 🔻</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                          | Statements                                                     | Branches                                                       | Functions                                                   | Lines                                                          |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------ | :------------------------------------------------------------- | :------------------------------------------------------------- | :---------------------------------------------------------- | :------------------------------------------------------------- |
|                                                                           🟡                                                                           | <div title="core/src/redocly/index.ts">`...` / index.ts</div> | <div title="Base coverage is: 66.67%">65.28% (-1.39% 🔻)</div> | <div title="Base coverage is: 71.43%">68.57% (-2.86% 🔻)</div> | <div title="Base coverage is: 75%">70.83% (-4.17% 🔻)</div> | <div title="Base coverage is: 66.67%">65.28% (-1.39% 🔻)</div> |
|                                                                           🟢                                                                           | <div title="core/src/config/load.ts">`...` / load.ts</div>    | 88.89%                                                         | 80%                                                            | 100%                                                        | 90.2%                                                          |

</details>

## Test suite run success
734 tests passing in 102 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from 7d19f450652e914049f089c5d4c3001ca8b680c3</p>


Patch:
diff --git a/.changeset/young-apes-sell.md b/.changeset/young-apes-sell.md
new file mode 100644
index 00000000..85504426
--- /dev/null
+++ b/.changeset/young-apes-sell.md
@@ -0,0 +1,5 @@
+---
+"@redocly/openapi-core": patch
+---
+
+Improved loading of configuration files in environments different from Node.js.
diff --git a/packages/core/src/config/load.ts b/packages/core/src/config/load.ts
index 2223ab40..d2e521c0 100644
--- a/packages/core/src/config/load.ts
+++ b/packages/core/src/config/load.ts
@@ -1,7 +1,7 @@
 import * as fs from 'fs';
 import * as path from 'path';
 import { RedoclyClient } from '../redocly';
-import { isEmptyObject, doesYamlFileExist } from '../utils';
+import { isEmptyObject } from '../utils';
 import { parseYaml } from '../js-yaml';
 import { Config, DOMAINS } from './config';
 import { ConfigValidationError, transformConfig } from './utils';
@@ -9,7 +9,7 @@ import { resolveConfig, resolveConfigFileAndRefs } from './config-resolvers';
 import { bundleConfig } from '../bundle';
 
 import type { Document } from '../resolve';
-import type { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
+import type { RegionalToken, RegionalTokenWithValidity } from '../redocly/redocly-client-types';
 import type { RawConfig, RawUniversalConfig, Region } from './types';
 import type { BaseResolver, ResolvedRefMap } from '../resolve';
 
@@ -25,7 +25,7 @@ async function addConfigMetadata({
   rawConfig: RawConfig;
   customExtends?: string[];
   configPath?: string;
-  tokens?: RegionalTokenWithValidity[];
+  tokens?: RegionalToken[];
   files?: string[];
   region?: Region;
   externalRefResolver?: BaseResolver;
@@ -102,8 +102,8 @@ export async function loadConfig(
   } = options;
   const rawConfig = await getConfig({ configPath, processRawConfig, externalRefResolver });
 
-  const redoclyClient = new RedoclyClient();
-  const tokens = await redoclyClient.getTokens();
+  const redoclyClient = isEmptyObject(fs) ? undefined : new RedoclyClient();
+  const tokens = redoclyClient && redoclyClient.hasTokens() ? redoclyClient.getAllTokens() : [];
 
   return addConfigMetadata({
     rawConfig,
@@ -119,7 +119,7 @@ export async function loadConfig(
 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;
+  if (!fs?.hasOwnProperty?.('existsSync')) return;
   const existingConfigFiles = CONFIG_FILE_NAMES.map((name) =>
     dir ? path.resolve(dir, name) : name
   ).filter(fs.existsSync);
@@ -141,7 +141,9 @@ export async function getConfig(
   } = {}
 ): Promise<RawConfig> {
   const { configPath = findConfig(), processRawConfig, externalRefResolver } = options;
-  if (!configPath || !doesYamlFileExist(configPath)) return {};
+  if (!configPath || !['.yaml', '.yml'].includes(path.extname(configPath))) {
+    return {};
+  }
   try {
     const { document, resolvedRefMap } = await resolveConfigFileAndRefs({
       configPath,
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index 7f2dabae..c34cf511 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -206,7 +206,7 @@ export function isCustomRuleId(id: string) {
 export function doesYamlFileExist(filePath: string): boolean {
   return (
     (extname(filePath) === '.yaml' || extname(filePath) === '.yml') &&
-    fs.hasOwnProperty('existsSync') &&
+    fs?.hasOwnProperty?.('existsSync') &&
     fs.existsSync(filePath)
   );
 }


Test patch:
diff --git a/packages/core/src/config/__tests__/load.test.ts b/packages/core/src/config/__tests__/load.test.ts
index e1a456a8..c762b75f 100644
--- a/packages/core/src/config/__tests__/load.test.ts
+++ b/packages/core/src/config/__tests__/load.test.ts
@@ -13,10 +13,9 @@ const path = require('path');
 describe('loadConfig', () => {
   it('should resolve config http header by US region', async () => {
     jest
-      .spyOn(RedoclyClient.prototype, 'getTokens')
-      .mockImplementation(() =>
-        Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }])
-      );
+      .spyOn(RedoclyClient.prototype, 'getAllTokens')
+      .mockImplementation(() => [{ region: 'us', token: 'accessToken' }]);
+    jest.spyOn(RedoclyClient.prototype, 'hasTokens').mockImplementation(() => true);
     const config = await loadConfig();
     expect(config.resolve.http.headers).toStrictEqual([
       {
@@ -36,10 +35,9 @@ describe('loadConfig', () => {
 
   it('should resolve config http header by EU region', async () => {
     jest
-      .spyOn(RedoclyClient.prototype, 'getTokens')
-      .mockImplementation(() =>
-        Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }])
-      );
+      .spyOn(RedoclyClient.prototype, 'getAllTokens')
+      .mockImplementation(() => [{ region: 'eu', token: 'accessToken' }]);
+    jest.spyOn(RedoclyClient.prototype, 'hasTokens').mockImplementation(() => true);
     const config = await loadConfig();
     expect(config.resolve.http.headers).toStrictEqual([
       {