@redocly/cli 1.18.1 → 1.19.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/lib/__mocks__/@redocly/openapi-core.d.ts +2 -2
  3. package/lib/__mocks__/@redocly/openapi-core.js +1 -0
  4. package/lib/__mocks__/fs.d.ts +0 -1
  5. package/lib/__mocks__/perf_hooks.d.ts +0 -1
  6. package/lib/__mocks__/redoc.d.ts +0 -1
  7. package/lib/__tests__/commands/build-docs.test.js +21 -23
  8. package/lib/__tests__/commands/bundle.test.js +21 -30
  9. package/lib/__tests__/commands/join.test.js +101 -70
  10. package/lib/__tests__/commands/lint.test.js +54 -54
  11. package/lib/__tests__/commands/push-region.test.js +24 -25
  12. package/lib/__tests__/commands/push.test.js +269 -170
  13. package/lib/__tests__/fetch-with-timeout.test.js +3 -12
  14. package/lib/__tests__/fixtures/config.d.ts +0 -1
  15. package/lib/__tests__/utils.test.js +32 -37
  16. package/lib/__tests__/wrapper.test.js +31 -20
  17. package/lib/cms/api/__tests__/api.client.test.js +29 -38
  18. package/lib/cms/api/api-client.d.ts +0 -2
  19. package/lib/cms/api/api-client.js +106 -127
  20. package/lib/cms/api/api-keys.js +1 -2
  21. package/lib/cms/api/domains.js +1 -2
  22. package/lib/cms/commands/__tests__/push-status.test.js +251 -162
  23. package/lib/cms/commands/__tests__/push.test.js +120 -102
  24. package/lib/cms/commands/__tests__/utils.test.js +12 -21
  25. package/lib/cms/commands/push-status.d.ts +3 -2
  26. package/lib/cms/commands/push-status.js +94 -106
  27. package/lib/cms/commands/push.d.ts +3 -2
  28. package/lib/cms/commands/push.js +66 -74
  29. package/lib/cms/commands/utils.js +20 -34
  30. package/lib/commands/build-docs/index.d.ts +2 -2
  31. package/lib/commands/build-docs/index.js +8 -17
  32. package/lib/commands/build-docs/utils.js +26 -38
  33. package/lib/commands/bundle.d.ts +2 -2
  34. package/lib/commands/bundle.js +70 -94
  35. package/lib/commands/join.d.ts +2 -2
  36. package/lib/commands/join.js +375 -388
  37. package/lib/commands/lint.d.ts +2 -2
  38. package/lib/commands/lint.js +64 -75
  39. package/lib/commands/login.d.ts +3 -2
  40. package/lib/commands/login.js +9 -21
  41. package/lib/commands/preview-docs/index.d.ts +2 -2
  42. package/lib/commands/preview-docs/index.js +92 -106
  43. package/lib/commands/preview-docs/preview-server/preview-server.js +64 -76
  44. package/lib/commands/preview-docs/preview-server/server.d.ts +0 -3
  45. package/lib/commands/preview-docs/preview-server/server.js +6 -6
  46. package/lib/commands/preview-project/index.d.ts +2 -1
  47. package/lib/commands/preview-project/index.js +5 -14
  48. package/lib/commands/push.d.ts +8 -11
  49. package/lib/commands/push.js +177 -195
  50. package/lib/commands/split/__tests__/index.test.js +31 -25
  51. package/lib/commands/split/index.d.ts +2 -1
  52. package/lib/commands/split/index.js +20 -33
  53. package/lib/commands/stats.d.ts +2 -2
  54. package/lib/commands/stats.js +34 -45
  55. package/lib/index.js +32 -46
  56. package/lib/types.d.ts +2 -2
  57. package/lib/utils/__mocks__/miscellaneous.d.ts +0 -1
  58. package/lib/utils/fetch-with-timeout.js +7 -12
  59. package/lib/utils/getCommandNameFromArgs.js +2 -4
  60. package/lib/utils/js-utils.js +6 -7
  61. package/lib/utils/miscellaneous.d.ts +4 -1
  62. package/lib/utils/miscellaneous.js +130 -152
  63. package/lib/utils/update-version-notifier.js +4 -13
  64. package/lib/wrapper.d.ts +9 -2
  65. package/lib/wrapper.js +27 -16
  66. package/package.json +3 -3
  67. package/src/__mocks__/@redocly/openapi-core.ts +1 -0
  68. package/src/__tests__/commands/build-docs.test.ts +5 -4
  69. package/src/__tests__/commands/join.test.ts +51 -51
  70. package/src/__tests__/commands/push-region.test.ts +10 -8
  71. package/src/__tests__/commands/push.test.ts +127 -102
  72. package/src/__tests__/utils.test.ts +1 -0
  73. package/src/__tests__/wrapper.test.ts +24 -2
  74. package/src/cms/commands/__tests__/push-status.test.ts +70 -56
  75. package/src/cms/commands/__tests__/push.test.ts +30 -24
  76. package/src/cms/commands/push-status.ts +8 -7
  77. package/src/cms/commands/push.ts +12 -9
  78. package/src/commands/build-docs/index.ts +10 -5
  79. package/src/commands/bundle.ts +14 -6
  80. package/src/commands/join.ts +6 -2
  81. package/src/commands/lint.ts +9 -3
  82. package/src/commands/login.ts +4 -2
  83. package/src/commands/preview-docs/index.ts +6 -1
  84. package/src/commands/preview-project/index.ts +5 -4
  85. package/src/commands/push.ts +13 -15
  86. package/src/commands/split/__tests__/index.test.ts +17 -6
  87. package/src/commands/split/index.ts +4 -2
  88. package/src/commands/stats.ts +4 -2
  89. package/src/utils/miscellaneous.ts +11 -1
  90. package/src/wrapper.ts +37 -11
  91. package/tsconfig.tsbuildinfo +1 -1
@@ -2,7 +2,6 @@ import * as path from 'path';
2
2
  import { red, blue, yellow, green } from 'colorette';
3
3
  import { performance } from 'perf_hooks';
4
4
  import {
5
- Config,
6
5
  SpecVersion,
7
6
  BaseResolver,
8
7
  formatProblems,
@@ -38,6 +37,7 @@ import type {
38
37
  Oas3Server,
39
38
  Oas3_1Definition,
40
39
  } from '@redocly/openapi-core/lib/typings/openapi';
40
+ import type { CommandArgs } from '../wrapper';
41
41
 
42
42
  const Tags = 'tags';
43
43
  const xTagGroups = 'x-tagGroups';
@@ -64,7 +64,11 @@ export type JoinOptions = {
64
64
  'lint-config'?: RuleSeverity;
65
65
  };
66
66
 
67
- export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
67
+ export async function handleJoin({
68
+ argv,
69
+ config,
70
+ version: packageVersion,
71
+ }: CommandArgs<JoinOptions>) {
68
72
  const startedAt = performance.now();
69
73
 
70
74
  if (argv.apis.length < 2) {
@@ -1,7 +1,6 @@
1
1
  import { blue, gray } from 'colorette';
2
2
  import { performance } from 'perf_hooks';
3
3
  import {
4
- Config,
5
4
  formatProblems,
6
5
  getMergedConfig,
7
6
  getTotals,
@@ -23,10 +22,11 @@ import {
23
22
  } from '../utils/miscellaneous';
24
23
  import { getCommandNameFromArgs } from '../utils/getCommandNameFromArgs';
25
24
 
25
+ import type { Arguments } from 'yargs';
26
26
  import type { OutputFormat, ProblemSeverity, RuleSeverity } from '@redocly/openapi-core';
27
27
  import type { RawConfigProcessor } from '@redocly/openapi-core/lib/config';
28
28
  import type { CommandOptions, Skips, Totals } from '../types';
29
- import type { Arguments } from 'yargs';
29
+ import type { CommandArgs } from '../wrapper';
30
30
 
31
31
  export type LintOptions = {
32
32
  apis?: string[];
@@ -38,7 +38,12 @@ export type LintOptions = {
38
38
  'lint-config'?: RuleSeverity;
39
39
  } & Omit<Skips, 'skip-decorator'>;
40
40
 
41
- export async function handleLint(argv: LintOptions, config: Config, version: string) {
41
+ export async function handleLint({
42
+ argv,
43
+ config,
44
+ version,
45
+ collectSpecData,
46
+ }: CommandArgs<LintOptions>) {
42
47
  const apis = await getFallbackApisOrExit(argv.apis, config);
43
48
 
44
49
  if (!apis.length) {
@@ -74,6 +79,7 @@ export async function handleLint(argv: LintOptions, config: Config, version: str
74
79
  const results = await lint({
75
80
  ref: path,
76
81
  config: resolvedConfig,
82
+ collectSpecData,
77
83
  });
78
84
 
79
85
  const fileTotals = getTotals(results);
@@ -1,7 +1,9 @@
1
- import { Region, RedoclyClient, Config } from '@redocly/openapi-core';
1
+ import { Region, RedoclyClient } from '@redocly/openapi-core';
2
2
  import { blue, green, gray } from 'colorette';
3
3
  import { promptUser } from '../utils/miscellaneous';
4
4
 
5
+ import type { CommandArgs } from '../wrapper';
6
+
5
7
  export function promptClientToken(domain: string) {
6
8
  return promptUser(
7
9
  green(
@@ -17,7 +19,7 @@ export type LoginOptions = {
17
19
  config?: string;
18
20
  };
19
21
 
20
- export async function handleLogin(argv: LoginOptions, config: Config) {
22
+ export async function handleLogin({ argv, config }: CommandArgs<LoginOptions>) {
21
23
  const region = argv.region || config.region;
22
24
  const client = new RedoclyClient(region);
23
25
  const clientToken = await promptClientToken(client.domain);
@@ -7,7 +7,9 @@ import {
7
7
  loadConfigAndHandleErrors,
8
8
  } from '../../utils/miscellaneous';
9
9
  import startPreviewServer from './preview-server/preview-server';
10
+
10
11
  import type { Skips } from '../../types';
12
+ import type { CommandArgs } from '../../wrapper';
11
13
 
12
14
  export type PreviewDocsOptions = {
13
15
  port: number;
@@ -18,7 +20,10 @@ export type PreviewDocsOptions = {
18
20
  force?: boolean;
19
21
  } & Omit<Skips, 'skip-rule'>;
20
22
 
21
- export async function previewDocs(argv: PreviewDocsOptions, configFromFile: Config) {
23
+ export async function previewDocs({
24
+ argv,
25
+ config: configFromFile,
26
+ }: CommandArgs<PreviewDocsOptions>) {
22
27
  let isAuthorizedWithRedocly = false;
23
28
  let redocOptions: any = {};
24
29
  let config = await reloadConfig(configFromFile);
@@ -4,12 +4,13 @@ import { spawn } from 'child_process';
4
4
  import { PRODUCT_NAMES, PRODUCT_PACKAGES } from './constants';
5
5
 
6
6
  import type { PreviewProjectOptions, Product } from './types';
7
+ import type { CommandArgs } from '../../wrapper';
7
8
 
8
- export const previewProject = async (args: PreviewProjectOptions) => {
9
- const { plan, port } = args;
10
- const projectDir = args['source-dir'];
9
+ export const previewProject = async ({ argv }: CommandArgs<PreviewProjectOptions>) => {
10
+ const { plan, port } = argv;
11
+ const projectDir = argv['source-dir'];
11
12
 
12
- const product = args.product || tryGetProductFromPackageJson(projectDir);
13
+ const product = argv.product || tryGetProductFromPackageJson(projectDir);
13
14
 
14
15
  if (!isValidProduct(product)) {
15
16
  process.stderr.write(`Invalid product ${product}`);
@@ -26,6 +26,8 @@ import {
26
26
  import { promptClientToken } from './login';
27
27
  import { handlePush as handleCMSPush } from '../cms/commands/push';
28
28
 
29
+ import type { CommandArgs } from '../wrapper';
30
+
29
31
  const DEFAULT_VERSION = 'latest';
30
32
 
31
33
  export const DESTINATION_REGEX =
@@ -59,7 +61,7 @@ export function commonPushHandler({
59
61
  return transformPush(handlePush);
60
62
  }
61
63
 
62
- export async function handlePush(argv: PushOptions, config: Config): Promise<void> {
64
+ export async function handlePush({ argv, config }: CommandArgs<PushOptions>): Promise<void> {
63
65
  const client = new RedoclyClient(config.region);
64
66
  const isAuthorized = await client.isAuthorizedWithRedoclyByRegion();
65
67
  if (!isAuthorized) {
@@ -366,16 +368,11 @@ type BarePushArgs = Omit<PushOptions, 'destination' | 'branchName'> & {
366
368
 
367
369
  export const transformPush =
368
370
  (callback: typeof handlePush) =>
369
- (
370
- {
371
- apis,
372
- branch,
373
- 'batch-id': batchId,
374
- 'job-id': jobId,
375
- ...rest
376
- }: BarePushArgs & { 'batch-id'?: string },
377
- config: Config
378
- ) => {
371
+ ({
372
+ argv: { apis, branch, 'batch-id': batchId, 'job-id': jobId, ...rest },
373
+ config,
374
+ version,
375
+ }: CommandArgs<BarePushArgs & { 'batch-id'?: string }>) => {
379
376
  const [maybeApiOrDestination, maybeDestination, maybeBranchName] = apis || [];
380
377
 
381
378
  if (batchId) {
@@ -414,16 +411,17 @@ export const transformPush =
414
411
  apiFile = maybeApiOrDestination;
415
412
  }
416
413
 
417
- return callback(
418
- {
414
+ return callback({
415
+ argv: {
419
416
  ...rest,
420
417
  destination: rest.destination ?? destination,
421
418
  api: apiFile,
422
419
  branchName: branch ?? maybeBranchName,
423
420
  'job-id': jobId || batchId,
424
421
  },
425
- config
426
- );
422
+ config,
423
+ version,
424
+ });
427
425
  };
428
426
 
429
427
  export function getApiRoot({
@@ -3,6 +3,9 @@ import * as path from 'path';
3
3
  import * as openapiCore from '@redocly/openapi-core';
4
4
  import { ComponentsFiles } from '../types';
5
5
  import { blue, green } from 'colorette';
6
+ import { loadConfigAndHandleErrors } from '../../../utils/__mocks__/miscellaneous';
7
+
8
+ import type { Config } from '@redocly/openapi-core';
6
9
 
7
10
  const utils = require('../../../utils/miscellaneous');
8
11
 
@@ -25,9 +28,13 @@ describe('#split', () => {
25
28
  jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
26
29
 
27
30
  await handleSplit({
28
- api: filePath,
29
- outDir: openapiDir,
30
- separator: '_',
31
+ argv: {
32
+ api: filePath,
33
+ outDir: openapiDir,
34
+ separator: '_',
35
+ },
36
+ config: loadConfigAndHandleErrors() as any as Config,
37
+ version: 'cli-version',
31
38
  });
32
39
 
33
40
  expect(process.stderr.write).toBeCalledTimes(2);
@@ -46,9 +53,13 @@ describe('#split', () => {
46
53
  jest.spyOn(utils, 'pathToFilename').mockImplementation(() => 'newFilePath');
47
54
 
48
55
  await handleSplit({
49
- api: filePath,
50
- outDir: openapiDir,
51
- separator: '_',
56
+ argv: {
57
+ api: filePath,
58
+ outDir: openapiDir,
59
+ separator: '_',
60
+ },
61
+ config: loadConfigAndHandleErrors() as any as Config,
62
+ version: 'cli-version',
52
63
  });
53
64
 
54
65
  expect(utils.pathToFilename).toBeCalledWith(expect.anything(), '_');
@@ -36,6 +36,7 @@ import type {
36
36
  Oas3PathItem,
37
37
  Referenced,
38
38
  } from './types';
39
+ import type { CommandArgs } from '../../wrapper';
39
40
 
40
41
  export type SplitOptions = {
41
42
  api: string;
@@ -44,12 +45,13 @@ export type SplitOptions = {
44
45
  config?: string;
45
46
  };
46
47
 
47
- export async function handleSplit(argv: SplitOptions) {
48
+ export async function handleSplit({ argv, collectSpecData }: CommandArgs<SplitOptions>) {
48
49
  const startedAt = performance.now();
49
50
  const { api, outDir, separator } = argv;
50
51
  validateDefinitionFileName(api!);
51
52
  const ext = getAndValidateFileExtension(api);
52
53
  const openapi = readYaml(api!) as Oas3Definition | Oas3_1Definition;
54
+ collectSpecData?.(openapi);
53
55
  splitDefinition(openapi, outDir, separator, ext);
54
56
  process.stderr.write(
55
57
  `🪓 Document: ${blue(api!)} ${green('is successfully split')}
@@ -292,7 +294,7 @@ function iteratePathItems(
292
294
 
293
295
  for (const pathName of Object.keys(pathItems)) {
294
296
  const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.${ext}`;
295
- const pathData = pathItems[pathName] as Oas3PathItem;
297
+ const pathData = pathItems[pathName];
296
298
 
297
299
  if (isRef(pathData)) continue;
298
300
 
@@ -1,7 +1,6 @@
1
1
  import { performance } from 'perf_hooks';
2
2
  import * as colors from 'colorette';
3
3
  import {
4
- Config,
5
4
  StyleguideConfig,
6
5
  normalizeTypes,
7
6
  BaseResolver,
@@ -15,7 +14,9 @@ import {
15
14
  } from '@redocly/openapi-core';
16
15
  import { getFallbackApisOrExit } from '../utils/miscellaneous';
17
16
  import { printExecutionTime } from '../utils/miscellaneous';
17
+
18
18
  import type { StatsAccumulator, StatsName, WalkContext, OutputFormat } from '@redocly/openapi-core';
19
+ import type { CommandArgs } from '../wrapper';
19
20
 
20
21
  const statsAccumulator: StatsAccumulator = {
21
22
  refs: { metric: '🚗 References', total: 0, color: 'red', items: new Set() },
@@ -86,10 +87,11 @@ export type StatsOptions = {
86
87
  config?: string;
87
88
  };
88
89
 
89
- export async function handleStats(argv: StatsOptions, config: Config) {
90
+ export async function handleStats({ argv, config, collectSpecData }: CommandArgs<StatsOptions>) {
90
91
  const [{ path }] = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
91
92
  const externalRefResolver = new BaseResolver(config.resolve);
92
93
  const { bundle: document } = await bundle({ config, ref: path });
94
+ collectSpecData?.(document.parsed);
93
95
  const lintConfig: StyleguideConfig = config.styleguide;
94
96
  const specVersion = detectSpec(document.parsed);
95
97
  const types = normalizeTypes(
@@ -522,6 +522,7 @@ export function checkIfRulesetExist(rules: typeof StyleguideConfig.prototype.rul
522
522
  ...rules.oas3_0,
523
523
  ...rules.oas3_1,
524
524
  ...rules.async2,
525
+ ...rules.async3,
525
526
  ...rules.arazzo,
526
527
  };
527
528
 
@@ -540,7 +541,10 @@ export function cleanColors(input: string): string {
540
541
  export async function sendTelemetry(
541
542
  argv: Arguments | undefined,
542
543
  exit_code: ExitCode,
543
- has_config: boolean | undefined
544
+ has_config: boolean | undefined,
545
+ spec_version: string | undefined,
546
+ spec_keyword: string | undefined,
547
+ spec_full_version: string | undefined
544
548
  ): Promise<void> {
545
549
  try {
546
550
  if (!argv) {
@@ -568,6 +572,9 @@ export async function sendTelemetry(
568
572
  environment_ci: process.env.CI,
569
573
  raw_input: cleanRawInput(process.argv.slice(2)),
570
574
  has_config,
575
+ spec_version,
576
+ spec_keyword,
577
+ spec_full_version,
571
578
  };
572
579
  await fetch(`https://api.redocly.com/registry/telemetry/cli`, {
573
580
  method: 'POST',
@@ -597,6 +604,9 @@ export type Analytics = {
597
604
  environment_ci?: string;
598
605
  raw_input: string;
599
606
  has_config?: boolean;
607
+ spec_version?: string;
608
+ spec_keyword?: string;
609
+ spec_full_version?: string;
600
610
  };
601
611
 
602
612
  function isFile(value: string) {
package/src/wrapper.ts CHANGED
@@ -1,22 +1,48 @@
1
- import { Config, Region, doesYamlFileExist } from '@redocly/openapi-core';
2
- import type { Arguments } from 'yargs';
1
+ import { detectSpec, doesYamlFileExist } from '@redocly/openapi-core';
2
+ import { isPlainObject } from '@redocly/openapi-core/lib/utils';
3
3
  import { version } from './utils/update-version-notifier';
4
- import {
5
- ExitCode,
6
- exitWithError,
7
- loadConfigAndHandleErrors,
8
- sendTelemetry,
9
- } from './utils/miscellaneous';
4
+ import { exitWithError, loadConfigAndHandleErrors, sendTelemetry } from './utils/miscellaneous';
10
5
  import { lintConfigCallback } from './commands/lint';
6
+
7
+ import type { Arguments } from 'yargs';
8
+ import type { Config, Region } from '@redocly/openapi-core';
9
+ import type { CollectFn } from '@redocly/openapi-core/lib/utils';
10
+ import type { ExitCode } from './utils/miscellaneous';
11
11
  import type { CommandOptions } from './types';
12
12
 
13
+ export type CommandArgs<T extends CommandOptions> = {
14
+ argv: T;
15
+ config: Config;
16
+ version: string;
17
+ collectSpecData?: CollectFn;
18
+ };
19
+
13
20
  export function commandWrapper<T extends CommandOptions>(
14
- commandHandler?: (argv: T, config: Config, version: string) => Promise<unknown>
21
+ commandHandler?: (wrapperArgs: CommandArgs<T>) => Promise<unknown>
15
22
  ) {
16
23
  return async (argv: Arguments<T>) => {
17
24
  let code: ExitCode = 2;
18
25
  let hasConfig;
19
26
  let telemetry;
27
+ let specVersion: string | undefined;
28
+ let specKeyword: string | undefined;
29
+ let specFullVersion: string | undefined;
30
+ const collectSpecData: CollectFn = (document) => {
31
+ specVersion = detectSpec(document);
32
+ if (!isPlainObject(document)) return;
33
+ specKeyword = document?.openapi
34
+ ? 'openapi'
35
+ : document?.swagger
36
+ ? 'swagger'
37
+ : document?.asyncapi
38
+ ? 'asyncapi'
39
+ : document?.arazzo
40
+ ? 'arazzo'
41
+ : undefined;
42
+ if (specKeyword) {
43
+ specFullVersion = document[specKeyword] as string;
44
+ }
45
+ };
20
46
  try {
21
47
  if (argv.config && !doesYamlFileExist(argv.config)) {
22
48
  exitWithError('Please provide a valid path to the configuration file.');
@@ -32,14 +58,14 @@ export function commandWrapper<T extends CommandOptions>(
32
58
  hasConfig = !config.styleguide.recommendedFallback;
33
59
  code = 1;
34
60
  if (typeof commandHandler === 'function') {
35
- await commandHandler(argv, config, version);
61
+ await commandHandler({ argv, config, version, collectSpecData });
36
62
  }
37
63
  code = 0;
38
64
  } catch (err) {
39
65
  // Do nothing
40
66
  } finally {
41
67
  if (process.env.REDOCLY_TELEMETRY !== 'off' && telemetry !== 'off') {
42
- await sendTelemetry(argv, code, hasConfig);
68
+ await sendTelemetry(argv, code, hasConfig, specVersion, specKeyword, specFullVersion);
43
69
  }
44
70
  process.once('beforeExit', () => {
45
71
  process.exit(code);