@redocly/openapi-core 1.0.0-beta.107 → 1.0.0-beta.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/lib/benchmark/benches/lint-with-top-level-rule-report.bench.js +0 -1
  2. package/lib/bundle.js +5 -2
  3. package/lib/config/all.js +2 -2
  4. package/lib/config/config-resolvers.js +31 -13
  5. package/lib/config/config.d.ts +3 -5
  6. package/lib/config/config.js +7 -4
  7. package/lib/config/load.d.ts +7 -0
  8. package/lib/config/load.js +14 -6
  9. package/lib/config/minimal.js +2 -2
  10. package/lib/config/recommended.js +2 -2
  11. package/lib/config/rules.d.ts +1 -1
  12. package/lib/config/utils.js +5 -5
  13. package/lib/decorators/common/registry-dependencies.js +1 -1
  14. package/lib/env.d.ts +3 -0
  15. package/lib/env.js +8 -0
  16. package/lib/format/codeframes.js +16 -10
  17. package/lib/format/format.js +28 -26
  18. package/lib/index.d.ts +5 -5
  19. package/lib/index.js +3 -1
  20. package/lib/js-yaml/index.js +1 -0
  21. package/lib/logger.d.ts +10 -0
  22. package/lib/logger.js +31 -0
  23. package/lib/output.d.ts +3 -0
  24. package/lib/output.js +9 -0
  25. package/lib/redocly/index.js +10 -9
  26. package/lib/redocly/registry-api-types.d.ts +28 -30
  27. package/lib/redocly/registry-api.d.ts +4 -3
  28. package/lib/redocly/registry-api.js +7 -2
  29. package/lib/ref-utils.js +2 -1
  30. package/lib/resolve.d.ts +1 -1
  31. package/lib/resolve.js +1 -1
  32. package/lib/rules/ajv.js +1 -1
  33. package/lib/rules/common/assertions/asserts.js +4 -4
  34. package/lib/rules/common/assertions/index.js +1 -1
  35. package/lib/rules/common/operation-security-defined.js +1 -1
  36. package/lib/rules/common/spec.js +2 -2
  37. package/lib/rules/oas2/remove-unused-components.js +2 -2
  38. package/lib/rules/oas3/index.js +2 -2
  39. package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
  40. package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +4 -4
  41. package/lib/rules/oas3/no-unused-components.js +1 -1
  42. package/lib/rules/oas3/remove-unused-components.js +3 -3
  43. package/lib/rules/utils.d.ts +1 -1
  44. package/lib/rules/utils.js +1 -1
  45. package/lib/types/redocly-yaml.js +1 -1
  46. package/lib/utils.d.ts +3 -0
  47. package/lib/utils.js +15 -7
  48. package/lib/visitors.d.ts +1 -1
  49. package/lib/visitors.js +2 -2
  50. package/package.json +1 -1
  51. package/src/__tests__/logger-browser.test.ts +53 -0
  52. package/src/__tests__/logger.test.ts +47 -0
  53. package/src/__tests__/output-browser.test.ts +18 -0
  54. package/src/__tests__/output.test.ts +15 -0
  55. package/src/__tests__/utils-browser.test.ts +11 -0
  56. package/src/__tests__/utils.test.ts +7 -0
  57. package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
  58. package/src/bundle.ts +6 -3
  59. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -4
  60. package/src/config/__tests__/config.test.ts +35 -0
  61. package/src/config/__tests__/load.test.ts +79 -1
  62. package/src/config/all.ts +2 -2
  63. package/src/config/config-resolvers.ts +43 -17
  64. package/src/config/config.ts +10 -8
  65. package/src/config/load.ts +28 -5
  66. package/src/config/minimal.ts +2 -2
  67. package/src/config/recommended.ts +2 -2
  68. package/src/config/utils.ts +6 -5
  69. package/src/decorators/common/registry-dependencies.ts +1 -1
  70. package/src/env.ts +5 -0
  71. package/src/format/codeframes.ts +15 -9
  72. package/src/format/format.ts +28 -33
  73. package/src/index.ts +6 -4
  74. package/src/js-yaml/index.ts +1 -0
  75. package/src/logger.ts +34 -0
  76. package/src/output.ts +7 -0
  77. package/src/redocly/index.ts +7 -4
  78. package/src/redocly/registry-api-types.ts +27 -29
  79. package/src/redocly/registry-api.ts +16 -6
  80. package/src/ref-utils.ts +2 -1
  81. package/src/resolve.ts +4 -4
  82. package/src/rules/ajv.ts +1 -1
  83. package/src/rules/common/assertions/asserts.ts +4 -4
  84. package/src/rules/common/assertions/index.ts +1 -1
  85. package/src/rules/common/operation-security-defined.ts +1 -1
  86. package/src/rules/common/spec.ts +2 -2
  87. package/src/rules/oas2/remove-unused-components.ts +2 -2
  88. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +16 -16
  89. package/src/rules/oas3/index.ts +2 -2
  90. package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +2 -2
  91. package/src/rules/oas3/no-unused-components.ts +1 -1
  92. package/src/rules/oas3/remove-unused-components.ts +4 -4
  93. package/src/rules/utils.ts +1 -1
  94. package/src/types/redocly-yaml.ts +1 -1
  95. package/src/utils.ts +16 -6
  96. package/src/visitors.ts +5 -5
  97. package/tsconfig.tsbuildinfo +1 -1
  98. package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
@@ -1,5 +1,4 @@
1
1
  import * as path from 'path';
2
- import { blue, red } from 'colorette';
3
2
  import { isAbsoluteUrl } from '../ref-utils';
4
3
  import { BaseResolver } from '../resolve';
5
4
  import { defaultPlugin } from './builtIn';
@@ -21,8 +20,10 @@ import type {
21
20
  RuleConfig,
22
21
  DeprecatedInRawConfig,
23
22
  } from './types';
23
+ import { isBrowser } from '../env';
24
24
  import { isNotString, isString, notUndefined, parseYaml } from '../utils';
25
25
  import { Config } from './config';
26
+ import { colorize, logger } from '../logger';
26
27
 
27
28
  export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
28
29
  if (rawConfig.styleguide?.extends?.some(isNotString)) {
@@ -71,34 +72,57 @@ export function resolvePlugins(
71
72
  ): Plugin[] {
72
73
  if (!plugins) return [];
73
74
 
74
- // @ts-ignore
75
- const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
75
+ // TODO: implement or reuse Resolver approach so it will work in node and browser envs
76
+ const requireFunc = (plugin: string | Plugin): Plugin | undefined => {
77
+ if (isBrowser && isString(plugin)) {
78
+ logger.error(`Cannot load ${plugin}. Plugins aren't supported in browser yet.`);
79
+
80
+ return undefined;
81
+ }
82
+
83
+ if (isString(plugin)) {
84
+ const absoltePluginPath = path.resolve(path.dirname(configPath), plugin);
85
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
86
+ // @ts-ignore
87
+ return typeof __webpack_require__ === 'function'
88
+ ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
89
+ // @ts-ignore
90
+ __non_webpack_require__(absoltePluginPath)
91
+ : require(absoltePluginPath);
92
+ }
93
+
94
+ return plugin;
95
+ };
76
96
 
77
97
  const seenPluginIds = new Map<string, string>();
78
98
 
79
99
  return plugins
80
100
  .map((p) => {
81
101
  if (isString(p) && isAbsoluteUrl(p)) {
82
- throw new Error(red(`We don't support remote plugins yet.`));
102
+ throw new Error(colorize.red(`We don't support remote plugins yet.`));
83
103
  }
84
104
 
85
105
  // TODO: resolve npm packages similar to eslint
86
- const pluginModule = isString(p)
87
- ? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
88
- : p;
106
+ const pluginModule = requireFunc(p);
107
+
108
+ if (!pluginModule) {
109
+ return;
110
+ }
89
111
 
90
112
  const id = pluginModule.id;
91
113
  if (typeof id !== 'string') {
92
- throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
114
+ throw new Error(
115
+ colorize.red(`Plugin must define \`id\` property in ${colorize.blue(p.toString())}.`)
116
+ );
93
117
  }
94
118
 
95
119
  if (seenPluginIds.has(id)) {
96
120
  const pluginPath = seenPluginIds.get(id)!;
97
121
  throw new Error(
98
- red(
99
- `Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
100
- id
101
- )}" already seen in ${blue(pluginPath)}`
122
+ colorize.red(
123
+ `Plugin "id" must be unique. Plugin ${colorize.blue(
124
+ p.toString()
125
+ )} uses id "${colorize.blue(id)}" already seen in ${colorize.blue(pluginPath)}`
102
126
  )
103
127
  );
104
128
  }
@@ -166,7 +190,7 @@ export async function resolveApis({
166
190
  resolver?: BaseResolver;
167
191
  }): Promise<Record<string, ResolvedApi>> {
168
192
  const { apis = {}, styleguide: styleguideConfig = {} } = rawConfig;
169
- let resolvedApis: Record<string, ResolvedApi> = {};
193
+ const resolvedApis: Record<string, ResolvedApi> = {};
170
194
  for (const [apiName, apiContent] of Object.entries(apis || {})) {
171
195
  if (apiContent.styleguide?.extends?.some(isNotString)) {
172
196
  throw new Error(
@@ -284,17 +308,19 @@ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedSt
284
308
  const { pluginId, configName } = parsePresetName(presetName);
285
309
  const plugin = plugins.find((p) => p.id === pluginId);
286
310
  if (!plugin) {
287
- throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
311
+ throw new Error(
312
+ `Invalid config ${colorize.red(presetName)}: plugin ${pluginId} is not included.`
313
+ );
288
314
  }
289
315
 
290
- const preset = plugin.configs?.[configName]! as ResolvedStyleguideConfig;
316
+ const preset = plugin.configs?.[configName];
291
317
  if (!preset) {
292
318
  throw new Error(
293
319
  pluginId
294
- ? `Invalid config ${red(
320
+ ? `Invalid config ${colorize.red(
295
321
  presetName
296
322
  )}: plugin ${pluginId} doesn't export config with name ${configName}.`
297
- : `Invalid config ${red(presetName)}: there is no such built-in config.`
323
+ : `Invalid config ${colorize.red(presetName)}: there is no such built-in config.`
298
324
  );
299
325
  }
300
326
  return preset;
@@ -4,6 +4,7 @@ import { parseYaml, stringifyYaml } from '../js-yaml';
4
4
  import { slash, doesYamlFileExist } from '../utils';
5
5
  import { NormalizedProblem } from '../walk';
6
6
  import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
7
+ import { env } from '../env';
7
8
 
8
9
  import type { NodeType } from '../types';
9
10
  import type {
@@ -19,9 +20,6 @@ import type {
19
20
  } from './types';
20
21
  import { getResolveConfig } from './utils';
21
22
 
22
- // Alias environment here so this file can work in browser environments too.
23
- export const env = typeof process !== 'undefined' ? process.env || {} : {};
24
-
25
23
  export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
26
24
  const IGNORE_BANNER =
27
25
  `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
@@ -172,9 +170,11 @@ export class StyleguideConfig {
172
170
  case OasVersion.Version3_1:
173
171
  if (!plugin.typeExtension.oas3) continue;
174
172
  extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
173
+ break;
175
174
  case OasVersion.Version2:
176
175
  if (!plugin.typeExtension.oas2) continue;
177
176
  extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
177
+ break;
178
178
  default:
179
179
  throw new Error('Not implemented');
180
180
  }
@@ -192,7 +192,7 @@ export class StyleguideConfig {
192
192
  severity: settings,
193
193
  };
194
194
  } else {
195
- return { severity: 'error' as 'error', ...settings };
195
+ return { severity: 'error', ...settings };
196
196
  }
197
197
  }
198
198
 
@@ -203,10 +203,10 @@ export class StyleguideConfig {
203
203
  const settings = this.preprocessors[oasVersion][ruleId] || 'off';
204
204
  if (typeof settings === 'string') {
205
205
  return {
206
- severity: settings === 'on' ? ('error' as 'error') : settings,
206
+ severity: settings === 'on' ? 'error' : settings,
207
207
  };
208
208
  } else {
209
- return { severity: 'error' as 'error', ...settings };
209
+ return { severity: 'error', ...settings };
210
210
  }
211
211
  }
212
212
 
@@ -216,10 +216,10 @@ export class StyleguideConfig {
216
216
  const settings = this.decorators[oasVersion][ruleId] || 'off';
217
217
  if (typeof settings === 'string') {
218
218
  return {
219
- severity: settings === 'on' ? ('error' as 'error') : settings,
219
+ severity: settings === 'on' ? 'error' : settings,
220
220
  };
221
221
  } else {
222
- return { severity: 'error' as 'error', ...settings };
222
+ return { severity: 'error', ...settings };
223
223
  }
224
224
  }
225
225
 
@@ -250,12 +250,14 @@ export class StyleguideConfig {
250
250
  getRulesForOasVersion(version: OasMajorVersion) {
251
251
  switch (version) {
252
252
  case OasMajorVersion.Version3:
253
+ // eslint-disable-next-line no-case-declarations
253
254
  const oas3Rules: Oas3RuleSet[] = []; // default ruleset
254
255
  this.plugins.forEach((p) => p.preprocessors?.oas3 && oas3Rules.push(p.preprocessors.oas3));
255
256
  this.plugins.forEach((p) => p.rules?.oas3 && oas3Rules.push(p.rules.oas3));
256
257
  this.plugins.forEach((p) => p.decorators?.oas3 && oas3Rules.push(p.decorators.oas3));
257
258
  return oas3Rules;
258
259
  case OasMajorVersion.Version2:
260
+ // eslint-disable-next-line no-case-declarations
259
261
  const oas2Rules: Oas2RuleSet[] = []; // default ruleset
260
262
  this.plugins.forEach((p) => p.preprocessors?.oas2 && oas2Rules.push(p.preprocessors.oas2));
261
263
  this.plugins.forEach((p) => p.rules?.oas2 && oas2Rules.push(p.rules.oas2));
@@ -2,20 +2,24 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { RedoclyClient } from '../redocly';
4
4
  import { isEmptyObject, loadYaml, doesYamlFileExist } from '../utils';
5
+ import { parseYaml } from '../js-yaml';
5
6
  import { Config, DOMAINS } from './config';
6
7
  import { transformConfig } from './utils';
7
8
  import { resolveConfig } from './config-resolvers';
8
9
 
9
10
  import type { DeprecatedInRawConfig, RawConfig, Region } from './types';
11
+ import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
10
12
 
11
13
  async function addConfigMetadata({
12
14
  rawConfig,
13
15
  customExtends,
14
16
  configPath,
17
+ tokens,
15
18
  }: {
16
19
  rawConfig: RawConfig;
17
20
  customExtends?: string[];
18
21
  configPath?: string;
22
+ tokens?: RegionalTokenWithValidity[];
19
23
  }): Promise<Config> {
20
24
  if (customExtends !== undefined) {
21
25
  rawConfig.styleguide = rawConfig.styleguide || {};
@@ -25,10 +29,7 @@ async function addConfigMetadata({
25
29
  // rawConfig.styleguide = { extends: ['recommended'], recommendedFallback: true };
26
30
  }
27
31
 
28
- const redoclyClient = new RedoclyClient();
29
- const tokens = await redoclyClient.getTokens();
30
-
31
- if (tokens.length) {
32
+ if (tokens?.length) {
32
33
  if (!rawConfig.resolve) rawConfig.resolve = {};
33
34
  if (!rawConfig.resolve.http) rawConfig.resolve.http = {};
34
35
  rawConfig.resolve.http.headers = [...(rawConfig.resolve.http.headers ?? [])];
@@ -69,10 +70,15 @@ export async function loadConfig(
69
70
  if (typeof processRawConfig === 'function') {
70
71
  await processRawConfig(rawConfig);
71
72
  }
72
- return await addConfigMetadata({
73
+
74
+ const redoclyClient = new RedoclyClient();
75
+ const tokens = await redoclyClient.getTokens();
76
+
77
+ return addConfigMetadata({
73
78
  rawConfig,
74
79
  customExtends,
75
80
  configPath,
81
+ tokens,
76
82
  });
77
83
  }
78
84
 
@@ -102,3 +108,20 @@ export async function getConfig(configPath: string | undefined = findConfig()):
102
108
  throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
103
109
  }
104
110
  }
111
+
112
+ type CreateConfigOptions = {
113
+ extends?: string[];
114
+ tokens?: RegionalTokenWithValidity[];
115
+ };
116
+
117
+ export async function createConfig(
118
+ config: string | RawConfig,
119
+ options?: CreateConfigOptions
120
+ ): Promise<Config> {
121
+ return addConfigMetadata({
122
+ rawConfig: transformConfig(
123
+ typeof config === 'string' ? (parseYaml(config) as RawConfig) : config
124
+ ),
125
+ ...options,
126
+ });
127
+ }
@@ -44,7 +44,7 @@ export default {
44
44
  'no-example-value-and-externalValue': 'warn',
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'warn',
47
- 'no-servers-empty-enum': 'error',
47
+ 'no-server-variables-empty-enum': 'error',
48
48
  },
49
49
  oas3_1Rules: {
50
50
  'no-invalid-media-type-examples': 'warn',
@@ -54,6 +54,6 @@ export default {
54
54
  'no-example-value-and-externalValue': 'warn',
55
55
  'no-unused-components': 'warn',
56
56
  'no-undefined-server-variable': 'warn',
57
- 'no-servers-empty-enum': 'error',
57
+ 'no-server-variables-empty-enum': 'error',
58
58
  },
59
59
  } as PluginStyleguideConfig;
@@ -44,7 +44,7 @@ export default {
44
44
  'no-example-value-and-externalValue': 'error',
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'error',
47
- 'no-servers-empty-enum': 'error',
47
+ 'no-server-variables-empty-enum': 'error',
48
48
  },
49
49
  oas3_1Rules: {
50
50
  'no-invalid-media-type-examples': 'warn',
@@ -54,6 +54,6 @@ export default {
54
54
  'no-example-value-and-externalValue': 'error',
55
55
  'no-unused-components': 'warn',
56
56
  'no-undefined-server-variable': 'error',
57
- 'no-servers-empty-enum': 'error',
57
+ 'no-server-variables-empty-enum': 'error',
58
58
  },
59
59
  } as PluginStyleguideConfig;
@@ -1,6 +1,6 @@
1
- import { yellow } from 'colorette';
2
1
  import {
3
2
  assignExisting,
3
+ isTruthy,
4
4
  showErrorForDeprecatedField,
5
5
  showWarningForDeprecatedField,
6
6
  } from '../utils';
@@ -17,6 +17,7 @@ import type {
17
17
  ResolvedStyleguideConfig,
18
18
  RulesFields,
19
19
  } from './types';
20
+ import { logger, colorize } from '../logger';
20
21
 
21
22
  export function parsePresetName(presetName: string): { pluginId: string; configName: string } {
22
23
  if (presetName.indexOf('/') > -1) {
@@ -83,7 +84,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
83
84
  extendPaths: [],
84
85
  };
85
86
 
86
- for (let rulesConf of rulesConfList) {
87
+ for (const rulesConf of rulesConfList) {
87
88
  if (rulesConf.extends) {
88
89
  throw new Error(
89
90
  `'extends' is not supported in shared configs yet: ${JSON.stringify(rulesConf, null, 2)}.`
@@ -128,14 +129,14 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
128
129
  config.rawConfig?.styleguide?.extendPaths,
129
130
  ]
130
131
  .flat()
131
- .filter(Boolean) as string[];
132
+ .filter(isTruthy);
132
133
 
133
134
  const pluginPaths = [
134
135
  ...Object.values(config.apis).map((api) => api?.styleguide?.pluginPaths),
135
136
  config.rawConfig?.styleguide?.pluginPaths,
136
137
  ]
137
138
  .flat()
138
- .filter(Boolean) as string[];
139
+ .filter(isTruthy);
139
140
 
140
141
  return apiName
141
142
  ? new Config(
@@ -221,7 +222,7 @@ export function getUniquePlugins(plugins: Plugin[]): Plugin[] {
221
222
  results.push(p);
222
223
  seen.add(p.id);
223
224
  } else if (p.id) {
224
- process.stderr.write(`Duplicate plugin id "${yellow(p.id)}".\n`);
225
+ logger.warn(`Duplicate plugin id "${colorize.red(p.id)}".\n`);
225
226
  }
226
227
  }
227
228
  return results;
@@ -4,7 +4,7 @@ import { isRedoclyRegistryURL } from '../../redocly';
4
4
  import { Oas3Decorator, Oas2Decorator } from '../../visitors';
5
5
 
6
6
  export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
7
- let registryDependencies = new Set<string>();
7
+ const registryDependencies = new Set<string>();
8
8
 
9
9
  return {
10
10
  DefinitionRoot: {
package/src/env.ts ADDED
@@ -0,0 +1,5 @@
1
+ export const isBrowser =
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore
4
+ typeof window !== 'undefined' || typeof self !== 'undefined' || typeof process === 'undefined'; // main and worker thread
5
+ export const env = isBrowser ? {} : process.env || {};
@@ -1,7 +1,7 @@
1
- import { gray, red, options as colorOptions } from 'colorette';
2
1
  import * as yamlAst from 'yaml-ast-parser';
3
2
  import { unescapePointer } from '../ref-utils';
4
3
  import { LineColLocationObject, Loc, LocationObject } from '../walk';
4
+ import { colorize, colorOptions } from '../logger';
5
5
 
6
6
  type YAMLMapping = yamlAst.YAMLMapping & { kind: yamlAst.Kind.MAPPING };
7
7
  type YAMLMap = yamlAst.YamlMap & { kind: yamlAst.Kind.MAP };
@@ -36,17 +36,23 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
36
36
  if (skipLines > 0 && i >= endLineNum - skipLines) break;
37
37
  const line = lines[i - 1] || '';
38
38
  if (line !== '') currentPad = padSize(line);
39
- let startIdx = i === startLineNum ? start.col - 1 : currentPad;
40
- let endIdx = i === endLineNum ? end.col - 1 : line.length;
39
+ const startIdx = i === startLineNum ? start.col - 1 : currentPad;
40
+ const endIdx = i === endLineNum ? end.col - 1 : line.length;
41
41
 
42
- prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, red)]);
42
+ prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, colorize.red)]);
43
43
  if (!color) prefixedLines.push(['', underlineLine(line, startIdx, endIdx)]);
44
44
  }
45
45
 
46
46
  if (skipLines > 0) {
47
- prefixedLines.push([`…`, `${whitespace(currentPad)}${gray(`< ${skipLines} more lines >`)}`]);
47
+ prefixedLines.push([
48
+ `…`,
49
+ `${whitespace(currentPad)}${colorize.gray(`< ${skipLines} more lines >`)}`,
50
+ ]);
48
51
  // print last line
49
- prefixedLines.push([`${endLineNum}`, markLine(lines[endLineNum - 1], -1, end.col - 1, red)]);
52
+ prefixedLines.push([
53
+ `${endLineNum}`,
54
+ markLine(lines[endLineNum - 1], -1, end.col - 1, colorize.red),
55
+ ]);
50
56
 
51
57
  if (!color) prefixedLines.push(['', underlineLine(lines[endLineNum - 1], -1, end.col - 1)]);
52
58
  }
@@ -63,7 +69,7 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
63
69
  line: string,
64
70
  startIdx: number = -1,
65
71
  endIdx: number = +Infinity,
66
- variant = gray
72
+ variant = colorize.gray
67
73
  ) {
68
74
  if (!color) return line;
69
75
  if (!line) return line;
@@ -90,7 +96,7 @@ function printPrefixedLines(lines: [string, string][]): string {
90
96
  return existingLines
91
97
  .map(
92
98
  ([prefix, line]) =>
93
- gray(leftPad(padLen, prefix) + ' |') +
99
+ colorize.gray(leftPad(padLen, prefix) + ' |') +
94
100
  (line ? ' ' + limitLineLength(line.substring(dedentLen)) : '')
95
101
  )
96
102
  .join('\n');
@@ -99,7 +105,7 @@ function printPrefixedLines(lines: [string, string][]): string {
99
105
  function limitLineLength(line: string, maxLen: number = MAX_LINE_LENGTH) {
100
106
  const overflowLen = line.length - maxLen;
101
107
  if (overflowLen > 0) {
102
- const charsMoreText = gray(`...<${overflowLen} chars>`);
108
+ const charsMoreText = colorize.gray(`...<${overflowLen} chars>`);
103
109
  return line.substring(0, maxLen - charsMoreText.length) + charsMoreText;
104
110
  } else {
105
111
  return line;
@@ -1,20 +1,12 @@
1
1
  import * as path from 'path';
2
- import {
3
- options as colorOptions,
4
- gray,
5
- blue,
6
- bgRed,
7
- bgYellow,
8
- black,
9
- yellow,
10
- red,
11
- } from 'colorette';
2
+ import { colorOptions, colorize, logger } from '../logger';
3
+ import { output } from '../output';
12
4
 
13
5
  const coreVersion = require('../../package.json').version;
14
6
 
15
7
  import { NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject } from '../walk';
16
8
  import { getCodeframe, getLineColLocation } from './codeframes';
17
- import { env } from '../config';
9
+ import { env } from '../env';
18
10
 
19
11
  export type Totals = {
20
12
  errors: number;
@@ -27,13 +19,13 @@ const ERROR_MESSAGE = {
27
19
  };
28
20
 
29
21
  const BG_COLORS = {
30
- warn: (str: string) => bgYellow(black(str)),
31
- error: bgRed,
22
+ warn: (str: string) => colorize.bgYellow(colorize.black(str)),
23
+ error: colorize.bgRed,
32
24
  };
33
25
 
34
26
  const COLORS = {
35
- warn: yellow,
36
- error: red,
27
+ warn: colorize.yellow,
28
+ error: colorize.red,
37
29
  };
38
30
 
39
31
  const SEVERITY_NAMES = {
@@ -114,7 +106,7 @@ export function formatProblems(
114
106
  case 'codeframe':
115
107
  for (let i = 0; i < problems.length; i++) {
116
108
  const problem = problems[i];
117
- process.stderr.write(`${formatCodeframe(problem, i)}\n`);
109
+ logger.info(`${formatCodeframe(problem, i)}\n`);
118
110
  }
119
111
  break;
120
112
  case 'stylish': {
@@ -122,30 +114,30 @@ export function formatProblems(
122
114
  for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(
123
115
  groupedByFile
124
116
  )) {
125
- process.stderr.write(`${blue(path.relative(cwd, file))}:\n`);
117
+ logger.info(`${colorize.blue(path.relative(cwd, file))}:\n`);
126
118
 
127
119
  for (let i = 0; i < fileProblems.length; i++) {
128
120
  const problem = fileProblems[i];
129
- process.stderr.write(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
121
+ logger.info(`${formatStylish(problem, positionPad, ruleIdPad)}\n`);
130
122
  }
131
123
 
132
- process.stderr.write('\n');
124
+ logger.info('\n');
133
125
  }
134
126
  break;
135
127
  }
136
128
  case 'checkstyle': {
137
129
  const groupedByFile = groupByFiles(problems);
138
130
 
139
- process.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n');
140
- process.stdout.write('<checkstyle version="4.3">\n');
131
+ output.write('<?xml version="1.0" encoding="UTF-8"?>\n');
132
+ output.write('<checkstyle version="4.3">\n');
141
133
 
142
134
  for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
143
- process.stdout.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
135
+ output.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
144
136
  fileProblems.forEach(formatCheckstyle);
145
- process.stdout.write(`</file>\n`);
137
+ output.write(`</file>\n`);
146
138
  }
147
139
 
148
- process.stdout.write(`</checkstyle>\n`);
140
+ output.write(`</checkstyle>\n`);
149
141
  break;
150
142
  }
151
143
  case 'codeclimate':
@@ -154,8 +146,8 @@ export function formatProblems(
154
146
  }
155
147
 
156
148
  if (totalProblems - ignoredProblems > maxProblems) {
157
- process.stderr.write(
158
- `< ... ${totalProblems - maxProblems} more problems hidden > ${gray(
149
+ logger.info(
150
+ `< ... ${totalProblems - maxProblems} more problems hidden > ${colorize.gray(
159
151
  'increase with `--max-problems N`'
160
152
  )}\n`
161
153
  );
@@ -177,7 +169,7 @@ export function formatProblems(
177
169
  fingerprint: `${p.ruleId}${p.location.length > 0 ? '-' + p.location[0].pointer : ''}`,
178
170
  };
179
171
  });
180
- process.stdout.write(JSON.stringify(issues, null, 2));
172
+ output.write(JSON.stringify(issues, null, 2));
181
173
  }
182
174
 
183
175
  function outputJSON() {
@@ -185,7 +177,7 @@ export function formatProblems(
185
177
  totals,
186
178
  version,
187
179
  problems: problems.map((p) => {
188
- let problem = {
180
+ const problem = {
189
181
  ...p,
190
182
  location: p.location.map((location: any) => ({
191
183
  ...location,
@@ -211,7 +203,7 @@ export function formatProblems(
211
203
  return problem;
212
204
  }),
213
205
  };
214
- process.stdout.write(JSON.stringify(resultObject, null, 2));
206
+ output.write(JSON.stringify(resultObject, null, 2));
215
207
  }
216
208
 
217
209
  function getBgColor(problem: NormalizedProblem) {
@@ -227,7 +219,7 @@ export function formatProblems(
227
219
  const location = problem.location[0]; // TODO: support multiple locations
228
220
  const relativePath = path.relative(cwd, location.source.absoluteRef);
229
221
  const loc = getLineColLocation(location);
230
- const atPointer = location.pointer ? gray(`at ${location.pointer}`) : '';
222
+ const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
231
223
  const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
232
224
  return (
233
225
  `[${idx + 1}] ${bgColor(fileWithLoc)} ${atPointer}\n\n` +
@@ -236,7 +228,9 @@ export function formatProblems(
236
228
  getCodeframe(loc, color) +
237
229
  '\n\n' +
238
230
  formatFrom(cwd, problem.from) +
239
- `${SEVERITY_NAMES[problem.severity]} was generated by the ${blue(problem.ruleId)} rule.\n\n`
231
+ `${SEVERITY_NAMES[problem.severity]} was generated by the ${colorize.blue(
232
+ problem.ruleId
233
+ )} rule.\n\n`
240
234
  );
241
235
  }
242
236
 
@@ -257,7 +251,7 @@ export function formatProblems(
257
251
  const severity = problem.severity == 'warn' ? 'warning' : 'error';
258
252
  const message = xmlEscape(problem.message);
259
253
  const source = xmlEscape(problem.ruleId);
260
- process.stdout.write(
254
+ output.write(
261
255
  `<error line="${line}" column="${col}" severity="${severity}" message="${message}" source="${source}" />\n`
262
256
  );
263
257
  }
@@ -269,7 +263,7 @@ function formatFrom(cwd: string, location?: LocationObject) {
269
263
  const loc = getLineColLocation(location);
270
264
  const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
271
265
 
272
- return `referenced from ${blue(fileWithLoc)}\n\n`;
266
+ return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
273
267
  }
274
268
 
275
269
  function formatDidYouMean(problem: NormalizedProblem) {
@@ -320,6 +314,7 @@ const groupByFiles = (problems: NormalizedProblem[]) => {
320
314
  };
321
315
 
322
316
  function xmlEscape(s: string): string {
317
+ // eslint-disable-next-line no-control-regex
323
318
  return s.replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, (char) => {
324
319
  switch (char) {
325
320
  case '<':
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
- export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist } from './utils';
1
+ export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy } from './utils';
2
2
  export { Oas3_1Types } from './types/oas3_1';
3
3
  export { Oas3Types } from './types/oas3';
4
4
  export { Oas2Types } from './types/oas2';
5
5
  export { ConfigTypes } from './types/redocly-yaml';
6
- export {
6
+ export type {
7
7
  Oas3Definition,
8
8
  Oas3_1Definition,
9
9
  Oas3Components,
@@ -15,9 +15,10 @@ export {
15
15
  Oas3Tag,
16
16
  Oas3_1Webhooks,
17
17
  Referenced,
18
+ OasRef,
18
19
  } from './typings/openapi';
19
- export { Oas2Definition } from './typings/swagger';
20
- export { StatsAccumulator, StatsName } from './typings/common';
20
+ export type { Oas2Definition } from './typings/swagger';
21
+ export type { StatsAccumulator, StatsName } from './typings/common';
21
22
  export { normalizeTypes } from './types';
22
23
  export { Stats } from './rules/other/stats';
23
24
 
@@ -34,6 +35,7 @@ export {
34
35
  findConfig,
35
36
  CONFIG_FILE_NAMES,
36
37
  RuleSeverity,
38
+ createConfig,
37
39
  } from './config';
38
40
 
39
41
  export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
@@ -1,4 +1,5 @@
1
1
  // TODO: add a type for "types" https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/js-yaml/index.d.ts
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
3
  // @ts-ignore
3
4
  import { JSON_SCHEMA, types, LoadOptions, DumpOptions, load, dump } from 'js-yaml';
4
5