@redocly/cli 1.0.0-beta.96

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 (79) hide show
  1. package/README.md +39 -0
  2. package/bin/cli.js +3 -0
  3. package/lib/__mocks__/utils.d.ts +17 -0
  4. package/lib/__mocks__/utils.js +14 -0
  5. package/lib/__tests__/commands/bundle.test.d.ts +1 -0
  6. package/lib/__tests__/commands/bundle.test.js +92 -0
  7. package/lib/__tests__/commands/push-region.test.d.ts +1 -0
  8. package/lib/__tests__/commands/push-region.test.js +55 -0
  9. package/lib/__tests__/commands/push.test.d.ts +1 -0
  10. package/lib/__tests__/commands/push.test.js +153 -0
  11. package/lib/__tests__/utils.test.d.ts +1 -0
  12. package/lib/__tests__/utils.test.js +41 -0
  13. package/lib/assert-node-version.d.ts +1 -0
  14. package/lib/assert-node-version.js +10 -0
  15. package/lib/commands/bundle.d.ts +19 -0
  16. package/lib/commands/bundle.js +128 -0
  17. package/lib/commands/join.d.ts +7 -0
  18. package/lib/commands/join.js +421 -0
  19. package/lib/commands/lint.d.ts +11 -0
  20. package/lib/commands/lint.js +80 -0
  21. package/lib/commands/login.d.ts +6 -0
  22. package/lib/commands/login.js +28 -0
  23. package/lib/commands/preview-docs/index.d.ts +12 -0
  24. package/lib/commands/preview-docs/index.js +141 -0
  25. package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
  26. package/lib/commands/preview-docs/preview-server/hot.js +42 -0
  27. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  28. package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
  29. package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
  30. package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
  31. package/lib/commands/preview-docs/preview-server/server.js +85 -0
  32. package/lib/commands/push.d.ts +25 -0
  33. package/lib/commands/push.js +247 -0
  34. package/lib/commands/split/__tests__/index.test.d.ts +1 -0
  35. package/lib/commands/split/__tests__/index.test.js +70 -0
  36. package/lib/commands/split/index.d.ts +8 -0
  37. package/lib/commands/split/index.js +279 -0
  38. package/lib/commands/split/types.d.ts +37 -0
  39. package/lib/commands/split/types.js +52 -0
  40. package/lib/commands/stats.d.ts +5 -0
  41. package/lib/commands/stats.js +92 -0
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.js +269 -0
  44. package/lib/js-utils.d.ts +3 -0
  45. package/lib/js-utils.js +16 -0
  46. package/lib/types.d.ts +13 -0
  47. package/lib/types.js +5 -0
  48. package/lib/utils.d.ts +28 -0
  49. package/lib/utils.js +260 -0
  50. package/package.json +54 -0
  51. package/src/__mocks__/utils.ts +11 -0
  52. package/src/__tests__/commands/bundle.test.ts +120 -0
  53. package/src/__tests__/commands/push-region.test.ts +51 -0
  54. package/src/__tests__/commands/push.test.ts +156 -0
  55. package/src/__tests__/utils.test.ts +50 -0
  56. package/src/assert-node-version.ts +8 -0
  57. package/src/commands/bundle.ts +178 -0
  58. package/src/commands/join.ts +488 -0
  59. package/src/commands/lint.ts +110 -0
  60. package/src/commands/login.ts +19 -0
  61. package/src/commands/preview-docs/index.ts +188 -0
  62. package/src/commands/preview-docs/preview-server/default.hbs +24 -0
  63. package/src/commands/preview-docs/preview-server/hot.js +42 -0
  64. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  65. package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
  66. package/src/commands/preview-docs/preview-server/server.ts +91 -0
  67. package/src/commands/push.ts +355 -0
  68. package/src/commands/split/__tests__/fixtures/spec.json +70 -0
  69. package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
  70. package/src/commands/split/__tests__/index.test.ts +96 -0
  71. package/src/commands/split/index.ts +349 -0
  72. package/src/commands/split/types.ts +73 -0
  73. package/src/commands/stats.ts +115 -0
  74. package/src/index.ts +311 -0
  75. package/src/js-utils.ts +12 -0
  76. package/src/types.ts +13 -0
  77. package/src/utils.ts +300 -0
  78. package/tsconfig.json +9 -0
  79. package/tsconfig.tsbuildinfo +1 -0
package/src/index.ts ADDED
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env node
2
+
3
+ import './assert-node-version';
4
+ import * as yargs from 'yargs';
5
+ import { outputExtensions, regionChoices } from './types';
6
+ import { RedoclyClient, OutputFormat } from '@redocly/openapi-core';
7
+ import { previewDocs } from './commands/preview-docs';
8
+ import { handleStats } from './commands/stats';
9
+ import { handleSplit } from './commands/split';
10
+ import { handleJoin } from './commands/join';
11
+ import { handlePush, transformPush } from './commands/push';
12
+ import { handleLint } from './commands/lint';
13
+ import { handleBundle } from './commands/bundle';
14
+ import { handleLogin } from './commands/login';
15
+ const version = require('../package.json').version;
16
+
17
+ yargs
18
+ .version('version', 'Show version number.', version)
19
+ .help('help', 'Show help.')
20
+ .command(
21
+ 'stats [entrypoint]',
22
+ 'Gathering statistics for a document.',
23
+ (yargs) =>
24
+ yargs.positional('entrypoint', { type: 'string' }).option({
25
+ config: { description: 'Specify path to the config file.', type: 'string' },
26
+ format: {
27
+ description: 'Use a specific output format.',
28
+ choices: ['stylish', 'json'] as ReadonlyArray<OutputFormat>,
29
+ default: 'stylish' as OutputFormat,
30
+ },
31
+ }),
32
+ handleStats,
33
+ )
34
+ .command(
35
+ 'split [entrypoint]',
36
+ 'Split definition into a multi-file structure.',
37
+ (yargs) =>
38
+ yargs
39
+ .positional('entrypoint', {
40
+ description: 'API definition file that you want to split',
41
+ type: 'string',
42
+ })
43
+ .option({
44
+ outDir: {
45
+ description: 'Output directory where files will be saved.',
46
+ required: true,
47
+ type: 'string',
48
+ },
49
+ separator: {
50
+ description: 'File path separator used while splitting.',
51
+ required: false,
52
+ type: 'string',
53
+ default: '_',
54
+ },
55
+ })
56
+ .demandOption('entrypoint'),
57
+ handleSplit,
58
+ )
59
+ .command(
60
+ 'join [entrypoints...]',
61
+ 'Join definitions [experimental].',
62
+ (yargs) =>
63
+ yargs
64
+ .positional('entrypoints', {
65
+ array: true,
66
+ type: 'string',
67
+ demandOption: true,
68
+ })
69
+ .option({
70
+ lint: { description: 'Lint definitions', type: 'boolean', default: false },
71
+ 'prefix-tags-with-info-prop': {
72
+ description: 'Prefix tags with property value from info object.',
73
+ requiresArg: true,
74
+ type: 'string',
75
+ },
76
+ 'prefix-tags-with-filename': {
77
+ description: 'Prefix tags with property value from file name.',
78
+ type: 'boolean',
79
+ default: false,
80
+ },
81
+ 'prefix-components-with-info-prop': {
82
+ description: 'Prefix components with property value from info object.',
83
+ requiresArg: true,
84
+ type: 'string',
85
+ },
86
+ }),
87
+ (argv) => {
88
+ handleJoin(argv, version);
89
+ },
90
+ )
91
+ .command(
92
+ 'push [maybeEntrypointOrAliasOrDestination] [maybeDestination] [maybeBranchName]',
93
+ 'Push an API definition to the Redocly API registry.',
94
+ (yargs) =>
95
+ yargs
96
+ .positional('maybeEntrypointOrAliasOrDestination', { type: 'string' })
97
+ .positional('maybeDestination', { type: 'string' })
98
+ .positional('maybeBranchName', { type: 'string' })
99
+ .option({
100
+ branch: { type: 'string', alias: 'b' },
101
+ upsert: { type: 'boolean', alias: 'u' },
102
+ 'run-id': { type: 'string', requiresArg: true },
103
+ region: { description: 'Specify a region.', alias: 'r', choices: regionChoices },
104
+ 'skip-decorator': {
105
+ description: 'Ignore certain decorators.',
106
+ array: true,
107
+ type: 'string',
108
+ },
109
+ }),
110
+ transformPush(handlePush),
111
+ )
112
+ .command(
113
+ 'lint [entrypoints...]',
114
+ 'Lint definition.',
115
+ (yargs) =>
116
+ yargs.positional('entrypoints', { array: true, type: 'string', demandOption: true }).option({
117
+ format: {
118
+ description: 'Use a specific output format.',
119
+ choices: [
120
+ 'stylish',
121
+ 'codeframe',
122
+ 'json',
123
+ 'checkstyle',
124
+ 'codeclimate',
125
+ ] as ReadonlyArray<OutputFormat>,
126
+ default: 'codeframe' as OutputFormat,
127
+ },
128
+ 'max-problems': {
129
+ requiresArg: true,
130
+ description: 'Reduce output to max N problems.',
131
+ type: 'number',
132
+ default: 100,
133
+ },
134
+ 'generate-ignore-file': {
135
+ description: 'Generate ignore file.',
136
+ type: 'boolean',
137
+ },
138
+ 'skip-rule': {
139
+ description: 'Ignore certain rules.',
140
+ array: true,
141
+ type: 'string',
142
+ },
143
+ 'skip-preprocessor': {
144
+ description: 'Ignore certain preprocessors.',
145
+ array: true,
146
+ type: 'string',
147
+ },
148
+ config: {
149
+ description: 'Specify path to the config file.',
150
+ requiresArg: true,
151
+ type: 'string',
152
+ },
153
+ extends: {
154
+ description: 'Override extends configurations (defaults or config file settings).',
155
+ requiresArg: true,
156
+ array: true,
157
+ type: 'string',
158
+ },
159
+ }),
160
+ (argv) => {
161
+ handleLint(argv, version);
162
+ },
163
+ )
164
+ .command(
165
+ 'bundle [entrypoints...]',
166
+ 'Bundle definition.',
167
+ (yargs) =>
168
+ yargs.positional('entrypoints', { array: true, type: 'string', demandOption: true }).options({
169
+ output: { type: 'string', alias: 'o' },
170
+ format: {
171
+ description: 'Use a specific output format.',
172
+ choices: ['stylish', 'codeframe', 'json', 'checkstyle'] as ReadonlyArray<OutputFormat>,
173
+ default: 'codeframe' as OutputFormat,
174
+ },
175
+ 'max-problems': {
176
+ requiresArg: true,
177
+ description: 'Reduce output to max N problems.',
178
+ type: 'number',
179
+ default: 100,
180
+ },
181
+ ext: {
182
+ description: 'Bundle file extension.',
183
+ requiresArg: true,
184
+ choices: outputExtensions,
185
+ },
186
+ 'skip-rule': {
187
+ description: 'Ignore certain rules.',
188
+ array: true,
189
+ type: 'string',
190
+ },
191
+ 'skip-preprocessor': {
192
+ description: 'Ignore certain preprocessors.',
193
+ array: true,
194
+ type: 'string',
195
+ },
196
+ 'skip-decorator': {
197
+ description: 'Ignore certain decorators.',
198
+ array: true,
199
+ type: 'string',
200
+ },
201
+ dereferenced: {
202
+ alias: 'd',
203
+ type: 'boolean',
204
+ description: 'Produce fully dereferenced bundle.',
205
+ },
206
+ force: {
207
+ alias: 'f',
208
+ type: 'boolean',
209
+ description: 'Produce bundle output even when errors occur.',
210
+ },
211
+ config: {
212
+ description: 'Specify path to the config file.',
213
+ type: 'string',
214
+ },
215
+ lint: {
216
+ description: 'Lint definitions',
217
+ type: 'boolean',
218
+ default: false,
219
+ },
220
+ metafile: {
221
+ description: 'Produce metadata about the bundle',
222
+ type: 'string',
223
+ },
224
+ extends: {
225
+ description: 'Override extends configurations (defaults or config file settings).',
226
+ requiresArg: true,
227
+ array: true,
228
+ type: 'string',
229
+ },
230
+ 'remove-unused-components': {
231
+ description: 'Remove unused components.',
232
+ type: 'boolean',
233
+ default: false,
234
+ },
235
+ }),
236
+ (argv) => {
237
+ handleBundle(argv, version);
238
+ },
239
+ )
240
+ .command(
241
+ 'login',
242
+ 'Login to the Redocly API registry with an access token.',
243
+ async (yargs) =>
244
+ yargs.options({
245
+ verbose: {
246
+ description: 'Include additional output.',
247
+ type: 'boolean',
248
+ },
249
+ region: {
250
+ description: 'Specify a region.',
251
+ alias: 'r',
252
+ choices: regionChoices,
253
+ },
254
+ }),
255
+ handleLogin,
256
+ )
257
+ .command(
258
+ 'logout',
259
+ 'Clear your stored credentials for the Redocly API registry.',
260
+ (yargs) => yargs,
261
+ async () => {
262
+ const client = new RedoclyClient();
263
+ client.logout();
264
+ },
265
+ )
266
+ .command(
267
+ 'preview-docs [entrypoint]',
268
+ 'Preview API reference docs for the specified definition.',
269
+ (yargs) =>
270
+ yargs.positional('entrypoint', { type: 'string' }).options({
271
+ port: {
272
+ alias: 'p',
273
+ type: 'number',
274
+ default: 8080,
275
+ description: 'Preview port.',
276
+ },
277
+ host: {
278
+ alias: 'h',
279
+ type: 'string',
280
+ default: '127.0.0.1',
281
+ description: 'Preview host.',
282
+ },
283
+ 'skip-preprocessor': {
284
+ description: 'Ignore certain preprocessors.',
285
+ array: true,
286
+ type: 'string',
287
+ },
288
+ 'skip-decorator': {
289
+ description: 'Ignore certain decorators.',
290
+ array: true,
291
+ type: 'string',
292
+ },
293
+ 'use-community-edition': {
294
+ description: 'Force using Redoc CE for docs preview.',
295
+ type: 'boolean',
296
+ },
297
+ force: {
298
+ alias: 'f',
299
+ type: 'boolean',
300
+ description: 'Produce bundle output even when errors occur.',
301
+ },
302
+ config: {
303
+ description: 'Specify path to the config file.',
304
+ type: 'string',
305
+ },
306
+ }),
307
+ previewDocs,
308
+ )
309
+ .completion('completion', 'Generate completion script.')
310
+ .demandCommand(1)
311
+ .strict().argv;
@@ -0,0 +1,12 @@
1
+ export function isObject(obj: any) {
2
+ const type = typeof obj;
3
+ return type === 'function' || type === 'object' && !!obj;
4
+ }
5
+
6
+ export function isEmptyObject(obj: any) {
7
+ return !!obj && Object.keys(obj).length === 0;
8
+ }
9
+
10
+ export function isString(str: string) {
11
+ return Object.prototype.toString.call(str) === "[object String]";
12
+ }
package/src/types.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { BundleOutputFormat, Region } from '@redocly/openapi-core';
2
+ export type Totals = {
3
+ errors: number;
4
+ warnings: number;
5
+ ignored: number;
6
+ };
7
+ export type Entrypoint = {
8
+ path: string;
9
+ alias?: string;
10
+ };
11
+ export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
12
+ export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
13
+ export const regionChoices = ['us', 'eu'] as ReadonlyArray<Region>;
package/src/utils.ts ADDED
@@ -0,0 +1,300 @@
1
+ import { basename, dirname, extname, join, resolve } from 'path';
2
+ import { blue, gray, green, red, yellow } from 'colorette';
3
+ import { performance } from "perf_hooks";
4
+ import * as glob from 'glob-promise';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as readline from 'readline';
8
+ import { Writable } from 'stream';
9
+ import {
10
+ BundleOutputFormat,
11
+ Config,
12
+ LintConfig,
13
+ ResolveError,
14
+ YamlParseError,
15
+ parseYaml,
16
+ stringifyYaml
17
+ } from '@redocly/openapi-core';
18
+ import { Totals, outputExtensions, Entrypoint } from './types';
19
+
20
+ export async function getFallbackEntryPointsOrExit(argsEntrypoints: string[] | undefined, config: Config): Promise<Entrypoint[]> {
21
+ const { apis } = config;
22
+ const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsEntrypoints) && apis && Object.keys(apis).length > 0;
23
+ const res = shouldFallbackToAllDefinitions
24
+ ? Object.entries(apis).map(([alias, { root }]) => ({
25
+ path: resolve(getConfigDirectory(config), root),
26
+ alias,
27
+ }))
28
+ : (await expandGlobsInEntrypoints(argsEntrypoints!, config));
29
+ if (!isNotEmptyArray(res)) {
30
+ process.stderr.write('error: missing required argument `entrypoints`.\n');
31
+ process.exit(1);
32
+ }
33
+ return res;
34
+ }
35
+
36
+ function getConfigDirectory(config: Config) {
37
+ return config.configFile ? dirname(config.configFile) : process.cwd();
38
+ }
39
+
40
+ function isNotEmptyArray<T>(args?: T[]): boolean {
41
+ return Array.isArray(args) && !!args.length;
42
+ }
43
+
44
+ function getAliasOrPath(config: Config, aliasOrPath: string): Entrypoint {
45
+ return config.apis[aliasOrPath]
46
+ ? { path: config.apis[aliasOrPath]?.root, alias: aliasOrPath }
47
+ : { path: aliasOrPath };
48
+ }
49
+
50
+ async function expandGlobsInEntrypoints(args: string[], config: Config) {
51
+ return (await Promise.all((args as string[]).map(async aliasOrPath => {
52
+ return glob.hasMagic(aliasOrPath)
53
+ ? (await glob(aliasOrPath)).map((g: string) => getAliasOrPath(config, g))
54
+ : getAliasOrPath(config, aliasOrPath);
55
+ }))).flat();
56
+ }
57
+
58
+ export function getExecutionTime(startedAt: number) {
59
+ return process.env.NODE_ENV === 'test'
60
+ ? '<test>ms'
61
+ : `${Math.ceil(performance.now() - startedAt)}ms`;
62
+ }
63
+
64
+ export function printExecutionTime(commandName: string, startedAt: number, entrypoint: string) {
65
+ const elapsed = getExecutionTime(startedAt);
66
+ process.stderr.write(gray(`\n${entrypoint}: ${commandName} processed in ${elapsed}\n\n`));
67
+ }
68
+
69
+ export function pathToFilename(path: string, pathSeparator: string) {
70
+ return path
71
+ .replace(/~1/g, '/')
72
+ .replace(/~0/g, '~')
73
+ .replace(/^\//, '')
74
+ .replace(/\//g, pathSeparator);
75
+ }
76
+
77
+ export class CircularJSONNotSupportedError extends Error {
78
+ constructor(public originalError: Error) {
79
+ super(originalError.message);
80
+ // Set the prototype explicitly.
81
+ Object.setPrototypeOf(this, CircularJSONNotSupportedError.prototype);
82
+ }
83
+ }
84
+
85
+ export function dumpBundle(obj: any, format: BundleOutputFormat, dereference?: boolean): string {
86
+ if (format === 'json') {
87
+ try {
88
+ return JSON.stringify(obj, null, 2);
89
+ } catch (e) {
90
+ if (e.message.indexOf('circular') > -1) {
91
+ throw new CircularJSONNotSupportedError(e);
92
+ }
93
+ throw e;
94
+ }
95
+ } else {
96
+ return stringifyYaml(obj, {
97
+ noRefs: !dereference,
98
+ });
99
+ }
100
+ }
101
+
102
+ export function saveBundle(filename: string, output: string) {
103
+ fs.mkdirSync(path.dirname(filename), { recursive: true });
104
+ fs.writeFileSync(filename, output);
105
+ }
106
+
107
+ export async function promptUser(query: string, hideUserInput = false): Promise<string> {
108
+ return new Promise((resolve) => {
109
+ let output: Writable = process.stdout;
110
+ let isOutputMuted = false;
111
+
112
+ if (hideUserInput) {
113
+ output = new Writable({
114
+ write: (chunk, encoding, callback) => {
115
+ if (!isOutputMuted) {
116
+ process.stdout.write(chunk, encoding);
117
+ }
118
+ callback();
119
+ },
120
+ });
121
+ }
122
+
123
+ const rl = readline.createInterface({
124
+ input: process.stdin,
125
+ output,
126
+ terminal: true,
127
+ historySize: hideUserInput ? 0 : 30,
128
+ });
129
+
130
+ rl.question(`${query}:\n\n `, (answer) => {
131
+ rl.close();
132
+ resolve(answer);
133
+ });
134
+
135
+ isOutputMuted = hideUserInput;
136
+ });
137
+ }
138
+
139
+ export function readYaml(filename: string) {
140
+ return parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
141
+ }
142
+
143
+ export function writeYaml(data: any, filename: string, noRefs = false) {
144
+ const content = stringifyYaml(data, { noRefs });
145
+
146
+ if (process.env.NODE_ENV === 'test') {
147
+ process.stderr.write(content);
148
+ return;
149
+ }
150
+ fs.writeFileSync(filename, content);
151
+ }
152
+
153
+ export function pluralize(label: string, num: number) {
154
+ if (label.endsWith('is')) {
155
+ [label] = label.split(' ');
156
+ return num === 1 ? `${label} is` : `${label}s are`;
157
+ }
158
+ return num === 1 ? `${label}` : `${label}s`;
159
+ }
160
+
161
+ export function handleError(e: Error, ref: string) {
162
+ if (e instanceof ResolveError) {
163
+ process.stderr.write(
164
+ `Failed to resolve entrypoint definition at ${ref}:\n\n - ${e.message}.\n\n`,
165
+ );
166
+ } else if (e instanceof YamlParseError) {
167
+ process.stderr.write(
168
+ `Failed to parse entrypoint definition at ${ref}:\n\n - ${e.message}.\n\n`,
169
+ );
170
+ // TODO: codeframe
171
+ } else { // @ts-ignore
172
+ if (e instanceof CircularJSONNotSupportedError) {
173
+ process.stderr.write(
174
+ red(`Detected circular reference which can't be converted to JSON.\n`) +
175
+ `Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.\n\n`,
176
+ );
177
+ } else {
178
+ process.stderr.write(`Something went wrong when processing ${ref}:\n\n - ${e.message}.\n\n`);
179
+ throw e;
180
+ }
181
+ }
182
+ }
183
+
184
+ export function printLintTotals(totals: Totals, definitionsCount: number) {
185
+ const ignored = totals.ignored
186
+ ? yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
187
+ : '';
188
+
189
+ if (totals.errors > 0) {
190
+ process.stderr.write(
191
+ red(
192
+ `❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${
193
+ totals.warnings > 0
194
+ ? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
195
+ : ''
196
+ }.\n${ignored}`,
197
+ ),
198
+ );
199
+ } else if (totals.warnings > 0) {
200
+ process.stderr.write(
201
+ green(`Woohoo! Your OpenAPI ${pluralize('definition is', definitionsCount)} valid. 🎉\n`),
202
+ );
203
+ process.stderr.write(
204
+ yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`),
205
+ );
206
+ } else {
207
+ process.stderr.write(
208
+ green(
209
+ `Woohoo! Your OpenAPI ${pluralize(
210
+ 'definition is',
211
+ definitionsCount,
212
+ )} valid. 🎉\n${ignored}`,
213
+ ),
214
+ );
215
+ }
216
+
217
+ if (totals.errors > 0) {
218
+ process.stderr.write(
219
+ gray(`run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.\n`),
220
+ );
221
+ }
222
+
223
+ process.stderr.write('\n');
224
+ }
225
+
226
+ export function getOutputFileName(
227
+ entrypoint: string,
228
+ entries: number,
229
+ output?: string,
230
+ ext?: BundleOutputFormat,
231
+ ) {
232
+ if (!output) {
233
+ return { outputFile: 'stdout', ext: ext || 'yaml' };
234
+ }
235
+
236
+ let outputFile = output;
237
+ if (entries > 1) {
238
+ ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
239
+ if (!outputExtensions.includes(ext as any)) {
240
+ throw new Error(`Invalid file extension: ${ext}.`);
241
+ }
242
+ outputFile = join(output, basename(entrypoint, extname(entrypoint))) + '.' + ext;
243
+ } else {
244
+ if (output) {
245
+ ext = ext || (extname(output).substring(1) as BundleOutputFormat);
246
+ }
247
+ ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
248
+ if (!outputExtensions.includes(ext as any)) {
249
+ throw new Error(`Invalid file extension: ${ext}.`);
250
+ }
251
+ outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
252
+ }
253
+ return { outputFile, ext };
254
+ }
255
+
256
+ export function printUnusedWarnings(config: LintConfig) {
257
+ const { preprocessors, rules, decorators } = config.getUnusedRules();
258
+ if (rules.length) {
259
+ process.stderr.write(
260
+ yellow(
261
+ `[WARNING] Unused rules found in ${blue(config.configFile || '')}: ${rules.join(', ')}.\n`,
262
+ ),
263
+ );
264
+ }
265
+ if (preprocessors.length) {
266
+ process.stderr.write(
267
+ yellow(
268
+ `[WARNING] Unused preprocessors found in ${blue(
269
+ config.configFile || '',
270
+ )}: ${preprocessors.join(', ')}.\n`,
271
+ ),
272
+ );
273
+ }
274
+ if (decorators.length) {
275
+ process.stderr.write(
276
+ yellow(
277
+ `[WARNING] Unused decorators found in ${blue(config.configFile || '')}: ${decorators.join(
278
+ ', ',
279
+ )}.\n`,
280
+ ),
281
+ );
282
+ }
283
+
284
+ if (rules.length || preprocessors.length) {
285
+ process.stderr.write(`Check the spelling and verify you added plugin prefix.\n`);
286
+ }
287
+ }
288
+
289
+ export function exitWithError(message: string) {
290
+ process.stderr.write(red(message)+ '\n\n');
291
+ process.exit(1);
292
+ }
293
+
294
+ /**
295
+ * Checks if dir is subdir of parent
296
+ */
297
+ export function isSubdir(parent: string, dir: string): boolean {
298
+ const relative = path.relative(parent, dir);
299
+ return !!relative && !/^..($|\/)/.test(relative) && !path.isAbsolute(relative);
300
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "lib"
6
+ },
7
+ "references": [{ "path": "../core" }],
8
+ "exclude": ["lib"]
9
+ }