@redocly/cli 1.0.0-beta.116 → 1.0.0-beta.118

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 (38) hide show
  1. package/lib/__mocks__/@redocly/openapi-core.d.ts +1 -0
  2. package/lib/__mocks__/@redocly/openapi-core.js +2 -1
  3. package/lib/__mocks__/utils.d.ts +11 -0
  4. package/lib/__mocks__/utils.js +3 -1
  5. package/lib/__tests__/commands/lint.test.js +4 -4
  6. package/lib/__tests__/commands/push.test.js +1 -1
  7. package/lib/__tests__/utils.test.js +98 -0
  8. package/lib/commands/build-docs/index.js +5 -3
  9. package/lib/commands/build-docs/utils.d.ts +1 -2
  10. package/lib/commands/build-docs/utils.js +3 -6
  11. package/lib/commands/bundle.js +10 -7
  12. package/lib/commands/join.js +1 -1
  13. package/lib/commands/lint.js +1 -1
  14. package/lib/commands/login.js +1 -1
  15. package/lib/commands/preview-docs/index.js +1 -1
  16. package/lib/commands/push.js +1 -1
  17. package/lib/commands/stats.js +1 -1
  18. package/lib/types.d.ts +2 -1
  19. package/lib/utils.d.ts +10 -3
  20. package/lib/utils.js +37 -13
  21. package/package.json +2 -2
  22. package/src/__mocks__/@redocly/openapi-core.ts +1 -0
  23. package/src/__mocks__/utils.ts +3 -0
  24. package/src/__tests__/commands/lint.test.ts +5 -5
  25. package/src/__tests__/commands/push.test.ts +3 -3
  26. package/src/__tests__/utils.test.ts +129 -1
  27. package/src/commands/build-docs/index.ts +12 -5
  28. package/src/commands/build-docs/utils.ts +2 -6
  29. package/src/commands/bundle.ts +8 -11
  30. package/src/commands/join.ts +2 -2
  31. package/src/commands/lint.ts +2 -2
  32. package/src/commands/login.ts +3 -3
  33. package/src/commands/preview-docs/index.ts +2 -3
  34. package/src/commands/push.ts +2 -2
  35. package/src/commands/stats.ts +2 -3
  36. package/src/types.ts +3 -1
  37. package/src/utils.ts +65 -19
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -52,6 +52,7 @@ export declare const bundleDocument: jest.Mock<Promise<{
52
52
  problems: {};
53
53
  }>, []>;
54
54
  export declare const detectOpenAPI: jest.Mock<any, any>;
55
+ export declare const isAbsoluteUrl: jest.Mock<any, any>;
55
56
  export declare class BaseResolver {
56
57
  cache: Map<string, Promise<ResolveError | Document>>;
57
58
  getFiles: jest.Mock<any, any>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Oas3Operations = exports.OasVersion = exports.BaseResolver = exports.detectOpenAPI = exports.bundleDocument = exports.doesYamlFileExist = exports.findConfig = exports.slash = exports.formatProblems = exports.getTotals = exports.bundle = exports.lint = exports.getMergedConfig = exports.loadConfig = exports.RedoclyClient = exports.__redoclyClient = void 0;
3
+ exports.Oas3Operations = exports.OasVersion = exports.BaseResolver = exports.isAbsoluteUrl = exports.detectOpenAPI = exports.bundleDocument = exports.doesYamlFileExist = exports.findConfig = exports.slash = exports.formatProblems = exports.getTotals = exports.bundle = exports.lint = exports.getMergedConfig = exports.loadConfig = exports.RedoclyClient = exports.__redoclyClient = void 0;
4
4
  const config_1 = require("./../../__tests__/fixtures/config");
5
5
  const documents_1 = require("../documents");
6
6
  exports.__redoclyClient = {
@@ -29,6 +29,7 @@ exports.findConfig = jest.fn();
29
29
  exports.doesYamlFileExist = jest.fn();
30
30
  exports.bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
31
31
  exports.detectOpenAPI = jest.fn();
32
+ exports.isAbsoluteUrl = jest.fn();
32
33
  class BaseResolver {
33
34
  constructor() {
34
35
  this.cache = new Map();
@@ -14,3 +14,14 @@ export declare const getOutputFileName: jest.Mock<{
14
14
  export declare const handleError: jest.Mock<any, any>;
15
15
  export declare const exitWithError: jest.Mock<any, any>;
16
16
  export declare const writeYaml: jest.Mock<any, any>;
17
+ export declare const loadConfigAndHandleErrors: jest.Mock<{
18
+ configFile: null;
19
+ styleguide: {
20
+ addIgnore: jest.Mock<any, any>;
21
+ skipRules: jest.Mock<any, any>;
22
+ skipPreprocessors: jest.Mock<any, any>;
23
+ saveIgnore: jest.Mock<any, any>;
24
+ skipDecorators: jest.Mock<any, any>;
25
+ ignore: null;
26
+ };
27
+ }, []>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeYaml = exports.exitWithError = exports.handleError = exports.getOutputFileName = exports.printLintTotals = exports.printUnusedWarnings = exports.printExecutionTime = exports.getExecutionTime = exports.pluralize = exports.slash = exports.dumpBundle = exports.getFallbackApisOrExit = void 0;
3
+ exports.loadConfigAndHandleErrors = exports.writeYaml = exports.exitWithError = exports.handleError = exports.getOutputFileName = exports.printLintTotals = exports.printUnusedWarnings = exports.printExecutionTime = exports.getExecutionTime = exports.pluralize = exports.slash = exports.dumpBundle = exports.getFallbackApisOrExit = void 0;
4
+ const config_1 = require("../__tests__/fixtures/config");
4
5
  exports.getFallbackApisOrExit = jest.fn((entrypoints) => entrypoints.map((path) => ({ path })));
5
6
  exports.dumpBundle = jest.fn(() => '');
6
7
  exports.slash = jest.fn();
@@ -13,3 +14,4 @@ exports.getOutputFileName = jest.fn(() => ({ outputFile: 'test.yaml', ext: 'yaml
13
14
  exports.handleError = jest.fn();
14
15
  exports.exitWithError = jest.fn();
15
16
  exports.writeYaml = jest.fn();
17
+ exports.loadConfigAndHandleErrors = jest.fn(() => config_1.ConfigFixture);
@@ -45,11 +45,11 @@ describe('handleLint', () => {
45
45
  it('should fail if config file does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
46
46
  yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { config: 'config.yaml' }), versionMock);
47
47
  expect(utils_1.exitWithError).toHaveBeenCalledWith('Please, provide valid path to the configuration file');
48
- expect(openapi_core_1.loadConfig).toHaveBeenCalledTimes(0);
48
+ expect(utils_1.loadConfigAndHandleErrors).toHaveBeenCalledTimes(0);
49
49
  }));
50
- it('should call loadConfig and getFallbackApisOrExit', () => __awaiter(void 0, void 0, void 0, function* () {
50
+ it('should call loadConfigAndHandleErrors and getFallbackApisOrExit', () => __awaiter(void 0, void 0, void 0, function* () {
51
51
  yield lint_1.handleLint(argvMock, versionMock);
52
- expect(openapi_core_1.loadConfig).toHaveBeenCalledWith({
52
+ expect(utils_1.loadConfigAndHandleErrors).toHaveBeenCalledWith({
53
53
  configPath: undefined,
54
54
  customExtends: undefined,
55
55
  processRawConfig: undefined,
@@ -58,7 +58,7 @@ describe('handleLint', () => {
58
58
  }));
59
59
  it('should call loadConfig with args if such exist', () => __awaiter(void 0, void 0, void 0, function* () {
60
60
  yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { config: 'redocly.yaml', extends: ['some/path'] }), versionMock);
61
- expect(openapi_core_1.loadConfig).toHaveBeenCalledWith({
61
+ expect(utils_1.loadConfigAndHandleErrors).toHaveBeenCalledWith({
62
62
  configPath: 'redocly.yaml',
63
63
  customExtends: ['some/path'],
64
64
  processRawConfig: undefined,
@@ -79,7 +79,7 @@ describe('push', () => {
79
79
  expect(utils_1.exitWithError).toBeCalledTimes(1);
80
80
  }));
81
81
  it('push with --files', () => __awaiter(void 0, void 0, void 0, function* () {
82
- openapi_core_1.loadConfig.mockImplementation(({ files }) => {
82
+ utils_1.loadConfigAndHandleErrors.mockImplementation(({ files }) => {
83
83
  return Object.assign(Object.assign({}, config_1.ConfigFixture), { files });
84
84
  });
85
85
  //@ts-ignore
@@ -10,9 +10,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const utils_1 = require("../utils");
13
+ const openapi_core_1 = require("@redocly/openapi-core");
13
14
  const colorette_1 = require("colorette");
15
+ const fs_1 = require("fs");
16
+ const path = require("path");
14
17
  jest.mock('os');
15
18
  jest.mock('colorette');
19
+ jest.mock('fs');
16
20
  describe('isSubdir', () => {
17
21
  it('can correctly determine if subdir', () => {
18
22
  [
@@ -52,6 +56,7 @@ describe('pathToFilename', () => {
52
56
  });
53
57
  describe('getFallbackApisOrExit', () => {
54
58
  it('should find alias by filename', () => __awaiter(void 0, void 0, void 0, function* () {
59
+ fs_1.existsSync.mockImplementationOnce(() => true);
55
60
  const entry = yield utils_1.getFallbackApisOrExit(['./test.yaml'], {
56
61
  apis: {
57
62
  main: {
@@ -98,3 +103,96 @@ describe('printConfigLintTotals', () => {
98
103
  expect(redColoretteMocks).toHaveBeenCalledTimes(0);
99
104
  });
100
105
  });
106
+ describe('getFallbackApisOrExit', () => {
107
+ const redColoretteMocks = colorette_1.red;
108
+ const yellowColoretteMocks = colorette_1.yellow;
109
+ const apis = {
110
+ main: {
111
+ root: 'someFile.yaml',
112
+ styleguide: {},
113
+ },
114
+ };
115
+ const config = { apis };
116
+ beforeEach(() => {
117
+ yellowColoretteMocks.mockImplementation((text) => text);
118
+ redColoretteMocks.mockImplementation((text) => text);
119
+ jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
120
+ jest.spyOn(process, 'exit').mockImplementation();
121
+ });
122
+ it('should exit with error because no path provided', () => __awaiter(void 0, void 0, void 0, function* () {
123
+ const apisConfig = {
124
+ apis: {},
125
+ };
126
+ yield utils_1.getFallbackApisOrExit([''], apisConfig);
127
+ expect(process.exit).toHaveBeenCalledWith(1);
128
+ }));
129
+ it('should error if file from config do not exist', () => __awaiter(void 0, void 0, void 0, function* () {
130
+ fs_1.existsSync.mockImplementationOnce(() => false);
131
+ yield utils_1.getFallbackApisOrExit(undefined, config);
132
+ expect(process.stderr.write).toHaveBeenCalledWith('\n someFile.yaml does not exist or is invalid. Please provide a valid path. \n\n');
133
+ expect(process.exit).toHaveBeenCalledWith(1);
134
+ }));
135
+ it('should return valid array with results if such file exist', () => __awaiter(void 0, void 0, void 0, function* () {
136
+ fs_1.existsSync.mockImplementationOnce(() => true);
137
+ jest.spyOn(path, 'resolve').mockImplementationOnce((_, path) => path);
138
+ const result = yield utils_1.getFallbackApisOrExit(undefined, config);
139
+ expect(process.stderr.write).toHaveBeenCalledTimes(0);
140
+ expect(process.exit).toHaveBeenCalledTimes(0);
141
+ expect(result).toStrictEqual([
142
+ {
143
+ alias: 'main',
144
+ path: 'someFile.yaml',
145
+ },
146
+ ]);
147
+ }));
148
+ it('should exit with error in case if invalid path provided as args', () => __awaiter(void 0, void 0, void 0, function* () {
149
+ const apisConfig = {
150
+ apis: {},
151
+ };
152
+ fs_1.existsSync.mockImplementationOnce(() => false);
153
+ yield utils_1.getFallbackApisOrExit(['someFile.yaml'], apisConfig);
154
+ expect(process.stderr.write).toHaveBeenCalledWith('\n someFile.yaml does not exist or is invalid. Please provide a valid path. \n\n');
155
+ expect(process.exit).toHaveBeenCalledWith(1);
156
+ }));
157
+ it('should exit with error in case if invalid 2 path provided as args', () => __awaiter(void 0, void 0, void 0, function* () {
158
+ const apisConfig = {
159
+ apis: {},
160
+ };
161
+ fs_1.existsSync.mockImplementationOnce(() => false);
162
+ yield utils_1.getFallbackApisOrExit(['someFile.yaml', 'someFile2.yaml'], apisConfig);
163
+ expect(process.stderr.write).lastCalledWith('\n someFile2.yaml does not exist or is invalid. Please provide a valid path. \n\n');
164
+ expect(process.exit).toHaveBeenCalledWith(1);
165
+ }));
166
+ it('should exit with error if only one file exist ', () => __awaiter(void 0, void 0, void 0, function* () {
167
+ const apisStub = Object.assign(Object.assign({}, apis), { notExist: {
168
+ root: 'notExist.yaml',
169
+ styleguide: {},
170
+ } });
171
+ const configStub = { apis: apisStub };
172
+ fs_1.existsSync.mockImplementationOnce((path) => path === 'someFile.yaml');
173
+ yield utils_1.getFallbackApisOrExit(undefined, configStub);
174
+ expect(process.stderr.write).toBeCalledWith('\n notExist.yaml does not exist or is invalid. Please provide a valid path. \n\n');
175
+ expect(process.exit).toHaveBeenCalledWith(1);
176
+ }));
177
+ it('should work ok if it is url passed', () => __awaiter(void 0, void 0, void 0, function* () {
178
+ fs_1.existsSync.mockImplementationOnce(() => false);
179
+ openapi_core_1.isAbsoluteUrl.mockImplementation(() => true);
180
+ const apisConfig = {
181
+ apis: {
182
+ main: {
183
+ root: 'https://someLinkt/petstore.yaml?main',
184
+ styleguide: {},
185
+ },
186
+ },
187
+ };
188
+ const result = yield utils_1.getFallbackApisOrExit(undefined, apisConfig);
189
+ expect(process.stderr.write).toHaveBeenCalledTimes(0);
190
+ expect(process.exit).toHaveBeenCalledTimes(0);
191
+ expect(result).toStrictEqual([
192
+ {
193
+ alias: 'main',
194
+ path: 'https://someLinkt/petstore.yaml?main',
195
+ },
196
+ ]);
197
+ }));
198
+ });
@@ -15,12 +15,12 @@ const path_1 = require("path");
15
15
  const fs_1 = require("fs");
16
16
  const perf_hooks_1 = require("perf_hooks");
17
17
  const utils_1 = require("./utils");
18
- const utils_2 = require("../../utils");
19
18
  const openapi_core_1 = require("@redocly/openapi-core");
19
+ const utils_2 = require("../../utils");
20
20
  const handlerBuildCommand = (argv) => __awaiter(void 0, void 0, void 0, function* () {
21
21
  var _a;
22
22
  const startedAt = perf_hooks_1.performance.now();
23
- const configFromFile = yield openapi_core_1.loadConfig({ configPath: argv.config });
23
+ const configFromFile = yield utils_2.loadConfigAndHandleErrors({ configPath: argv.config });
24
24
  const config = openapi_core_1.getMergedConfig(configFromFile, argv.api);
25
25
  const apis = yield utils_2.getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
26
26
  const { path: pathToApi } = apis[0];
@@ -36,7 +36,9 @@ const handlerBuildCommand = (argv) => __awaiter(void 0, void 0, void 0, function
36
36
  const redocCurrentVersion = require('../../../package.json').dependencies.redoc.substring(1); // remove ~
37
37
  try {
38
38
  const elapsed = utils_2.getExecutionTime(startedAt);
39
- const api = yield redoc_1.loadAndBundleSpec(utils_1.isURL(pathToApi) ? pathToApi : path_1.resolve(argv.config ? path_1.dirname(argv.config) : '', pathToApi));
39
+ const api = yield redoc_1.loadAndBundleSpec(openapi_core_1.isAbsoluteUrl(pathToApi)
40
+ ? pathToApi
41
+ : path_1.resolve(argv.config ? path_1.dirname(argv.config) : '', pathToApi));
40
42
  const pageHTML = yield utils_1.getPageHTML(api, pathToApi, Object.assign(Object.assign({}, options), { redocCurrentVersion }));
41
43
  fs_1.mkdirSync(path_1.dirname(options.output), { recursive: true });
42
44
  fs_1.writeFileSync(options.output, pageHTML);
@@ -1,8 +1,7 @@
1
+ import { Config } from '@redocly/openapi-core';
1
2
  import type { BuildDocsOptions } from './types';
2
- import type { Config } from '@redocly/openapi-core';
3
3
  export declare function getObjectOrJSON(openapiOptions: string | Record<string, unknown>, config: Config): JSON | Record<string, unknown> | Config;
4
4
  export declare function getPageHTML(api: any, pathToApi: string, { cdn, title, disableGoogleFont, templateFileName, templateOptions, redocOptions, redocCurrentVersion, }: BuildDocsOptions): Promise<string>;
5
- export declare function isURL(str: string): boolean;
6
5
  export declare function sanitizeJSONString(str: string): string;
7
6
  export declare function escapeClosingScriptTag(str: string): string;
8
7
  export declare function escapeUnicode(str: string): string;
@@ -9,9 +9,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.escapeUnicode = exports.escapeClosingScriptTag = exports.sanitizeJSONString = exports.isURL = exports.getPageHTML = exports.getObjectOrJSON = void 0;
12
+ exports.escapeUnicode = exports.escapeClosingScriptTag = exports.sanitizeJSONString = exports.getPageHTML = exports.getObjectOrJSON = void 0;
13
13
  const React = require("react");
14
14
  const redoc_1 = require("redoc");
15
+ const openapi_core_1 = require("@redocly/openapi-core");
15
16
  const server_1 = require("react-dom/server");
16
17
  const styled_components_1 = require("styled-components");
17
18
  const handlebars_1 = require("handlebars");
@@ -51,7 +52,7 @@ exports.getObjectOrJSON = getObjectOrJSON;
51
52
  function getPageHTML(api, pathToApi, { cdn, title, disableGoogleFont, templateFileName, templateOptions, redocOptions = {}, redocCurrentVersion, }) {
52
53
  return __awaiter(this, void 0, void 0, function* () {
53
54
  process.stderr.write('Prerendering docs');
54
- const apiUrl = redocOptions.specUrl || (isURL(pathToApi) ? pathToApi : undefined);
55
+ const apiUrl = redocOptions.specUrl || (openapi_core_1.isAbsoluteUrl(pathToApi) ? pathToApi : undefined);
55
56
  const store = yield redoc_1.createStore(api, apiUrl, redocOptions);
56
57
  const sheet = new styled_components_1.ServerStyleSheet();
57
58
  const html = server_1.renderToString(sheet.collectStyles(React.createElement(redoc_1.Redoc, { store })));
@@ -80,10 +81,6 @@ function getPageHTML(api, pathToApi, { cdn, title, disableGoogleFont, templateFi
80
81
  });
81
82
  }
82
83
  exports.getPageHTML = getPageHTML;
83
- function isURL(str) {
84
- return /^(https?:)\/\//m.test(str);
85
- }
86
- exports.isURL = isURL;
87
84
  function sanitizeJSONString(str) {
88
85
  return escapeClosingScriptTag(escapeUnicode(str));
89
86
  }
@@ -27,11 +27,14 @@ const perf_hooks_1 = require("perf_hooks");
27
27
  const colorette_1 = require("colorette");
28
28
  const fs_1 = require("fs");
29
29
  function handleBundle(argv, version) {
30
- var _a, _b, _c, _d;
30
+ var _a, _b, _c, _d, _e;
31
31
  return __awaiter(this, void 0, void 0, function* () {
32
- const config = yield openapi_core_1.loadConfig({ configPath: argv.config, customExtends: argv.extends });
33
- const removeUnusedComponents = argv['remove-unused-components'] &&
34
- !((_b = (_a = config.rawConfig.styleguide) === null || _a === void 0 ? void 0 : _a.decorators) === null || _b === void 0 ? void 0 : _b.hasOwnProperty('remove-unused-components'));
32
+ const config = yield utils_1.loadConfigAndHandleErrors({
33
+ configPath: argv.config,
34
+ customExtends: argv.extends,
35
+ });
36
+ const removeUnusedComponents = argv['remove-unused-components'] ||
37
+ ((_c = (_b = (_a = config.rawConfig) === null || _a === void 0 ? void 0 : _a.styleguide) === null || _b === void 0 ? void 0 : _b.decorators) === null || _c === void 0 ? void 0 : _c.hasOwnProperty('remove-unused-components'));
35
38
  const apis = yield utils_1.getFallbackApisOrExit(argv.apis, config);
36
39
  const totals = { errors: 0, warnings: 0, ignored: 0 };
37
40
  const maxProblems = argv['max-problems'];
@@ -64,13 +67,13 @@ function handleBundle(argv, version) {
64
67
  utils_1.printLintTotals(fileLintTotals, 2);
65
68
  }
66
69
  process.stderr.write(colorette_1.gray(`bundling ${path}...\n`));
67
- const _e = yield openapi_core_1.bundle({
70
+ const _f = yield openapi_core_1.bundle({
68
71
  config: resolvedConfig,
69
72
  ref: path,
70
73
  dereference: argv.dereferenced,
71
74
  removeUnusedComponents,
72
75
  keepUrlRefs: argv['keep-url-references'],
73
- }), { bundle: result, problems } = _e, meta = __rest(_e, ["bundle", "problems"]);
76
+ }), { bundle: result, problems } = _f, meta = __rest(_f, ["bundle", "problems"]);
74
77
  const fileTotals = openapi_core_1.getTotals(problems);
75
78
  const { outputFile, ext } = utils_1.getOutputFileName(path, apis.length, argv.output, argv.ext);
76
79
  if (fileTotals.errors === 0 || argv.force) {
@@ -112,7 +115,7 @@ function handleBundle(argv, version) {
112
115
  else {
113
116
  process.stderr.write(`📦 Created a bundle for ${colorette_1.blue(path)} at ${colorette_1.blue(outputFile)} ${colorette_1.green(elapsed)}.\n`);
114
117
  }
115
- const removedCount = (_d = (_c = meta.visitorsData) === null || _c === void 0 ? void 0 : _c['remove-unused-components']) === null || _d === void 0 ? void 0 : _d.removedCount;
118
+ const removedCount = (_e = (_d = meta.visitorsData) === null || _d === void 0 ? void 0 : _d['remove-unused-components']) === null || _e === void 0 ? void 0 : _e.removedCount;
116
119
  if (removedCount) {
117
120
  process.stderr.write(colorette_1.gray(`🧹 Removed ${removedCount} unused components.\n`));
118
121
  }
@@ -37,7 +37,7 @@ function handleJoin(argv, packageVersion) {
37
37
  if (usedTagsOptions.length > 1) {
38
38
  return utils_1.exitWithError(`You use ${colorette_1.yellow(usedTagsOptions.join(', '))} together.\nPlease choose only one! \n\n`);
39
39
  }
40
- const config = yield openapi_core_1.loadConfig();
40
+ const config = yield utils_1.loadConfigAndHandleErrors();
41
41
  const apis = yield utils_1.getFallbackApisOrExit(argv.apis, config);
42
42
  const externalRefResolver = new openapi_core_1.BaseResolver(config.resolve);
43
43
  const documents = yield Promise.all(apis.map(({ path }) => externalRefResolver.resolveDocument(null, path, true)));
@@ -19,7 +19,7 @@ function handleLint(argv, version) {
19
19
  if (argv.config && !openapi_core_1.doesYamlFileExist(argv.config)) {
20
20
  return utils_1.exitWithError('Please, provide valid path to the configuration file');
21
21
  }
22
- const config = yield openapi_core_1.loadConfig({
22
+ const config = yield utils_1.loadConfigAndHandleErrors({
23
23
  configPath: argv.config,
24
24
  customExtends: argv.extends,
25
25
  processRawConfig: lintConfigCallback(argv, version),
@@ -19,7 +19,7 @@ function promptClientToken(domain) {
19
19
  exports.promptClientToken = promptClientToken;
20
20
  function handleLogin(argv) {
21
21
  return __awaiter(this, void 0, void 0, function* () {
22
- const region = argv.region || (yield openapi_core_1.loadConfig()).region;
22
+ const region = argv.region || (yield utils_1.loadConfigAndHandleErrors()).region;
23
23
  const client = new openapi_core_1.RedoclyClient(region);
24
24
  const clientToken = yield promptClientToken(client.domain);
25
25
  process.stdout.write(colorette_1.gray('\n Logging in...\n'));
@@ -94,7 +94,7 @@ function previewDocs(argv) {
94
94
  });
95
95
  function reloadConfig() {
96
96
  return __awaiter(this, void 0, void 0, function* () {
97
- const config = yield openapi_core_1.loadConfig({ configPath: argv.config });
97
+ const config = yield utils_1.loadConfigAndHandleErrors({ configPath: argv.config });
98
98
  const redoclyClient = new openapi_core_1.RedoclyClient();
99
99
  isAuthorizedWithRedocly = yield redoclyClient.isAuthorizedWithRedocly();
100
100
  const resolvedConfig = openapi_core_1.getMergedConfig(config, argv.api);
@@ -33,7 +33,7 @@ const login_1 = require("./login");
33
33
  const DEFAULT_VERSION = 'latest';
34
34
  function handlePush(argv) {
35
35
  return __awaiter(this, void 0, void 0, function* () {
36
- const config = yield openapi_core_1.loadConfig({ region: argv.region, files: argv.files });
36
+ const config = yield utils_1.loadConfigAndHandleErrors({ region: argv.region, files: argv.files });
37
37
  const client = new openapi_core_1.RedoclyClient(config.region);
38
38
  const isAuthorized = yield client.isAuthorizedWithRedoclyByRegion();
39
39
  if (!isAuthorized) {
@@ -54,7 +54,7 @@ function printStats(statsAccumulator, api, format) {
54
54
  }
55
55
  function handleStats(argv) {
56
56
  return __awaiter(this, void 0, void 0, function* () {
57
- const config = yield openapi_core_1.loadConfig({ configPath: argv.config });
57
+ const config = yield utils_1.loadConfigAndHandleErrors({ configPath: argv.config });
58
58
  const [{ path }] = yield utils_1.getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
59
59
  const externalRefResolver = new openapi_core_1.BaseResolver(config.resolve);
60
60
  const { bundle: document } = yield openapi_core_1.bundle({ config, ref: path });
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BundleOutputFormat, OutputFormat, Region } from '@redocly/openapi-core';
1
+ import type { BundleOutputFormat, OutputFormat, Region, Config } from '@redocly/openapi-core';
2
2
  export declare type Totals = {
3
3
  errors: number;
4
4
  warnings: number;
@@ -23,3 +23,4 @@ export declare type Skips = {
23
23
  'skip-decorator'?: string[];
24
24
  'skip-preprocessor'?: string[];
25
25
  };
26
+ export declare type ConfigApis = Pick<Config, 'apis' | 'configFile'>;
package/lib/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { BundleOutputFormat, Config, StyleguideConfig } from '@redocly/openapi-core';
2
- import { Totals, Entrypoint } from './types';
3
- export declare function getFallbackApisOrExit(argsApis: string[] | undefined, config: Config): Promise<Entrypoint[]>;
1
+ import { BundleOutputFormat, StyleguideConfig, RawConfig, Region, Config } from '@redocly/openapi-core';
2
+ import { Totals, Entrypoint, ConfigApis } from './types';
3
+ export declare function getFallbackApisOrExit(argsApis: string[] | undefined, config: ConfigApis): Promise<Entrypoint[]>;
4
4
  export declare function getExecutionTime(startedAt: number): string;
5
5
  export declare function printExecutionTime(commandName: string, startedAt: number, api: string): void;
6
6
  export declare function pathToFilename(path: string, pathSeparator: string): string;
@@ -28,3 +28,10 @@ export declare function exitWithError(message: string): void;
28
28
  * Checks if dir is subdir of parent
29
29
  */
30
30
  export declare function isSubdir(parent: string, dir: string): boolean;
31
+ export declare function loadConfigAndHandleErrors(options?: {
32
+ configPath?: string;
33
+ customExtends?: string[];
34
+ processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
35
+ files?: string[];
36
+ region?: Region;
37
+ }): Promise<Config>;
package/lib/utils.js CHANGED
@@ -9,13 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.handleError = exports.pluralize = exports.writeYaml = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
12
+ exports.loadConfigAndHandleErrors = exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.handleError = exports.pluralize = exports.writeYaml = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
13
13
  const path_1 = require("path");
14
14
  const colorette_1 = require("colorette");
15
15
  const perf_hooks_1 = require("perf_hooks");
16
16
  const glob = require("glob-promise");
17
17
  const fs = require("fs");
18
- const path = require("path");
19
18
  const readline = require("readline");
20
19
  const stream_1 = require("stream");
21
20
  const openapi_core_1 = require("@redocly/openapi-core");
@@ -25,13 +24,13 @@ function getFallbackApisOrExit(argsApis, config) {
25
24
  const { apis } = config;
26
25
  const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsApis) && apis && Object.keys(apis).length > 0;
27
26
  const res = shouldFallbackToAllDefinitions
28
- ? Object.entries(apis).map(([alias, { root }]) => ({
29
- path: path_1.resolve(getConfigDirectory(config), root),
30
- alias,
31
- }))
27
+ ? fallbackToAllDefinitions(apis, config)
32
28
  : yield expandGlobsInEntrypoints(argsApis, config);
33
- if (!isNotEmptyArray(res)) {
34
- process.stderr.write('error: missing required argument `apis`.\n');
29
+ const filteredInvalidEntrypoints = res.filter(({ path }) => !isApiPathValid(path));
30
+ if (isNotEmptyArray(filteredInvalidEntrypoints)) {
31
+ for (const { path } of filteredInvalidEntrypoints) {
32
+ process.stderr.write(colorette_1.yellow(`\n ${path_1.relative(process.cwd(), path)} ${colorette_1.red(`does not exist or is invalid. Please provide a valid path. \n\n`)}`));
33
+ }
35
34
  process.exit(1);
36
35
  }
37
36
  return res;
@@ -44,6 +43,19 @@ function getConfigDirectory(config) {
44
43
  function isNotEmptyArray(args) {
45
44
  return Array.isArray(args) && !!args.length;
46
45
  }
46
+ function isApiPathValid(apiPath) {
47
+ if (!apiPath.trim()) {
48
+ exitWithError('Path cannot be empty.');
49
+ return;
50
+ }
51
+ return fs.existsSync(apiPath) || openapi_core_1.isAbsoluteUrl(apiPath) ? apiPath : undefined;
52
+ }
53
+ function fallbackToAllDefinitions(apis, config) {
54
+ return Object.entries(apis).map(([alias, { root }]) => ({
55
+ path: openapi_core_1.isAbsoluteUrl(root) ? root : path_1.resolve(getConfigDirectory(config), root),
56
+ alias,
57
+ }));
58
+ }
47
59
  function getAliasOrPath(config, aliasOrPath) {
48
60
  var _a, _b, _c;
49
61
  return config.apis[aliasOrPath]
@@ -52,14 +64,14 @@ function getAliasOrPath(config, aliasOrPath) {
52
64
  path: aliasOrPath,
53
65
  // find alias by path, take the first match
54
66
  alias: (_c = (_b = Object.entries(config.apis).find(([_alias, api]) => {
55
- return path.resolve(api.root) === path.resolve(aliasOrPath);
67
+ return path_1.resolve(api.root) === path_1.resolve(aliasOrPath);
56
68
  })) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : undefined,
57
69
  };
58
70
  }
59
71
  function expandGlobsInEntrypoints(args, config) {
60
72
  return __awaiter(this, void 0, void 0, function* () {
61
73
  return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
62
- return glob.hasMagic(aliasOrPath)
74
+ return glob.hasMagic(aliasOrPath) && !openapi_core_1.isAbsoluteUrl(aliasOrPath)
63
75
  ? (yield glob(aliasOrPath)).map((g) => getAliasOrPath(config, g))
64
76
  : getAliasOrPath(config, aliasOrPath);
65
77
  })))).flat();
@@ -118,7 +130,7 @@ function dumpBundle(obj, format, dereference) {
118
130
  }
119
131
  exports.dumpBundle = dumpBundle;
120
132
  function saveBundle(filename, output) {
121
- fs.mkdirSync(path.dirname(filename), { recursive: true });
133
+ fs.mkdirSync(path_1.dirname(filename), { recursive: true });
122
134
  fs.writeFileSync(filename, output);
123
135
  }
124
136
  exports.saveBundle = saveBundle;
@@ -277,7 +289,19 @@ exports.exitWithError = exitWithError;
277
289
  * Checks if dir is subdir of parent
278
290
  */
279
291
  function isSubdir(parent, dir) {
280
- const relative = path.relative(parent, dir);
281
- return !!relative && !/^..($|\/)/.test(relative) && !path.isAbsolute(relative);
292
+ const relativePath = path_1.relative(parent, dir);
293
+ return !!relativePath && !/^..($|\/)/.test(relativePath) && !path_1.isAbsolute(relativePath);
282
294
  }
283
295
  exports.isSubdir = isSubdir;
296
+ function loadConfigAndHandleErrors(options = {}) {
297
+ return __awaiter(this, void 0, void 0, function* () {
298
+ try {
299
+ return yield openapi_core_1.loadConfig(options);
300
+ }
301
+ catch (e) {
302
+ exitWithError(e.message);
303
+ return new openapi_core_1.Config({ apis: {}, styleguide: {} });
304
+ }
305
+ });
306
+ }
307
+ exports.loadConfigAndHandleErrors = loadConfigAndHandleErrors;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/cli",
3
- "version": "1.0.0-beta.116",
3
+ "version": "1.0.0-beta.118",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  "Roman Hotsiy <roman@redoc.ly> (https://redoc.ly/)"
34
34
  ],
35
35
  "dependencies": {
36
- "@redocly/openapi-core": "1.0.0-beta.116",
36
+ "@redocly/openapi-core": "1.0.0-beta.118",
37
37
  "assert-node-version": "^1.0.3",
38
38
  "chokidar": "^3.5.1",
39
39
  "colorette": "^1.2.0",
@@ -29,6 +29,7 @@ export const findConfig = jest.fn();
29
29
  export const doesYamlFileExist = jest.fn();
30
30
  export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
31
31
  export const detectOpenAPI = jest.fn();
32
+ export const isAbsoluteUrl = jest.fn();
32
33
 
33
34
  export class BaseResolver {
34
35
  cache = new Map<string, Promise<Document | ResolveError>>();
@@ -1,3 +1,5 @@
1
+ import { ConfigFixture } from '../__tests__/fixtures/config';
2
+
1
3
  export const getFallbackApisOrExit = jest.fn((entrypoints) =>
2
4
  entrypoints.map((path: string) => ({ path }))
3
5
  );
@@ -12,3 +14,4 @@ export const getOutputFileName = jest.fn(() => ({ outputFile: 'test.yaml', ext:
12
14
  export const handleError = jest.fn();
13
15
  export const exitWithError = jest.fn();
14
16
  export const writeYaml = jest.fn();
17
+ export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
@@ -1,6 +1,5 @@
1
1
  import { handleLint, LintOptions } from '../../commands/lint';
2
2
  import {
3
- loadConfig,
4
3
  getMergedConfig,
5
4
  lint,
6
5
  getTotals,
@@ -13,6 +12,7 @@ import {
13
12
  printUnusedWarnings,
14
13
  handleError,
15
14
  exitWithError,
15
+ loadConfigAndHandleErrors,
16
16
  } from '../../utils';
17
17
  import { ConfigFixture } from '../fixtures/config';
18
18
  import { performance } from 'perf_hooks';
@@ -58,12 +58,12 @@ describe('handleLint', () => {
58
58
  expect(exitWithError).toHaveBeenCalledWith(
59
59
  'Please, provide valid path to the configuration file'
60
60
  );
61
- expect(loadConfig).toHaveBeenCalledTimes(0);
61
+ expect(loadConfigAndHandleErrors).toHaveBeenCalledTimes(0);
62
62
  });
63
63
 
64
- it('should call loadConfig and getFallbackApisOrExit', async () => {
64
+ it('should call loadConfigAndHandleErrors and getFallbackApisOrExit', async () => {
65
65
  await handleLint(argvMock, versionMock);
66
- expect(loadConfig).toHaveBeenCalledWith({
66
+ expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
67
67
  configPath: undefined,
68
68
  customExtends: undefined,
69
69
  processRawConfig: undefined,
@@ -76,7 +76,7 @@ describe('handleLint', () => {
76
76
  { ...argvMock, config: 'redocly.yaml', extends: ['some/path'] },
77
77
  versionMock
78
78
  );
79
- expect(loadConfig).toHaveBeenCalledWith({
79
+ expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
80
80
  configPath: 'redocly.yaml',
81
81
  customExtends: ['some/path'],
82
82
  processRawConfig: undefined,
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
- import { Config, getMergedConfig, loadConfig } from '@redocly/openapi-core';
3
- import { exitWithError } from '../../utils';
2
+ import { Config, getMergedConfig } from '@redocly/openapi-core';
3
+ import { exitWithError, loadConfigAndHandleErrors } from '../../utils';
4
4
  import { getApiRoot, getDestinationProps, handlePush, transformPush } from '../../commands/push';
5
5
  import { ConfigFixture } from '../fixtures/config';
6
6
 
@@ -79,7 +79,7 @@ describe('push', () => {
79
79
  });
80
80
 
81
81
  it('push with --files', async () => {
82
- (loadConfig as jest.Mock).mockImplementation(({ files }) => {
82
+ (loadConfigAndHandleErrors as jest.Mock).mockImplementation(({ files }) => {
83
83
  return { ...ConfigFixture, files };
84
84
  });
85
85