@redocly/cli 1.5.0 → 1.7.0

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.
@@ -29,6 +29,7 @@ import {
29
29
  sortTopLevelKeysForOas,
30
30
  getAndValidateFileExtension,
31
31
  writeToFileByExtension,
32
+ checkForDeprecatedOptions,
32
33
  } from '../utils';
33
34
  import { isObject, isString, keysOf } from '../js-utils';
34
35
  import {
@@ -65,7 +66,6 @@ export type JoinOptions = {
65
66
  'without-x-tag-groups'?: boolean;
66
67
  output?: string;
67
68
  config?: string;
68
- extends?: undefined;
69
69
  'lint-config'?: RuleSeverity;
70
70
  };
71
71
 
@@ -76,6 +76,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
76
76
  return exitWithError(`At least 2 apis should be provided. \n\n`);
77
77
  }
78
78
 
79
+ checkForDeprecatedOptions(argv, ['lint'] as Array<keyof JoinOptions>);
80
+
79
81
  const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
80
82
 
81
83
  const {
@@ -1,13 +1,10 @@
1
1
  import {
2
2
  Config,
3
- findConfig,
4
3
  formatProblems,
5
4
  getMergedConfig,
6
5
  getTotals,
7
6
  lint,
8
7
  lintConfig,
9
- makeDocumentFromString,
10
- stringifyYaml,
11
8
  } from '@redocly/openapi-core';
12
9
  import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
13
10
  import {
@@ -21,11 +18,13 @@ import {
21
18
  printLintTotals,
22
19
  printUnusedWarnings,
23
20
  } from '../utils';
24
- import type { OutputFormat, ProblemSeverity, RawConfig, RuleSeverity } from '@redocly/openapi-core';
25
- import type { CommandOptions, Skips, Totals } from '../types';
26
21
  import { blue, gray } from 'colorette';
27
22
  import { performance } from 'perf_hooks';
28
23
 
24
+ import type { OutputFormat, ProblemSeverity, Document, RuleSeverity } from '@redocly/openapi-core';
25
+ import type { ResolvedRefMap } from '@redocly/openapi-core/lib/resolve';
26
+ import type { CommandOptions, Skips, Totals } from '../types';
27
+
29
28
  export type LintOptions = {
30
29
  apis?: string[];
31
30
  'max-problems': number;
@@ -129,12 +128,10 @@ export function lintConfigCallback(
129
128
  return;
130
129
  }
131
130
 
132
- return async (config: RawConfig) => {
133
- const configPath = findConfig(argv.config) || '';
134
- const stringYaml = stringifyYaml(config);
135
- const configContent = makeDocumentFromString(stringYaml, configPath);
131
+ return async (document: Document, resolvedRefMap: ResolvedRefMap) => {
136
132
  const problems = await lintConfig({
137
- document: configContent,
133
+ document,
134
+ resolvedRefMap,
138
135
  severity: (argv['lint-config'] || 'warn') as ProblemSeverity,
139
136
  });
140
137
 
@@ -0,0 +1,23 @@
1
+ import { Product } from './types';
2
+
3
+ export const PRODUCT_PACKAGES = {
4
+ realm: '@redocly/realm',
5
+ 'redoc-revel': '@redocly/redoc-revel',
6
+ 'revel-reef': '@redocly/revel-reef',
7
+ 'redoc-reef': '@redocly/redoc-reef',
8
+ redoc: '@redocly/redoc',
9
+ revel: '@redocly/revel',
10
+ reef: '@redocly/reef',
11
+ };
12
+
13
+ export const PRODUCT_NAMES: { [key in Product]: string } = {
14
+ redoc: 'Redoc',
15
+ revel: 'Revel',
16
+ reef: 'Reef',
17
+ realm: 'Realm',
18
+ 'redoc-revel': 'Redoc + Revel',
19
+ 'redoc-reef': 'Redoc + Reef',
20
+ 'revel-reef': 'Revel + Reef',
21
+ };
22
+
23
+ export const PRODUCT_PLANS = ['pro', 'enterprise'] as const;
@@ -0,0 +1,58 @@
1
+ import path = require('path');
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { spawn } from 'child_process';
4
+ import { PRODUCT_NAMES, PRODUCT_PACKAGES } from './constants';
5
+
6
+ import type { PreviewProjectOptions, Product } from './types';
7
+
8
+ export const previewProject = async (args: PreviewProjectOptions) => {
9
+ const { plan, port } = args;
10
+ const projectDir = args['source-dir'];
11
+
12
+ const product = args.product || tryGetProductFromPackageJson(projectDir);
13
+
14
+ if (!isValidProduct(product)) {
15
+ process.stderr.write(`Invalid product ${product}`);
16
+ throw new Error(`Project preview launch failed`);
17
+ }
18
+
19
+ const productName = PRODUCT_NAMES[product];
20
+ const packageName = PRODUCT_PACKAGES[product];
21
+
22
+ process.stdout.write(`\nLaunching preview of ${productName} ${plan} using NPX\n\n`);
23
+
24
+ spawn('npx', ['-y', packageName, 'develop', `--plan=${plan}`, `--port=${port || 4000}`], {
25
+ stdio: 'inherit',
26
+ cwd: projectDir,
27
+ });
28
+ };
29
+
30
+ const isValidProduct = (product: string | undefined): product is Product => {
31
+ if (!product) {
32
+ return false;
33
+ }
34
+
35
+ return !!PRODUCT_NAMES[product as Product];
36
+ };
37
+
38
+ const tryGetProductFromPackageJson = (projectDir: string): Product => {
39
+ const packageJsonPath = path.join(process.cwd(), projectDir, 'package.json');
40
+
41
+ if (existsSync(packageJsonPath)) {
42
+ try {
43
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
44
+ const packageJsonDeps = packageJson.dependencies || {};
45
+
46
+ for (const [product, packageName] of Object.entries(PRODUCT_PACKAGES)) {
47
+ if (packageJsonDeps[packageName]) {
48
+ process.stdout.write(`\n${packageName} detected in project's 'package.json'`);
49
+ return product as Product;
50
+ }
51
+ }
52
+ } catch (error) {
53
+ process.stdout.write(`Invalid 'package.json': ${packageJsonPath}. Using Realm.`);
54
+ }
55
+ }
56
+
57
+ return 'realm';
58
+ };
@@ -0,0 +1,12 @@
1
+ import { PRODUCT_PACKAGES, PRODUCT_PLANS } from './constants';
2
+
3
+ export type Product = keyof typeof PRODUCT_PACKAGES;
4
+ export type ProductPlan = typeof PRODUCT_PLANS[number];
5
+
6
+ export type PreviewProjectOptions = {
7
+ product?: Product | string;
8
+ plan: ProductPlan | string;
9
+ port?: number;
10
+ 'source-dir': string;
11
+ config: string | undefined;
12
+ };
@@ -97,8 +97,8 @@ function isStartsWithComponents(node: string) {
97
97
  return node.startsWith(componentsPath);
98
98
  }
99
99
 
100
- function isNotYaml(filename: string) {
101
- return !(filename.endsWith('.yaml') || filename.endsWith('.yml'));
100
+ function isSupportedExtension(filename: string) {
101
+ return filename.endsWith('.yaml') || filename.endsWith('.yml') || filename.endsWith('.json');
102
102
  }
103
103
 
104
104
  function loadFile(fileName: string) {
@@ -138,7 +138,7 @@ function traverseDirectoryDeepCallback(
138
138
  directory: string,
139
139
  componentsFiles: object
140
140
  ) {
141
- if (isNotYaml(filename)) return;
141
+ if (!isSupportedExtension(filename)) return;
142
142
  const pathData = readYaml(filename);
143
143
  replace$Refs(pathData, directory, componentsFiles);
144
144
  writeToFileByExtension(pathData, filename);
package/src/index.ts CHANGED
@@ -19,6 +19,8 @@ import { version } from './update-version-notifier';
19
19
  import type { Arguments } from 'yargs';
20
20
  import type { OutputFormat, RuleSeverity } from '@redocly/openapi-core';
21
21
  import type { BuildDocsArgv } from './commands/build-docs/types';
22
+ import { previewProject } from './commands/preview-project';
23
+ import { PRODUCT_PLANS } from './commands/preview-project/constants';
22
24
 
23
25
  if (!('replaceAll' in String.prototype)) {
24
26
  require('core-js/actual/string/replace-all');
@@ -92,7 +94,7 @@ yargs
92
94
  )
93
95
  .command(
94
96
  'join [apis...]',
95
- 'Join definitions [experimental].',
97
+ 'Join multiple API descriptions into one [experimental].',
96
98
  (yargs) =>
97
99
  yargs
98
100
  .positional('apis', {
@@ -101,7 +103,7 @@ yargs
101
103
  demandOption: true,
102
104
  })
103
105
  .option({
104
- lint: { description: 'Lint definitions', type: 'boolean', default: false },
106
+ lint: { description: 'Lint descriptions', type: 'boolean', default: false, hidden: true },
105
107
  decorate: { description: 'Run decorators', type: 'boolean', default: false },
106
108
  preprocess: { description: 'Run preprocessors', type: 'boolean', default: false },
107
109
  'prefix-tags-with-info-prop': {
@@ -124,7 +126,7 @@ yargs
124
126
  type: 'boolean',
125
127
  },
126
128
  output: {
127
- describe: 'Output file',
129
+ description: 'Output file.',
128
130
  alias: 'o',
129
131
  type: 'string',
130
132
  },
@@ -228,7 +230,7 @@ yargs
228
230
  )
229
231
  .command(
230
232
  'lint [apis...]',
231
- 'Lint definition.',
233
+ 'Lint an API description.',
232
234
  (yargs) =>
233
235
  yargs.positional('apis', { array: true, type: 'string', demandOption: true }).option({
234
236
  format: {
@@ -287,10 +289,14 @@ yargs
287
289
  )
288
290
  .command(
289
291
  'bundle [apis...]',
290
- 'Bundle definition.',
292
+ 'Bundle a multi-file API description to a single file.',
291
293
  (yargs) =>
292
294
  yargs.positional('apis', { array: true, type: 'string', demandOption: true }).options({
293
- output: { type: 'string', alias: 'o' },
295
+ output: {
296
+ type: 'string',
297
+ description: 'Output file.',
298
+ alias: 'o',
299
+ },
294
300
  format: {
295
301
  description: 'Use a specific output format.',
296
302
  choices: ['stylish', 'codeframe', 'json', 'checkstyle'] as ReadonlyArray<OutputFormat>,
@@ -413,9 +419,41 @@ yargs
413
419
  })(argv);
414
420
  }
415
421
  )
422
+ .command(
423
+ 'preview',
424
+ 'Preview Redocly project using one of the product NPM packages.',
425
+ (yargs) =>
426
+ yargs.options({
427
+ product: {
428
+ type: 'string',
429
+ choices: ['redoc', 'revel', 'reef', 'realm', 'redoc-revel', 'redoc-reef', 'revel-reef'],
430
+ description:
431
+ "Product used to launch preview. Default is inferred from project's package.json or 'realm' is used.",
432
+ },
433
+ plan: {
434
+ type: 'string',
435
+ choices: PRODUCT_PLANS,
436
+ default: 'enterprise',
437
+ },
438
+ port: {
439
+ type: 'number',
440
+ description: 'Preview port.',
441
+ default: 4000,
442
+ },
443
+ 'source-dir': {
444
+ alias: 'd',
445
+ type: 'string',
446
+ description: 'Project directory.',
447
+ default: '.',
448
+ },
449
+ }),
450
+ (argv) => {
451
+ commandWrapper(previewProject)(argv);
452
+ }
453
+ )
416
454
  .command(
417
455
  'preview-docs [api]',
418
- 'Preview API reference docs for the specified definition.',
456
+ 'Preview API reference docs for an API description.',
419
457
  (yargs) =>
420
458
  yargs.positional('api', { type: 'string' }).options({
421
459
  port: {
@@ -519,7 +557,7 @@ yargs
519
557
  commandWrapper(handlerBuildCommand)(argv as Arguments<BuildDocsArgv>);
520
558
  }
521
559
  )
522
- .completion('completion', 'Generate completion script.')
560
+ .completion('completion', 'Generate autocomplete script for `redocly` command.')
523
561
  .demandCommand(1)
524
562
  .middleware([notifyUpdateCliVersion])
525
563
  .strict().argv;
package/src/types.ts CHANGED
@@ -8,6 +8,7 @@ import type { StatsOptions } from './commands/stats';
8
8
  import type { SplitOptions } from './commands/split';
9
9
  import type { PreviewDocsOptions } from './commands/preview-docs';
10
10
  import type { BuildDocsArgv } from './commands/build-docs/types';
11
+ import type { PreviewProjectOptions } from './commands/preview-project/types';
11
12
 
12
13
  export type Totals = {
13
14
  errors: number;
@@ -30,7 +31,9 @@ export type CommandOptions =
30
31
  | BundleOptions
31
32
  | LoginOptions
32
33
  | PreviewDocsOptions
33
- | BuildDocsArgv;
34
+ | BuildDocsArgv
35
+ | PreviewProjectOptions;
36
+
34
37
  export type Skips = {
35
38
  'skip-rule'?: string[];
36
39
  'skip-decorator'?: string[];
package/src/utils.ts CHANGED
@@ -17,14 +17,12 @@ import {
17
17
  stringifyYaml,
18
18
  isAbsoluteUrl,
19
19
  loadConfig,
20
- RawConfig,
21
20
  Region,
22
21
  Config,
23
22
  Oas3Definition,
24
23
  Oas2Definition,
25
24
  RedoclyClient,
26
25
  } from '@redocly/openapi-core';
27
- import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
28
26
  import {
29
27
  Totals,
30
28
  outputExtensions,
@@ -37,6 +35,9 @@ import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
37
35
  import { Arguments } from 'yargs';
38
36
  import { version } from './update-version-notifier';
39
37
  import { DESTINATION_REGEX } from './commands/push';
38
+ import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
39
+
40
+ import type { RawConfigProcessor } from '@redocly/openapi-core/lib/config';
40
41
 
41
42
  export async function getFallbackApisOrExit(
42
43
  argsApis: string[] | undefined,
@@ -145,6 +146,24 @@ export function langToExt(lang: string) {
145
146
  javascript: '.js',
146
147
  js: '.js',
147
148
  python: '.py',
149
+ c: '.c',
150
+ 'c++': '.cpp',
151
+ coffeescript: '.litcoffee',
152
+ dart: '.dart',
153
+ elixir: '.ex',
154
+ go: '.go',
155
+ groovy: '.groovy',
156
+ java: '.java',
157
+ kotlin: '.kt',
158
+ 'objective-c': '.m',
159
+ perl: '.pl',
160
+ powershell: '.ps1',
161
+ ruby: '.rb',
162
+ rust: '.rs',
163
+ scala: '.sc',
164
+ swift: '.swift',
165
+ typescript: '.ts',
166
+ tsx: '.tsx',
148
167
  };
149
168
  return langObj[lang.toLowerCase()];
150
169
  }
@@ -425,7 +444,7 @@ export async function loadConfigAndHandleErrors(
425
444
  options: {
426
445
  configPath?: string;
427
446
  customExtends?: string[];
428
- processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
447
+ processRawConfig?: RawConfigProcessor;
429
448
  files?: string[];
430
449
  region?: Region;
431
450
  } = {}
@@ -626,3 +645,17 @@ export function cleanArgs(args: CommandOptions) {
626
645
  export function cleanRawInput(argv: string[]) {
627
646
  return argv.map((entry) => entry.split('=').map(cleanString).join('=')).join(' ');
628
647
  }
648
+
649
+ export function checkForDeprecatedOptions<T>(argv: T, deprecatedOptions: Array<keyof T>) {
650
+ for (const option of deprecatedOptions) {
651
+ if (argv[option]) {
652
+ process.stderr.write(
653
+ yellow(
654
+ `[WARNING] "${String(
655
+ option
656
+ )}" option is deprecated and will be removed in a future release. \n\n`
657
+ )
658
+ );
659
+ }
660
+ }
661
+ }