mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: openapi-core in browser support (#811)
* feat: use openapi-core in browser Co-authored-by: Andrew Tatomyr <andrew.tatomyr@redocly.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
coverage/
|
||||
dist/
|
||||
packages/cli/lib/
|
||||
packages/core/lib/
|
||||
*snapshot.js
|
||||
|
||||
@@ -2,6 +2,7 @@ module.exports = {
|
||||
clearMocks: true,
|
||||
restoreMocks: true,
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
collectCoverageFrom: [
|
||||
'packages/*/src/**/*.ts',
|
||||
'!packages/**/__tests__/**/*',
|
||||
|
||||
53
packages/core/src/__tests__/logger-browser.test.ts
Normal file
53
packages/core/src/__tests__/logger-browser.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import * as colorette from 'colorette';
|
||||
import { logger, colorize } from '../logger';
|
||||
|
||||
describe('Logger in Browser', () => {
|
||||
it('should call "console.error"', () => {
|
||||
const error = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
logger.error('error');
|
||||
|
||||
expect(error).toBeCalledTimes(1);
|
||||
expect(error).toBeCalledWith('error');
|
||||
|
||||
error.mockRestore();
|
||||
});
|
||||
|
||||
it('should call "console.log"', () => {
|
||||
const log = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
logger.info('info');
|
||||
|
||||
expect(log).toBeCalledTimes(1);
|
||||
expect(log).toBeCalledWith('info');
|
||||
|
||||
log.mockRestore();
|
||||
});
|
||||
|
||||
it('should call "console.warn"', () => {
|
||||
const warn = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
logger.warn('warn');
|
||||
|
||||
expect(warn).toBeCalledTimes(1);
|
||||
expect(warn).toBeCalledWith('warn');
|
||||
|
||||
warn.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('colorize in Browser', () => {
|
||||
it('should not call original colorette lib', () => {
|
||||
const color = 'cyan';
|
||||
const spyingCyan = jest.spyOn(colorette, color);
|
||||
|
||||
const colorized = colorize.cyan(color);
|
||||
|
||||
expect(spyingCyan).not.toBeCalled();
|
||||
expect(colorized).toEqual(color);
|
||||
});
|
||||
});
|
||||
47
packages/core/src/__tests__/logger.test.ts
Normal file
47
packages/core/src/__tests__/logger.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as colorette from 'colorette';
|
||||
import { logger, colorize } from '../logger';
|
||||
|
||||
describe('Logger in nodejs', () => {
|
||||
let spyingStderr: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyingStderr = jest.spyOn(process.stderr, 'write').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spyingStderr.mockRestore();
|
||||
});
|
||||
|
||||
it('should call "process.stderr.write" for error severity', () => {
|
||||
logger.error('error');
|
||||
|
||||
expect(spyingStderr).toBeCalledTimes(1);
|
||||
expect(spyingStderr).toBeCalledWith(colorette.red('error'));
|
||||
});
|
||||
|
||||
it('should call "process.stderr.write" for warn severity', () => {
|
||||
logger.warn('warn');
|
||||
|
||||
expect(spyingStderr).toBeCalledTimes(1);
|
||||
expect(spyingStderr).toBeCalledWith(colorette.yellow('warn'));
|
||||
});
|
||||
|
||||
it('should call "process.stderr.write" for info severity', () => {
|
||||
logger.info('info');
|
||||
|
||||
expect(spyingStderr).toBeCalledTimes(1);
|
||||
expect(spyingStderr).toBeCalledWith('info');
|
||||
});
|
||||
});
|
||||
|
||||
describe('colorize in nodejs', () => {
|
||||
it('should call original colorette lib', () => {
|
||||
const color = 'cyan';
|
||||
const spyingCyan = jest.spyOn(colorette, color);
|
||||
|
||||
const colorized = colorize.cyan(color);
|
||||
|
||||
expect(spyingCyan).toBeCalledWith(color);
|
||||
expect(colorized).toEqual(colorette[color](color));
|
||||
});
|
||||
});
|
||||
18
packages/core/src/__tests__/output-browser.test.ts
Normal file
18
packages/core/src/__tests__/output-browser.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { output } from '../output';
|
||||
|
||||
describe('output', () => {
|
||||
it('should ignore all parsable data in browser', () => {
|
||||
const spyingStdout = jest.spyOn(process.stdout, 'write').mockImplementation();
|
||||
const data = '{ "errors" : [] }';
|
||||
|
||||
output.write(data);
|
||||
|
||||
expect(spyingStdout).not.toBeCalled();
|
||||
|
||||
spyingStdout.mockRestore();
|
||||
});
|
||||
});
|
||||
15
packages/core/src/__tests__/output.test.ts
Normal file
15
packages/core/src/__tests__/output.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { output } from '../output';
|
||||
|
||||
describe('output', () => {
|
||||
it('should write all parsable data to stdout', () => {
|
||||
const spyingStdout = jest.spyOn(process.stdout, 'write').mockImplementation();
|
||||
const data = '{ "errors" : [] }';
|
||||
|
||||
output.write(data);
|
||||
|
||||
expect(spyingStdout).toBeCalledTimes(1);
|
||||
expect(spyingStdout).toBeCalledWith(data);
|
||||
|
||||
spyingStdout.mockRestore();
|
||||
});
|
||||
});
|
||||
11
packages/core/src/__tests__/utils-browser.test.ts
Normal file
11
packages/core/src/__tests__/utils-browser.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { isBrowser } from '../env';
|
||||
|
||||
describe('isBrowser', () => {
|
||||
it('should be browser', () => {
|
||||
expect(isBrowser).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getMatchingStatusCodeRange,
|
||||
doesYamlFileExist,
|
||||
} from '../utils';
|
||||
import { isBrowser } from '../env';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -122,5 +123,11 @@ describe('utils', () => {
|
||||
expect(doesYamlFileExist('redocly.yam')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBrowser', () => {
|
||||
it('should not be browser', () => {
|
||||
expect(isBrowser).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as path from 'path';
|
||||
import { blue, red } from 'colorette';
|
||||
import { isAbsoluteUrl } from '../ref-utils';
|
||||
import { BaseResolver } from '../resolve';
|
||||
import { defaultPlugin } from './builtIn';
|
||||
@@ -21,8 +20,10 @@ import type {
|
||||
RuleConfig,
|
||||
DeprecatedInRawConfig,
|
||||
} from './types';
|
||||
import { isBrowser } from '../env';
|
||||
import { isNotString, isString, notUndefined, parseYaml } from '../utils';
|
||||
import { Config } from './config';
|
||||
import { colorize, logger } from '../logger';
|
||||
|
||||
export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
|
||||
if (rawConfig.styleguide?.extends?.some(isNotString)) {
|
||||
@@ -71,35 +72,57 @@ export function resolvePlugins(
|
||||
): Plugin[] {
|
||||
if (!plugins) return [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
|
||||
// TODO: implement or reuse Resolver approach so it will work in node and browser envs
|
||||
const requireFunc = (plugin: string | Plugin): Plugin | undefined => {
|
||||
if (isBrowser && isString(plugin)) {
|
||||
logger.error(`Cannot load ${plugin}. Plugins aren't supported in browser yet.`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isString(plugin)) {
|
||||
const absoltePluginPath = path.resolve(path.dirname(configPath), plugin);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return typeof __webpack_require__ === 'function'
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
__non_webpack_require__(absoltePluginPath)
|
||||
: require(absoltePluginPath);
|
||||
}
|
||||
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const seenPluginIds = new Map<string, string>();
|
||||
|
||||
return plugins
|
||||
.map((p) => {
|
||||
if (isString(p) && isAbsoluteUrl(p)) {
|
||||
throw new Error(red(`We don't support remote plugins yet.`));
|
||||
throw new Error(colorize.red(`We don't support remote plugins yet.`));
|
||||
}
|
||||
|
||||
// TODO: resolve npm packages similar to eslint
|
||||
const pluginModule = isString(p)
|
||||
? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
|
||||
: p;
|
||||
const pluginModule = requireFunc(p);
|
||||
|
||||
if (!pluginModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = pluginModule.id;
|
||||
if (typeof id !== 'string') {
|
||||
throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
|
||||
throw new Error(
|
||||
colorize.red(`Plugin must define \`id\` property in ${colorize.blue(p.toString())}.`)
|
||||
);
|
||||
}
|
||||
|
||||
if (seenPluginIds.has(id)) {
|
||||
const pluginPath = seenPluginIds.get(id)!;
|
||||
throw new Error(
|
||||
red(
|
||||
`Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
|
||||
id
|
||||
)}" already seen in ${blue(pluginPath)}`
|
||||
colorize.red(
|
||||
`Plugin "id" must be unique. Plugin ${colorize.blue(
|
||||
p.toString()
|
||||
)} uses id "${colorize.blue(id)}" already seen in ${colorize.blue(pluginPath)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -285,17 +308,19 @@ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedSt
|
||||
const { pluginId, configName } = parsePresetName(presetName);
|
||||
const plugin = plugins.find((p) => p.id === pluginId);
|
||||
if (!plugin) {
|
||||
throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
|
||||
throw new Error(
|
||||
`Invalid config ${colorize.red(presetName)}: plugin ${pluginId} is not included.`
|
||||
);
|
||||
}
|
||||
|
||||
const preset = plugin.configs?.[configName];
|
||||
if (!preset) {
|
||||
throw new Error(
|
||||
pluginId
|
||||
? `Invalid config ${red(
|
||||
? `Invalid config ${colorize.red(
|
||||
presetName
|
||||
)}: plugin ${pluginId} doesn't export config with name ${configName}.`
|
||||
: `Invalid config ${red(presetName)}: there is no such built-in config.`
|
||||
: `Invalid config ${colorize.red(presetName)}: there is no such built-in config.`
|
||||
);
|
||||
}
|
||||
return preset;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { parseYaml, stringifyYaml } from '../js-yaml';
|
||||
import { slash, doesYamlFileExist } from '../utils';
|
||||
import { NormalizedProblem } from '../walk';
|
||||
import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
|
||||
import { env } from '../env';
|
||||
|
||||
import type { NodeType } from '../types';
|
||||
import type {
|
||||
@@ -19,9 +20,6 @@ import type {
|
||||
} from './types';
|
||||
import { getResolveConfig } from './utils';
|
||||
|
||||
// Alias environment here so this file can work in browser environments too.
|
||||
export const env = typeof process !== 'undefined' ? process.env || {} : {};
|
||||
|
||||
export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
|
||||
const IGNORE_BANNER =
|
||||
`# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { yellow } from 'colorette';
|
||||
import {
|
||||
assignExisting,
|
||||
isTruthy,
|
||||
@@ -18,6 +17,7 @@ import type {
|
||||
ResolvedStyleguideConfig,
|
||||
RulesFields,
|
||||
} from './types';
|
||||
import { logger, colorize } from '../logger';
|
||||
|
||||
export function parsePresetName(presetName: string): { pluginId: string; configName: string } {
|
||||
if (presetName.indexOf('/') > -1) {
|
||||
@@ -222,7 +222,7 @@ export function getUniquePlugins(plugins: Plugin[]): Plugin[] {
|
||||
results.push(p);
|
||||
seen.add(p.id);
|
||||
} else if (p.id) {
|
||||
process.stderr.write(`Duplicate plugin id "${yellow(p.id)}".\n`);
|
||||
logger.warn(`Duplicate plugin id "${colorize.red(p.id)}".\n`);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
||||
5
packages/core/src/env.ts
Normal file
5
packages/core/src/env.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const isBrowser =
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
typeof window !== 'undefined' || typeof self !== 'undefined' || typeof process === 'undefined'; // main and worker thread
|
||||
export const env = isBrowser ? {} : process.env || {};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { gray, red, options as colorOptions } from 'colorette';
|
||||
import * as yamlAst from 'yaml-ast-parser';
|
||||
import { unescapePointer } from '../ref-utils';
|
||||
import { LineColLocationObject, Loc, LocationObject } from '../walk';
|
||||
import { colorize, colorOptions } from '../logger';
|
||||
|
||||
type YAMLMapping = yamlAst.YAMLMapping & { kind: yamlAst.Kind.MAPPING };
|
||||
type YAMLMap = yamlAst.YamlMap & { kind: yamlAst.Kind.MAP };
|
||||
@@ -39,14 +39,20 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
|
||||
const startIdx = i === startLineNum ? start.col - 1 : currentPad;
|
||||
const endIdx = i === endLineNum ? end.col - 1 : line.length;
|
||||
|
||||
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, red)]);
|
||||
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, colorize.red)]);
|
||||
if (!color) prefixedLines.push(['', underlineLine(line, startIdx, endIdx)]);
|
||||
}
|
||||
|
||||
if (skipLines > 0) {
|
||||
prefixedLines.push([`…`, `${whitespace(currentPad)}${gray(`< ${skipLines} more lines >`)}`]);
|
||||
prefixedLines.push([
|
||||
`…`,
|
||||
`${whitespace(currentPad)}${colorize.gray(`< ${skipLines} more lines >`)}`,
|
||||
]);
|
||||
// print last line
|
||||
prefixedLines.push([`${endLineNum}`, markLine(lines[endLineNum - 1], -1, end.col - 1, red)]);
|
||||
prefixedLines.push([
|
||||
`${endLineNum}`,
|
||||
markLine(lines[endLineNum - 1], -1, end.col - 1, colorize.red),
|
||||
]);
|
||||
|
||||
if (!color) prefixedLines.push(['', underlineLine(lines[endLineNum - 1], -1, end.col - 1)]);
|
||||
}
|
||||
@@ -63,7 +69,7 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
|
||||
line: string,
|
||||
startIdx: number = -1,
|
||||
endIdx: number = +Infinity,
|
||||
variant = gray
|
||||
variant = colorize.gray
|
||||
) {
|
||||
if (!color) return line;
|
||||
if (!line) return line;
|
||||
@@ -90,7 +96,7 @@ function printPrefixedLines(lines: [string, string][]): string {
|
||||
return existingLines
|
||||
.map(
|
||||
([prefix, line]) =>
|
||||
gray(leftPad(padLen, prefix) + ' |') +
|
||||
colorize.gray(leftPad(padLen, prefix) + ' |') +
|
||||
(line ? ' ' + limitLineLength(line.substring(dedentLen)) : '')
|
||||
)
|
||||
.join('\n');
|
||||
@@ -99,7 +105,7 @@ function printPrefixedLines(lines: [string, string][]): string {
|
||||
function limitLineLength(line: string, maxLen: number = MAX_LINE_LENGTH) {
|
||||
const overflowLen = line.length - maxLen;
|
||||
if (overflowLen > 0) {
|
||||
const charsMoreText = gray(`...<${overflowLen} chars>`);
|
||||
const charsMoreText = colorize.gray(`...<${overflowLen} chars>`);
|
||||
return line.substring(0, maxLen - charsMoreText.length) + charsMoreText;
|
||||
} else {
|
||||
return line;
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import * as path from 'path';
|
||||
import {
|
||||
options as colorOptions,
|
||||
gray,
|
||||
blue,
|
||||
bgRed,
|
||||
bgYellow,
|
||||
black,
|
||||
yellow,
|
||||
red,
|
||||
} from 'colorette';
|
||||
import { colorOptions, colorize, logger } from '../logger';
|
||||
import { output } from '../output';
|
||||
|
||||
const coreVersion = require('../../package.json').version;
|
||||
|
||||
import { NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject } from '../walk';
|
||||
import { getCodeframe, getLineColLocation } from './codeframes';
|
||||
import { env } from '../config';
|
||||
import { env } from '../env';
|
||||
|
||||
export type Totals = {
|
||||
errors: number;
|
||||
@@ -27,13 +19,13 @@ const ERROR_MESSAGE = {
|
||||
};
|
||||
|
||||
const BG_COLORS = {
|
||||
warn: (str: string) => bgYellow(black(str)),
|
||||
error: bgRed,
|
||||
warn: (str: string) => colorize.bgYellow(colorize.black(str)),
|
||||
error: colorize.bgRed,
|
||||
};
|
||||
|
||||
const COLORS = {
|
||||
warn: yellow,
|
||||
error: red,
|
||||
warn: colorize.yellow,
|
||||
error: colorize.red,
|
||||
};
|
||||
|
||||
const SEVERITY_NAMES = {
|
||||
@@ -114,7 +106,7 @@ export function formatProblems(
|
||||
case 'codeframe':
|
||||
for (let i = 0; i < problems.length; i++) {
|
||||
const problem = problems[i];
|
||||
process.stderr.write(`${formatCodeframe(problem, i)}\n`);
|
||||
logger.info(`${formatCodeframe(problem, i)}\n`);
|
||||
}
|
||||
break;
|
||||
case 'stylish': {
|
||||
@@ -122,30 +114,30 @@ export function formatProblems(
|
||||
for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(
|
||||
groupedByFile
|
||||
)) {
|
||||
process.stderr.write(`${blue(path.relative(cwd, file))}:\n`);
|
||||
logger.info(`${colorize.blue(path.relative(cwd, file))}:\n`);
|
||||
|
||||
for (let i = 0; i < fileProblems.length; i++) {
|
||||
const problem = fileProblems[i];
|
||||
process.stderr.write(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
|
||||
logger.info(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
|
||||
}
|
||||
|
||||
process.stderr.write('\n');
|
||||
logger.info('\n');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'checkstyle': {
|
||||
const groupedByFile = groupByFiles(problems);
|
||||
|
||||
process.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n');
|
||||
process.stdout.write('<checkstyle version="4.3">\n');
|
||||
output.write('<?xml version="1.0" encoding="UTF-8"?>\n');
|
||||
output.write('<checkstyle version="4.3">\n');
|
||||
|
||||
for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
|
||||
process.stdout.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
|
||||
output.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
|
||||
fileProblems.forEach(formatCheckstyle);
|
||||
process.stdout.write(`</file>\n`);
|
||||
output.write(`</file>\n`);
|
||||
}
|
||||
|
||||
process.stdout.write(`</checkstyle>\n`);
|
||||
output.write(`</checkstyle>\n`);
|
||||
break;
|
||||
}
|
||||
case 'codeclimate':
|
||||
@@ -154,8 +146,8 @@ export function formatProblems(
|
||||
}
|
||||
|
||||
if (totalProblems - ignoredProblems > maxProblems) {
|
||||
process.stderr.write(
|
||||
`< ... ${totalProblems - maxProblems} more problems hidden > ${gray(
|
||||
logger.info(
|
||||
`< ... ${totalProblems - maxProblems} more problems hidden > ${colorize.gray(
|
||||
'increase with `--max-problems N`'
|
||||
)}\n`
|
||||
);
|
||||
@@ -177,7 +169,7 @@ export function formatProblems(
|
||||
fingerprint: `${p.ruleId}${p.location.length > 0 ? '-' + p.location[0].pointer : ''}`,
|
||||
};
|
||||
});
|
||||
process.stdout.write(JSON.stringify(issues, null, 2));
|
||||
output.write(JSON.stringify(issues, null, 2));
|
||||
}
|
||||
|
||||
function outputJSON() {
|
||||
@@ -211,7 +203,7 @@ export function formatProblems(
|
||||
return problem;
|
||||
}),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(resultObject, null, 2));
|
||||
output.write(JSON.stringify(resultObject, null, 2));
|
||||
}
|
||||
|
||||
function getBgColor(problem: NormalizedProblem) {
|
||||
@@ -227,7 +219,7 @@ export function formatProblems(
|
||||
const location = problem.location[0]; // TODO: support multiple locations
|
||||
const relativePath = path.relative(cwd, location.source.absoluteRef);
|
||||
const loc = getLineColLocation(location);
|
||||
const atPointer = location.pointer ? gray(`at ${location.pointer}`) : '';
|
||||
const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
|
||||
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
||||
return (
|
||||
`[${idx + 1}] ${bgColor(fileWithLoc)} ${atPointer}\n\n` +
|
||||
@@ -236,7 +228,9 @@ export function formatProblems(
|
||||
getCodeframe(loc, color) +
|
||||
'\n\n' +
|
||||
formatFrom(cwd, problem.from) +
|
||||
`${SEVERITY_NAMES[problem.severity]} was generated by the ${blue(problem.ruleId)} rule.\n\n`
|
||||
`${SEVERITY_NAMES[problem.severity]} was generated by the ${colorize.blue(
|
||||
problem.ruleId
|
||||
)} rule.\n\n`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -257,7 +251,7 @@ export function formatProblems(
|
||||
const severity = problem.severity == 'warn' ? 'warning' : 'error';
|
||||
const message = xmlEscape(problem.message);
|
||||
const source = xmlEscape(problem.ruleId);
|
||||
process.stdout.write(
|
||||
output.write(
|
||||
`<error line="${line}" column="${col}" severity="${severity}" message="${message}" source="${source}" />\n`
|
||||
);
|
||||
}
|
||||
@@ -269,7 +263,7 @@ function formatFrom(cwd: string, location?: LocationObject) {
|
||||
const loc = getLineColLocation(location);
|
||||
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
||||
|
||||
return `referenced from ${blue(fileWithLoc)}\n\n`;
|
||||
return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
|
||||
}
|
||||
|
||||
function formatDidYouMean(problem: NormalizedProblem) {
|
||||
|
||||
34
packages/core/src/logger.ts
Normal file
34
packages/core/src/logger.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as colorette from 'colorette';
|
||||
export { options as colorOptions } from 'colorette';
|
||||
|
||||
import { isBrowser } from './env';
|
||||
import { identity } from './utils';
|
||||
|
||||
export const colorize = new Proxy(colorette, {
|
||||
get(target: typeof colorette, prop: string): typeof identity {
|
||||
if (isBrowser) {
|
||||
return identity;
|
||||
}
|
||||
|
||||
return (target as any)[prop];
|
||||
},
|
||||
});
|
||||
class Logger {
|
||||
protected stderr(str: string) {
|
||||
return process.stderr.write(str);
|
||||
}
|
||||
|
||||
info(str: string) {
|
||||
return isBrowser ? console.log(str) : this.stderr(str);
|
||||
}
|
||||
|
||||
warn(str: string) {
|
||||
return isBrowser ? console.warn(str) : this.stderr(colorize.yellow(str));
|
||||
}
|
||||
|
||||
error(str: string) {
|
||||
return isBrowser ? console.error(str) : this.stderr(colorize.red(str));
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
7
packages/core/src/output.ts
Normal file
7
packages/core/src/output.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { isBrowser } from './env';
|
||||
|
||||
export const output = {
|
||||
write(str: string) {
|
||||
return isBrowser ? undefined : process.stdout.write(str);
|
||||
},
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { green } from 'colorette';
|
||||
import { RegistryApi } from './registry-api';
|
||||
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS, env } from '../config/config';
|
||||
import { DEFAULT_REGION, DOMAINS, AVAILABLE_REGIONS } from '../config/config';
|
||||
import { env } from '../env';
|
||||
import { RegionalToken, RegionalTokenWithValidity } from './redocly-client-types';
|
||||
import { isNotEmptyObject } from '../utils';
|
||||
import { colorize } from '../logger';
|
||||
|
||||
import type { AccessTokens, Region } from '../config/types';
|
||||
|
||||
@@ -29,7 +30,9 @@ export class RedoclyClient {
|
||||
loadRegion(region?: Region) {
|
||||
if (region && !DOMAINS[region]) {
|
||||
throw new Error(
|
||||
`Invalid argument: region in config file.\nGiven: ${green(region)}, choices: "us", "eu".`
|
||||
`Invalid argument: region in config file.\nGiven: ${colorize.green(
|
||||
region
|
||||
)}, choices: "us", "eu".`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import * as pluralize from 'pluralize';
|
||||
import { parseYaml } from './js-yaml';
|
||||
import { UserContext } from './walk';
|
||||
import { HttpResolveConfig } from './config';
|
||||
import { env } from './config';
|
||||
import { green, yellow } from 'colorette';
|
||||
import { env } from './env';
|
||||
import { logger, colorize } from './logger';
|
||||
|
||||
export { parseYaml, stringifyYaml } from './js-yaml';
|
||||
|
||||
@@ -207,8 +207,8 @@ export function doesYamlFileExist(filePath: string): boolean {
|
||||
}
|
||||
|
||||
export function showWarningForDeprecatedField(deprecatedField: string, updatedField: string) {
|
||||
process.stderr.write(
|
||||
`The ${yellow(deprecatedField)} field is deprecated. Use ${green(
|
||||
logger.warn(
|
||||
`The ${colorize.red(deprecatedField)} field is deprecated. Use ${colorize.green(
|
||||
updatedField
|
||||
)} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`
|
||||
);
|
||||
@@ -223,3 +223,7 @@ export type Falsy = undefined | null | false | '' | 0;
|
||||
export function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy {
|
||||
return !!value;
|
||||
}
|
||||
|
||||
export function identity<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user