@redocly/cli 1.22.0 → 1.23.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 (51) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/lib/__tests__/commands/bundle.test.js +110 -1
  3. package/lib/__tests__/fetch-with-timeout.test.js +29 -5
  4. package/lib/__tests__/utils.test.js +54 -32
  5. package/lib/cms/api/__tests__/api.client.test.js +17 -9
  6. package/lib/cms/api/api-client.d.ts +26 -7
  7. package/lib/cms/api/api-client.js +103 -72
  8. package/lib/cms/commands/__tests__/push-status.test.js +1 -1
  9. package/lib/cms/commands/__tests__/push.test.js +41 -1
  10. package/lib/cms/commands/__tests__/utils.test.js +1 -1
  11. package/lib/cms/commands/push-status.d.ts +1 -1
  12. package/lib/cms/commands/push-status.js +3 -7
  13. package/lib/cms/commands/push.js +4 -4
  14. package/lib/cms/commands/utils.d.ts +3 -0
  15. package/lib/cms/commands/utils.js +8 -1
  16. package/lib/commands/bundle.d.ts +1 -1
  17. package/lib/commands/bundle.js +9 -9
  18. package/lib/commands/eject.d.ts +1 -1
  19. package/lib/commands/eject.js +1 -1
  20. package/lib/commands/preview-project/index.js +1 -1
  21. package/lib/index.js +1 -2
  22. package/lib/types.d.ts +1 -0
  23. package/lib/utils/__mocks__/miscellaneous.d.ts +1 -0
  24. package/lib/utils/__mocks__/miscellaneous.js +2 -1
  25. package/lib/utils/fetch-with-timeout.d.ts +6 -1
  26. package/lib/utils/fetch-with-timeout.js +16 -14
  27. package/lib/utils/miscellaneous.d.ts +4 -1
  28. package/lib/utils/miscellaneous.js +24 -29
  29. package/lib/utils/update-version-notifier.js +8 -4
  30. package/package.json +2 -2
  31. package/src/__tests__/commands/bundle.test.ts +131 -4
  32. package/src/__tests__/fetch-with-timeout.test.ts +36 -6
  33. package/src/__tests__/utils.test.ts +58 -33
  34. package/src/cms/api/__tests__/api.client.test.ts +20 -11
  35. package/src/cms/api/api-client.ts +158 -91
  36. package/src/cms/commands/__tests__/push-status.test.ts +1 -1
  37. package/src/cms/commands/__tests__/push.test.ts +49 -2
  38. package/src/cms/commands/__tests__/utils.test.ts +1 -1
  39. package/src/cms/commands/push-status.ts +5 -9
  40. package/src/cms/commands/push.ts +5 -6
  41. package/src/cms/commands/utils.ts +15 -1
  42. package/src/commands/bundle.ts +14 -12
  43. package/src/commands/eject.ts +2 -2
  44. package/src/commands/preview-project/index.ts +1 -1
  45. package/src/index.ts +1 -2
  46. package/src/types.ts +1 -0
  47. package/src/utils/__mocks__/miscellaneous.ts +1 -0
  48. package/src/utils/fetch-with-timeout.ts +23 -14
  49. package/src/utils/miscellaneous.ts +32 -37
  50. package/src/utils/update-version-notifier.ts +11 -5
  51. package/tsconfig.tsbuildinfo +1 -1
@@ -1,24 +1,33 @@
1
- import nodeFetch from 'node-fetch';
1
+ import nodeFetch, { type RequestInit } from 'node-fetch';
2
2
  import AbortController from 'abort-controller';
3
3
  import { getProxyAgent } from '@redocly/openapi-core';
4
4
 
5
- const TIMEOUT = 3000;
5
+ export const DEFAULT_FETCH_TIMEOUT = 3000;
6
6
 
7
- export default async (url: string, options = {}) => {
8
- try {
9
- const controller = new AbortController();
10
- const timeout = setTimeout(() => {
11
- controller.abort();
12
- }, TIMEOUT);
7
+ export type FetchWithTimeoutOptions = RequestInit & {
8
+ timeout?: number;
9
+ };
13
10
 
14
- const res = await nodeFetch(url, {
15
- signal: controller.signal,
11
+ export default async (url: string, { timeout, ...options }: FetchWithTimeoutOptions = {}) => {
12
+ if (!timeout) {
13
+ return nodeFetch(url, {
16
14
  ...options,
17
15
  agent: getProxyAgent(),
18
16
  });
19
- clearTimeout(timeout);
20
- return res;
21
- } catch (e) {
22
- return;
23
17
  }
18
+
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => {
21
+ controller.abort();
22
+ }, timeout);
23
+
24
+ const res = await nodeFetch(url, {
25
+ signal: controller.signal,
26
+ ...options,
27
+ agent: getProxyAgent(),
28
+ });
29
+
30
+ clearTimeout(timeoutId);
31
+
32
+ return res;
24
33
  };
@@ -16,13 +16,19 @@ import {
16
16
  loadConfig,
17
17
  RedoclyClient,
18
18
  } from '@redocly/openapi-core';
19
- import { isEmptyObject, isPlainObject, pluralize } from '@redocly/openapi-core/lib/utils';
19
+ import {
20
+ isEmptyObject,
21
+ isNotEmptyArray,
22
+ isNotEmptyObject,
23
+ isPlainObject,
24
+ pluralize,
25
+ } from '@redocly/openapi-core/lib/utils';
20
26
  import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
21
27
  import { deprecatedRefDocsSchema } from '@redocly/config/lib/reference-docs-config-schema';
22
28
  import { outputExtensions } from '../types';
23
29
  import { version } from './update-version-notifier';
24
30
  import { DESTINATION_REGEX } from '../commands/push';
25
- import fetch from './fetch-with-timeout';
31
+ import fetch, { DEFAULT_FETCH_TIMEOUT } from './fetch-with-timeout';
26
32
 
27
33
  import type { Arguments } from 'yargs';
28
34
  import type {
@@ -42,8 +48,7 @@ export async function getFallbackApisOrExit(
42
48
  config: ConfigApis
43
49
  ): Promise<Entrypoint[]> {
44
50
  const { apis } = config;
45
- const shouldFallbackToAllDefinitions =
46
- !isNotEmptyArray(argsApis) && apis && Object.keys(apis).length > 0;
51
+ const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsApis) && isNotEmptyObject(apis);
47
52
  const res = shouldFallbackToAllDefinitions
48
53
  ? fallbackToAllDefinitions(apis, config)
49
54
  : await expandGlobsInEntrypoints(argsApis!, config);
@@ -64,10 +69,6 @@ function getConfigDirectory(config: ConfigApis) {
64
69
  return config.configFile ? dirname(config.configFile) : process.cwd();
65
70
  }
66
71
 
67
- function isNotEmptyArray<T>(args?: T[]): boolean {
68
- return Array.isArray(args) && !!args.length;
69
- }
70
-
71
72
  function isApiPathValid(apiPath: string): string | void {
72
73
  if (!apiPath.trim()) {
73
74
  exitWithError('Path cannot be empty.');
@@ -80,15 +81,21 @@ function fallbackToAllDefinitions(
80
81
  apis: Record<string, ResolvedApi>,
81
82
  config: ConfigApis
82
83
  ): Entrypoint[] {
83
- return Object.entries(apis).map(([alias, { root }]) => ({
84
+ return Object.entries(apis).map(([alias, { root, output }]) => ({
84
85
  path: isAbsoluteUrl(root) ? root : resolve(getConfigDirectory(config), root),
85
86
  alias,
87
+ output: output && resolve(getConfigDirectory(config), output),
86
88
  }));
87
89
  }
88
90
 
89
91
  function getAliasOrPath(config: ConfigApis, aliasOrPath: string): Entrypoint {
90
- return config.apis[aliasOrPath]
91
- ? { path: config.apis[aliasOrPath]?.root, alias: aliasOrPath }
92
+ const aliasApi = config.apis[aliasOrPath];
93
+ return aliasApi
94
+ ? {
95
+ path: aliasApi.root,
96
+ alias: aliasOrPath,
97
+ output: aliasApi.output,
98
+ }
92
99
  : {
93
100
  path: aliasOrPath,
94
101
  // find alias by path, take the first match
@@ -99,10 +106,10 @@ function getAliasOrPath(config: ConfigApis, aliasOrPath: string): Entrypoint {
99
106
  };
100
107
  }
101
108
 
102
- async function expandGlobsInEntrypoints(args: string[], config: ConfigApis) {
109
+ async function expandGlobsInEntrypoints(argApis: string[], config: ConfigApis) {
103
110
  return (
104
111
  await Promise.all(
105
- (args as string[]).map(async (aliasOrPath) => {
112
+ argApis.map(async (aliasOrPath) => {
106
113
  return glob.hasMagic(aliasOrPath) && !isAbsoluteUrl(aliasOrPath)
107
114
  ? (await promisify(glob)(aliasOrPath)).map((g: string) => getAliasOrPath(config, g))
108
115
  : getAliasOrPath(config, aliasOrPath);
@@ -356,33 +363,20 @@ export function printConfigLintTotals(totals: Totals, command?: string | number)
356
363
  }
357
364
  }
358
365
 
359
- export function getOutputFileName(
360
- entrypoint: string,
361
- entries: number,
362
- output?: string,
363
- ext?: BundleOutputFormat
364
- ) {
365
- if (!output) {
366
- return { outputFile: 'stdout', ext: ext || 'yaml' };
366
+ export function getOutputFileName(entrypoint: string, output?: string, ext?: BundleOutputFormat) {
367
+ let outputFile = output;
368
+ if (!outputFile) {
369
+ return { ext: ext || 'yaml' };
367
370
  }
368
371
 
369
- let outputFile = output;
370
- if (entries > 1) {
371
- ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
372
- if (!outputExtensions.includes(ext as any)) {
373
- throw new Error(`Invalid file extension: ${ext}.`);
374
- }
375
- outputFile = join(output, basename(entrypoint, extname(entrypoint))) + '.' + ext;
376
- } else {
377
- if (output) {
378
- ext = ext || (extname(output).substring(1) as BundleOutputFormat);
379
- }
380
- ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
381
- if (!outputExtensions.includes(ext as any)) {
382
- throw new Error(`Invalid file extension: ${ext}.`);
383
- }
384
- outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
372
+ if (outputFile) {
373
+ ext = ext || (extname(outputFile).substring(1) as BundleOutputFormat);
374
+ }
375
+ ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
376
+ if (!outputExtensions.includes(ext)) {
377
+ throw new Error(`Invalid file extension: ${ext}.`);
385
378
  }
379
+ outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
386
380
  return { outputFile, ext };
387
381
  }
388
382
 
@@ -569,6 +563,7 @@ export async function sendTelemetry(
569
563
  spec_full_version,
570
564
  };
571
565
  await fetch(`https://api.redocly.com/registry/telemetry/cli`, {
566
+ timeout: DEFAULT_FETCH_TIMEOUT,
572
567
  method: 'POST',
573
568
  headers: {
574
569
  'content-type': 'application/json',
@@ -2,7 +2,7 @@ import { tmpdir } from 'os';
2
2
  import { join } from 'path';
3
3
  import { existsSync, writeFileSync, readFileSync, statSync } from 'fs';
4
4
  import { compare } from 'semver';
5
- import fetch from './fetch-with-timeout';
5
+ import fetch, { DEFAULT_FETCH_TIMEOUT } from './fetch-with-timeout';
6
6
  import { cyan, green, yellow } from 'colorette';
7
7
  import { cleanColors } from './miscellaneous';
8
8
 
@@ -34,10 +34,16 @@ const isNewVersionAvailable = (current: string, latest: string) => compare(curre
34
34
 
35
35
  const getLatestVersion = async (packageName: string): Promise<string | undefined> => {
36
36
  const latestUrl = `http://registry.npmjs.org/${packageName}/latest`;
37
- const response = await fetch(latestUrl);
38
- if (!response) return;
39
- const info = await response.json();
40
- return info.version;
37
+
38
+ try {
39
+ const response = await fetch(latestUrl, { timeout: DEFAULT_FETCH_TIMEOUT });
40
+ const info = await response.json();
41
+
42
+ return info.version;
43
+ } catch {
44
+ // Do nothing
45
+ return;
46
+ }
41
47
  };
42
48
 
43
49
  export const cacheLatestVersion = () => {