Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
53e4d61
fix: update lint-ignore logic
kanoru3101 Jan 7, 2026
323e239
fix: update tests
kanoru3101 Jan 7, 2026
f0491ec
fix: add logs
kanoru3101 Jan 7, 2026
04aff2e
fix: refactoring
kanoru3101 Jan 8, 2026
80c55f0
fix: refactoring
kanoru3101 Jan 8, 2026
b12cd36
fix: revert changes
kanoru3101 Jan 8, 2026
25d06f0
fix: update tests
kanoru3101 Jan 8, 2026
dd80539
fix: add logs
kanoru3101 Jan 8, 2026
fb47186
fix(core): remove logs
kanoru3101 Jan 8, 2026
0f336e4
fix(core): remove comments
kanoru3101 Jan 8, 2026
a12049c
fix(core): refactoring
kanoru3101 Jan 8, 2026
2d024cc
Merge remote-tracking branch 'origin/main' into fix/ignore-file-for-b…
kanoru3101 Jan 8, 2026
4a53e67
Merge remote-tracking branch 'origin/main' into fix/ignore-file-for-b…
kanoru3101 Jan 8, 2026
4465598
fix(core): resolve comments
kanoru3101 Jan 13, 2026
83bf54c
Merge remote-tracking branch 'origin/main' into fix/ignore-file-for-b…
kanoru3101 Jan 13, 2026
8cc6944
Update .changeset/sour-wings-follow.md
kanoru3101 Jan 13, 2026
12cfd27
fix(core): refactoring
kanoru3101 Jan 13, 2026
d85e099
fix(core): test in reunite changes
kanoru3101 Jan 13, 2026
cf41d7b
fix(core): update
kanoru3101 Jan 13, 2026
2c56788
fix(core): revert changes
kanoru3101 Jan 13, 2026
483b4d1
fix(core): check again
kanoru3101 Jan 13, 2026
aa636ba
fix(core): update snapshot for smoke test
kanoru3101 Jan 13, 2026
1b0ddb8
fix(core): update load again
kanoru3101 Jan 14, 2026
a2514c0
fix(core): debug load
kanoru3101 Jan 14, 2026
4260ee7
fix(core): add logs
kanoru3101 Jan 14, 2026
d3abf6f
fix(core): remove logs
kanoru3101 Jan 14, 2026
0f7eb30
Merge remote-tracking branch 'origin/main' into fix/ignore-file-for-b…
kanoru3101 Jan 14, 2026
42a85f6
fix(core): resolve comment
kanoru3101 Jan 19, 2026
236f083
fix(core): move the part of logic to а config adopter
kanoru3101 Jan 19, 2026
d64b3ac
fix(core): rebuild redoc.html
kanoru3101 Jan 19, 2026
4a988ea
fix(core): fix valuability
kanoru3101 Jan 19, 2026
a67636b
fix(core): fix bug with path without redocly.yaml
kanoru3101 Jan 19, 2026
76e98ee
fix(core): update export
kanoru3101 Jan 19, 2026
a4aeeef
fix(core): add logs
kanoru3101 Jan 19, 2026
0908c72
fix(core): remove logs
kanoru3101 Jan 19, 2026
aad5b73
fix(core): remove isAbsoluteUrlOrFileUrl and update isAbsoluteUrl
kanoru3101 Jan 20, 2026
5a026f5
fix(core): resolve comments and add log for ConfigDir
kanoru3101 Jan 21, 2026
84c82cf
fix(core): update resolvedFileName
kanoru3101 Jan 21, 2026
0ee91d4
fix(core): remove console.log and resolvedIgnoreDir
kanoru3101 Jan 21, 2026
876d5ec
fix(core): add types
kanoru3101 Jan 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sour-wings-follow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@redocly/openapi-core": patch
---

Fixed loading of `.redocly.lint-ignore.yaml` in browser environments.
32 changes: 9 additions & 23 deletions packages/core/src/config/__tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
import { type SpecVersion } from '../../oas-types.js';
import { Config } from '../config.js';
import * as jsYaml from '../../js-yaml/index.js';
import * as fs from 'node:fs';
import { ignoredFileStub } from './fixtures/ingore-file.js';
import * as path from 'node:path';
import { createConfig } from '../index.js';
import * as doesYamlFileExistModule from '../../utils/does-yaml-file-exist.js';

vi.mock('../../js-yaml/index.js', async () => {
const actual = await vi.importActual('../../js-yaml/index.js');
return { ...actual };
});
vi.mock('node:fs', async () => {
const actual = await vi.importActual('node:fs');
return { ...actual };
});
vi.mock('node:path', async () => {
const actual = await vi.importActual('node:path');
return { ...actual };
});

// Create the config and clean up not needed props for consistency
const testConfig: Config = await createConfig(
Expand Down Expand Up @@ -237,12 +219,16 @@ describe('Config.extendTypes', () => {

describe('generation ignore object', () => {
it('should generate config with absoluteUri for ignore', () => {
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => '');
vi.spyOn(jsYaml, 'parseYaml').mockImplementationOnce(() => ignoredFileStub);
vi.spyOn(doesYamlFileExistModule, 'doesYamlFileExist').mockImplementationOnce(() => true);
vi.spyOn(path, 'resolve').mockImplementationOnce((_, filename) => `some-path/${filename}`);
const ignore = {
'some-path/openapi.yaml': {
'no-unused-components': new Set(['#/components/schemas/Foo']),
},
'https://some-path.yaml': {
'no-unused-components': new Set(['#/components/schemas/Foo']),
},
};

const config = new Config(testConfig.resolvedConfig);
const config = new Config(testConfig.resolvedConfig, { ignore });
config.resolvedConfig = 'resolvedConfig stub' as any;

expect(config).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
api.yaml:
operation-operationId:
- '#/paths/~1pets/get/operationId'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rules:
operation-operationId: error
8 changes: 0 additions & 8 deletions packages/core/src/config/__tests__/fixtures/ingore-file.ts

This file was deleted.

30 changes: 30 additions & 0 deletions packages/core/src/config/__tests__/load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1344,3 +1344,33 @@ function verifyOasRules(
}
});
}

describe('loadIgnoreFile', () => {
const ignoreFileDir = path.join(__dirname, './fixtures/ignore-file');
const ignoreFileConfig = path.join(ignoreFileDir, 'redocly.yaml');
const expectedIgnoreKey = path.join(ignoreFileDir, 'api.yaml');

it('should load and parse ignore file correctly', async () => {
const config = await loadConfig({ configPath: ignoreFileConfig });

expect(Object.keys(config.ignore)).toEqual([expectedIgnoreKey]);
expect(config.ignore[expectedIgnoreKey]['operation-operationId']).toBeInstanceOf(Set);
});

it('should return empty object when ignore file does not exist', async () => {
const configPath = path.join(__dirname, './fixtures/load-redocly.yaml');
const config = await loadConfig({ configPath });

expect(config.ignore).toEqual({});
});

it('should load ignore file in browser environment (without fs.existsSync)', async () => {
const existsSyncSpy = vi.spyOn(fs, 'existsSync').mockImplementation(undefined as any);

const config = await loadConfig({ configPath: ignoreFileConfig });

expect(Object.keys(config.ignore)).toEqual([expectedIgnoreKey]);

existsSyncSpy.mockRestore();
});
});
43 changes: 4 additions & 39 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parseYaml, stringifyYaml } from '../js-yaml/index.js';
import { stringifyYaml } from '../js-yaml/index.js';
import { slash } from '../utils/slash.js';
import { doesYamlFileExist } from '../utils/does-yaml-file-exist.js';
import { isPlainObject } from '../utils/is-plain-object.js';
import { specVersions } from '../detect-spec.js';
import { isBrowser } from '../env.js';
import { getResolveConfig } from './get-resolve-config.js';
import { isAbsoluteUrl } from '../ref-utils.js';
import { groupAssertionRules } from './group-assertion-rules.js';
Expand Down Expand Up @@ -35,16 +33,6 @@ import type {
RuleSettings,
} from './types.js';

function getIgnoreFilePath(configPath?: string): string | undefined {
if (configPath) {
return doesYamlFileExist(configPath)
? path.join(path.dirname(configPath), IGNORE_FILE)
: path.join(configPath, IGNORE_FILE);
} else {
return isBrowser ? undefined : path.join(process.cwd(), IGNORE_FILE);
}
}

export class Config {
resolvedConfig: ResolvedConfig;
configPath?: string;
Expand All @@ -71,6 +59,7 @@ export class Config {
resolvedRefMap?: ResolvedRefMap;
alias?: string;
plugins?: Plugin[];
ignore?: Record<string, Record<string, Set<string>>>;
} = {}
) {
this.resolvedConfig = resolvedConfig;
Expand Down Expand Up @@ -153,7 +142,7 @@ export class Config {
},
};

this.resolveIgnore(getIgnoreFilePath(opts.configPath));
this.ignore = opts.ignore || {};
}

forAlias(alias?: string) {
Expand All @@ -171,35 +160,11 @@ export class Config {
resolvedRefMap: this.resolvedRefMap,
alias,
plugins: this.plugins,
ignore: this.ignore,
}
);
}

resolveIgnore(ignoreFile?: string) {
if (!ignoreFile || !doesYamlFileExist(ignoreFile)) return;

this.ignore =
(parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) as Record<
string,
Record<string, Set<string>>
>) || {};

// resolve ignore paths
for (const fileName of Object.keys(this.ignore)) {
this.ignore[
isAbsoluteUrl(fileName) ? fileName : path.resolve(path.dirname(ignoreFile), fileName)
] = this.ignore[fileName];

for (const ruleId of Object.keys(this.ignore[fileName])) {
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
}

if (!isAbsoluteUrl(fileName)) {
delete this.ignore[fileName];
}
}
}

saveIgnore() {
const dir = this.configPath ? path.dirname(this.configPath) : process.cwd();
const ignoreFile = path.join(dir, IGNORE_FILE);
Expand Down
69 changes: 68 additions & 1 deletion packages/core/src/config/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,69 @@ import {
type Document,
type ResolvedRefMap,
} from '../resolve.js';
import { CONFIG_FILE_NAME } from './constants.js';
import { CONFIG_FILE_NAME, IGNORE_FILE } from './constants.js';

import type { RawUniversalConfig } from './types.js';

function isUrl(ref: string): boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we have something similar to this (isAbsolutePath? Maybe it needs to be adjusted.)
Could you check the codebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isAbsoluteUrl is used in many places. I created another function based on it, so as not to break anything. What do you think about this? Or is it better to edit isAbsoluteUrl?

return ref.startsWith('http://') || ref.startsWith('https://') || ref.startsWith('file://');
}

function resolvePath(base: string, relative: string): string {
if (isUrl(base)) {
return new URL(relative, base.endsWith('/') ? base : `${base}/`).href;
}
return path.resolve(base, relative);
}

function getConfigDir(configPath: string): string {
if (!path.extname(configPath)) {
return configPath;
}

return isUrl(configPath)
? configPath.substring(0, configPath.lastIndexOf('/'))
: path.dirname(configPath);
}

async function loadIgnoreFile(
configPath: string | undefined,
resolver: BaseResolver
): Promise<Record<string, Record<string, Set<string>>> | undefined> {
if (!configPath) return undefined;

const configDir = getConfigDir(configPath);
const ignorePath = resolvePath(configDir, IGNORE_FILE);

if (fs?.existsSync && !isUrl(ignorePath) && !fs.existsSync(ignorePath)) {
return undefined;
}

const ignoreDocument = await resolver.resolveDocument(null, ignorePath, true);

if (ignoreDocument instanceof Error || !ignoreDocument.parsed) {
return undefined;
}

const ignore = (ignoreDocument.parsed || {}) as Record<string, Record<string, Set<string>>>;

for (const fileName of Object.keys(ignore)) {
const resolvedFileName = isUrl(fileName) ? fileName : resolvePath(configDir, fileName);

ignore[resolvedFileName] = ignore[fileName];

for (const ruleId of Object.keys(ignore[fileName])) {
ignore[fileName][ruleId] = new Set(ignore[fileName][ruleId]);
}

if (resolvedFileName !== fileName) {
delete ignore[fileName];
}
}

return ignore;
}

export async function loadConfig(
options: {
configPath?: string;
Expand All @@ -38,11 +97,14 @@ export async function loadConfig(
externalRefResolver,
});

const ignore = await loadIgnoreFile(configPath, resolver);

const config = new Config(resolvedConfig, {
configPath,
document: rawConfigDocument,
resolvedRefMap: resolvedRefMap,
plugins,
ignore,
});

return config;
Expand Down Expand Up @@ -79,11 +141,16 @@ export async function createConfig(
configPath,
externalRefResolver,
});

const resolver = externalRefResolver ?? new BaseResolver();
const ignore = await loadIgnoreFile(configPath, resolver);

return new Config(resolvedConfig, {
configPath,
document: rawConfigDocument,
resolvedRefMap,
plugins,
ignore,
});
}

Expand Down
Loading