mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-09 20:57:44 +00:00
chore: add command wrapper and telemetry (#1136)
This commit is contained in:
2
.github/workflows/performance.yaml
vendored
2
.github/workflows/performance.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- run: redocly-next --version
|
- run: redocly-next --version
|
||||||
- run: redocly --version
|
- run: redocly --version
|
||||||
- name: Run Benchmark
|
- name: Run Benchmark
|
||||||
run: hyperfine -i --warmup 3 'redocly lint packages/core/src/benchmark/benches/rebilly.yaml' 'redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml' --export-markdown benchmark_check.md --export-json benchmark_check.json
|
run: hyperfine -i --warmup 3 'REDOCLY_TELEMETRY=off redocly lint packages/core/src/benchmark/benches/rebilly.yaml' 'REDOCLY_TELEMETRY=off redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml' --export-markdown benchmark_check.md --export-json benchmark_check.json
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
- name: Comment PR
|
- name: Comment PR
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Options:
|
|||||||
--help Show help. [boolean]
|
--help Show help. [boolean]
|
||||||
--outDir Output directory where files will be saved. [string] [required]
|
--outDir Output directory where files will be saved. [string] [required]
|
||||||
--separator File path separator used while splitting. [string] [default: "_"]
|
--separator File path separator used while splitting. [string] [default: "_"]
|
||||||
|
--config Specify path to the config file. [string]
|
||||||
|
|
||||||
Missing required argument: outDir
|
Missing required argument: outDir
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ module.exports = {
|
|||||||
lines: 80,
|
lines: 80,
|
||||||
},
|
},
|
||||||
'packages/cli/': {
|
'packages/cli/': {
|
||||||
statements: 54,
|
statements: 55,
|
||||||
branches: 45,
|
branches: 46,
|
||||||
functions: 53,
|
functions: 55,
|
||||||
lines: 55,
|
lines: 55,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run typecheck && npm run unit",
|
"test": "npm run typecheck && npm run unit",
|
||||||
"jest": "jest ./packages",
|
"jest": "REDOCLY_TELEMETRY=off jest ./packages",
|
||||||
"unit": "npm run jest -- --coverage --coverageReporters lcov text-summary",
|
"unit": "npm run jest -- --coverage --coverageReporters lcov text-summary",
|
||||||
"coverage:cli": "jest --roots packages/cli/src --coverage",
|
"coverage:cli": "npm run jest -- --roots packages/cli/src --coverage",
|
||||||
"coverage:core": "jest --roots packages/core/src --coverage",
|
"coverage:core": "npm run jest -- --roots packages/core/src --coverage",
|
||||||
"typecheck": "tsc --noEmit --skipLibCheck",
|
"typecheck": "tsc --noEmit --skipLibCheck",
|
||||||
"e2e": "npm run webpack-bundle -- --mode=none && jest --roots=./__tests__/",
|
"e2e": "npm run webpack-bundle -- --mode=none && REDOCLY_TELEMETRY=off jest --roots=./__tests__/",
|
||||||
"prettier": "npx prettier --write \"**/*.{ts,js,yaml,json}\"",
|
"prettier": "npx prettier --write \"**/*.{ts,js,yaml,json}\"",
|
||||||
"prettier:check": "npx prettier --check \"**/*.{ts,js,yaml,json}\"",
|
"prettier:check": "npx prettier --check \"**/*.{ts,js,yaml,json}\"",
|
||||||
"eslint": "eslint packages/**",
|
"eslint": "eslint packages/**",
|
||||||
|
|||||||
@@ -44,16 +44,19 @@ describe('build-docs', () => {
|
|||||||
|
|
||||||
it('should work correctly when calling handlerBuildCommand', async () => {
|
it('should work correctly when calling handlerBuildCommand', async () => {
|
||||||
const processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
const processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
||||||
await handlerBuildCommand({
|
await handlerBuildCommand(
|
||||||
o: '',
|
{
|
||||||
cdn: false,
|
o: '',
|
||||||
title: 'test',
|
cdn: false,
|
||||||
disableGoogleFont: false,
|
title: 'test',
|
||||||
template: '',
|
disableGoogleFont: false,
|
||||||
templateOptions: {},
|
template: '',
|
||||||
theme: { openapi: {} },
|
templateOptions: {},
|
||||||
api: '../some-path/openapi.yaml',
|
theme: { openapi: {} },
|
||||||
} as BuildDocsArgv);
|
api: '../some-path/openapi.yaml',
|
||||||
|
} as BuildDocsArgv,
|
||||||
|
{} as any
|
||||||
|
);
|
||||||
expect(loadAndBundleSpec).toBeCalledTimes(1);
|
expect(loadAndBundleSpec).toBeCalledTimes(1);
|
||||||
expect(getFallbackApisOrExit).toBeCalledTimes(1);
|
expect(getFallbackApisOrExit).toBeCalledTimes(1);
|
||||||
expect(processExitMock).toBeCalledTimes(0);
|
expect(processExitMock).toBeCalledTimes(0);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { lint, bundle, getTotals, getMergedConfig } from '@redocly/openapi-core';
|
import { lint, bundle, getTotals, getMergedConfig } from '@redocly/openapi-core';
|
||||||
|
|
||||||
import { handleBundle } from '../../commands/bundle';
|
import { BundleOptions, handleBundle } from '../../commands/bundle';
|
||||||
import { handleError } from '../../utils';
|
import { handleError } from '../../utils';
|
||||||
|
import { commandWrapper } from '../../wrapper';
|
||||||
import SpyInstance = jest.SpyInstance;
|
import SpyInstance = jest.SpyInstance;
|
||||||
|
import { Arguments } from 'yargs';
|
||||||
|
|
||||||
jest.mock('@redocly/openapi-core');
|
jest.mock('@redocly/openapi-core');
|
||||||
jest.mock('../../utils');
|
jest.mock('../../utils');
|
||||||
@@ -31,14 +33,11 @@ describe('bundle', () => {
|
|||||||
it('bundles definitions w/o linting', async () => {
|
it('bundles definitions w/o linting', async () => {
|
||||||
const apis = ['foo.yaml', 'bar.yaml'];
|
const apis = ['foo.yaml', 'bar.yaml'];
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(lint).toBeCalledTimes(0);
|
expect(lint).toBeCalledTimes(0);
|
||||||
expect(bundle).toBeCalledTimes(apis.length);
|
expect(bundle).toBeCalledTimes(apis.length);
|
||||||
@@ -47,16 +46,13 @@ describe('bundle', () => {
|
|||||||
it('exits with code 0 when bundles definitions', async () => {
|
it('exits with code 0 when bundles definitions', async () => {
|
||||||
const apis = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
const apis = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
exitCb?.();
|
await exitCb?.();
|
||||||
expect(processExitMock).toHaveBeenCalledWith(0);
|
expect(processExitMock).toHaveBeenCalledWith(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,15 +65,12 @@ describe('bundle', () => {
|
|||||||
ignored: 0,
|
ignored: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
lint: true,
|
||||||
lint: true,
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(lint).toBeCalledTimes(apis.length);
|
expect(lint).toBeCalledTimes(apis.length);
|
||||||
expect(bundle).toBeCalledTimes(apis.length);
|
expect(bundle).toBeCalledTimes(apis.length);
|
||||||
@@ -86,17 +79,14 @@ describe('bundle', () => {
|
|||||||
it('exits with code 0 when bundles definitions w/linting w/o errors', async () => {
|
it('exits with code 0 when bundles definitions w/linting w/o errors', async () => {
|
||||||
const apis = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
const apis = ['foo.yaml', 'bar.yaml', 'foobar.yaml'];
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
lint: true,
|
||||||
lint: true,
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
exitCb?.();
|
await exitCb?.();
|
||||||
expect(processExitMock).toHaveBeenCalledWith(0);
|
expect(processExitMock).toHaveBeenCalledWith(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,18 +99,15 @@ describe('bundle', () => {
|
|||||||
ignored: 0,
|
ignored: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
lint: true,
|
||||||
lint: true,
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(lint).toBeCalledTimes(apis.length);
|
expect(lint).toBeCalledTimes(apis.length);
|
||||||
exitCb?.();
|
await exitCb?.();
|
||||||
expect(processExitMock).toHaveBeenCalledWith(1);
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,15 +118,12 @@ describe('bundle', () => {
|
|||||||
throw new Error('Invalid definition');
|
throw new Error('Invalid definition');
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'json',
|
||||||
ext: 'json',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
lint: false,
|
||||||
lint: false,
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(handleError).toHaveBeenCalledTimes(1);
|
expect(handleError).toHaveBeenCalledTimes(1);
|
||||||
expect(handleError).toHaveBeenCalledWith(new Error('Invalid definition'), 'invalid.json');
|
expect(handleError).toHaveBeenCalledWith(new Error('Invalid definition'), 'invalid.json');
|
||||||
@@ -154,15 +138,12 @@ describe('bundle', () => {
|
|||||||
ignored: 0,
|
ignored: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleBundle(
|
await commandWrapper(handleBundle)({
|
||||||
{
|
apis,
|
||||||
apis,
|
ext: 'yaml',
|
||||||
ext: 'yaml',
|
format: 'codeframe',
|
||||||
format: 'codeframe',
|
lint: false,
|
||||||
lint: false,
|
} as Arguments<BundleOptions>);
|
||||||
},
|
|
||||||
'1.0.0'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(handleError).toHaveBeenCalledTimes(0);
|
expect(handleError).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { exitWithError, writeYaml } from '../../utils';
|
|||||||
import { yellow } from 'colorette';
|
import { yellow } from 'colorette';
|
||||||
import { detectOpenAPI } from '@redocly/openapi-core';
|
import { detectOpenAPI } from '@redocly/openapi-core';
|
||||||
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
||||||
|
import { ConfigFixture } from '../fixtures/config';
|
||||||
|
|
||||||
jest.mock('../../utils');
|
jest.mock('../../utils');
|
||||||
jest.mock('colorette');
|
jest.mock('colorette');
|
||||||
@@ -12,7 +13,7 @@ describe('handleJoin fails', () => {
|
|||||||
colloreteYellowMock.mockImplementation((string: string) => string);
|
colloreteYellowMock.mockImplementation((string: string) => string);
|
||||||
|
|
||||||
it('should call exitWithError because only one entrypoint', async () => {
|
it('should call exitWithError because only one entrypoint', async () => {
|
||||||
await handleJoin({ apis: ['first.yaml'] }, 'cli-version');
|
await handleJoin({ apis: ['first.yaml'] }, {} as any, 'cli-version');
|
||||||
expect(exitWithError).toHaveBeenCalledWith(`At least 2 apis should be provided. \n\n`);
|
expect(exitWithError).toHaveBeenCalledWith(`At least 2 apis should be provided. \n\n`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ describe('handleJoin fails', () => {
|
|||||||
'without-x-tag-groups': true,
|
'without-x-tag-groups': true,
|
||||||
'prefix-tags-with-filename': true,
|
'prefix-tags-with-filename': true,
|
||||||
},
|
},
|
||||||
|
{} as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ describe('handleJoin fails', () => {
|
|||||||
'without-x-tag-groups': true,
|
'without-x-tag-groups': true,
|
||||||
'prefix-tags-with-filename': true,
|
'prefix-tags-with-filename': true,
|
||||||
},
|
},
|
||||||
|
{} as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ describe('handleJoin fails', () => {
|
|||||||
{
|
{
|
||||||
apis: ['first.yaml', 'second.yaml'],
|
apis: ['first.yaml', 'second.yaml'],
|
||||||
},
|
},
|
||||||
|
ConfigFixture as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
expect(exitWithError).toHaveBeenCalledWith('Only OpenAPI 3 is supported: undefined \n\n');
|
expect(exitWithError).toHaveBeenCalledWith('Only OpenAPI 3 is supported: undefined \n\n');
|
||||||
@@ -63,6 +67,7 @@ describe('handleJoin fails', () => {
|
|||||||
{
|
{
|
||||||
apis: ['first.yaml', 'second.yaml'],
|
apis: ['first.yaml', 'second.yaml'],
|
||||||
},
|
},
|
||||||
|
ConfigFixture as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -76,6 +81,7 @@ describe('handleJoin fails', () => {
|
|||||||
apis: ['first.yaml', 'second.yaml'],
|
apis: ['first.yaml', 'second.yaml'],
|
||||||
output: 'output.yml',
|
output: 'output.yml',
|
||||||
},
|
},
|
||||||
|
ConfigFixture as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -88,6 +94,7 @@ describe('handleJoin fails', () => {
|
|||||||
{
|
{
|
||||||
apis: ['first.yaml', 'second.yaml'],
|
apis: ['first.yaml', 'second.yaml'],
|
||||||
},
|
},
|
||||||
|
ConfigFixture as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -104,6 +111,7 @@ describe('handleJoin fails', () => {
|
|||||||
decorate: true,
|
decorate: true,
|
||||||
preprocess: true,
|
preprocess: true,
|
||||||
},
|
},
|
||||||
|
ConfigFixture as any,
|
||||||
'cli-version'
|
'cli-version'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,18 +17,23 @@ import {
|
|||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { ConfigFixture } from '../fixtures/config';
|
import { ConfigFixture } from '../fixtures/config';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
import { commandWrapper } from '../../wrapper';
|
||||||
|
import { Arguments } from 'yargs';
|
||||||
|
import { blue } from 'colorette';
|
||||||
|
|
||||||
jest.mock('@redocly/openapi-core');
|
jest.mock('@redocly/openapi-core');
|
||||||
jest.mock('../../utils');
|
jest.mock('../../utils');
|
||||||
jest.mock('perf_hooks');
|
jest.mock('perf_hooks');
|
||||||
|
|
||||||
const argvMock: LintOptions = {
|
jest.mock('../../update-version-notifier', () => ({
|
||||||
|
version: '1.0.0',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const argvMock = {
|
||||||
apis: ['openapi.yaml'],
|
apis: ['openapi.yaml'],
|
||||||
'lint-config': 'off',
|
'lint-config': 'off',
|
||||||
format: 'codeframe',
|
format: 'codeframe',
|
||||||
};
|
} as Arguments<LintOptions>;
|
||||||
|
|
||||||
const versionMock = '1.0.0';
|
|
||||||
|
|
||||||
describe('handleLint', () => {
|
describe('handleLint', () => {
|
||||||
let processExitMock: jest.SpyInstance;
|
let processExitMock: jest.SpyInstance;
|
||||||
@@ -55,15 +60,14 @@ describe('handleLint', () => {
|
|||||||
|
|
||||||
describe('loadConfig and getEnrtypoints stage', () => {
|
describe('loadConfig and getEnrtypoints stage', () => {
|
||||||
it('should fail if config file does not exist', async () => {
|
it('should fail if config file does not exist', async () => {
|
||||||
await handleLint({ ...argvMock, config: 'config.yaml' }, versionMock);
|
await commandWrapper(handleLint)({ ...argvMock, config: 'config.yaml' });
|
||||||
expect(exitWithError).toHaveBeenCalledWith(
|
expect(exitWithError).toHaveBeenCalledWith(
|
||||||
'Please, provide valid path to the configuration file'
|
'Please, provide valid path to the configuration file'
|
||||||
);
|
);
|
||||||
expect(loadConfigAndHandleErrors).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call loadConfigAndHandleErrors and getFallbackApisOrExit', async () => {
|
it('should call loadConfigAndHandleErrors and getFallbackApisOrExit', async () => {
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
|
expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
|
||||||
configPath: undefined,
|
configPath: undefined,
|
||||||
customExtends: undefined,
|
customExtends: undefined,
|
||||||
@@ -73,10 +77,11 @@ describe('handleLint', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call loadConfig with args if such exist', async () => {
|
it('should call loadConfig with args if such exist', async () => {
|
||||||
await handleLint(
|
await commandWrapper(handleLint)({
|
||||||
{ ...argvMock, config: 'redocly.yaml', extends: ['some/path'] },
|
...argvMock,
|
||||||
versionMock
|
config: 'redocly.yaml',
|
||||||
);
|
extends: ['some/path'],
|
||||||
|
});
|
||||||
expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
|
expect(loadConfigAndHandleErrors).toHaveBeenCalledWith({
|
||||||
configPath: 'redocly.yaml',
|
configPath: 'redocly.yaml',
|
||||||
customExtends: ['some/path'],
|
customExtends: ['some/path'],
|
||||||
@@ -85,25 +90,25 @@ describe('handleLint', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call mergedConfig with clear ignore if `generate-ignore-file` argv', async () => {
|
it('should call mergedConfig with clear ignore if `generate-ignore-file` argv', async () => {
|
||||||
await handleLint({ ...argvMock, 'generate-ignore-file': true }, versionMock);
|
await commandWrapper(handleLint)({ ...argvMock, 'generate-ignore-file': true });
|
||||||
expect(getMergedConfigMock).toHaveBeenCalled();
|
expect(getMergedConfigMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check if ruleset exist', async () => {
|
it('should check if ruleset exist', async () => {
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
expect(checkIfRulesetExist).toHaveBeenCalledTimes(1);
|
expect(checkIfRulesetExist).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if apis not provided', async () => {
|
it('should fail if apis not provided', async () => {
|
||||||
await handleLint({ ...argvMock, apis: [] }, versionMock);
|
await commandWrapper(handleLint)({ ...argvMock, apis: [] });
|
||||||
expect(getFallbackApisOrExit).toHaveBeenCalledTimes(1);
|
expect(getFallbackApisOrExit).toHaveBeenCalledTimes(1);
|
||||||
expect(exitWithError).toHaveBeenCalledWith('No APIs were provided');
|
expect(exitWithError).toHaveBeenCalledWith('No APIs were provided');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loop through entrypints and lint stage', () => {
|
describe('loop through entrypoints and lint stage', () => {
|
||||||
it('should call getMergedConfig and lint ', async () => {
|
it('should call getMergedConfig and lint ', async () => {
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
expect(performance.now).toHaveBeenCalled();
|
expect(performance.now).toHaveBeenCalled();
|
||||||
expect(getMergedConfigMock).toHaveBeenCalled();
|
expect(getMergedConfigMock).toHaveBeenCalled();
|
||||||
expect(lint).toHaveBeenCalled();
|
expect(lint).toHaveBeenCalled();
|
||||||
@@ -111,56 +116,75 @@ describe('handleLint', () => {
|
|||||||
|
|
||||||
it('should call skipRules,skipPreprocessors and addIgnore with argv', async () => {
|
it('should call skipRules,skipPreprocessors and addIgnore with argv', async () => {
|
||||||
(lint as jest.Mock<any, any>).mockResolvedValueOnce(['problem']);
|
(lint as jest.Mock<any, any>).mockResolvedValueOnce(['problem']);
|
||||||
await handleLint(
|
await commandWrapper(handleLint)({
|
||||||
{
|
...argvMock,
|
||||||
...argvMock,
|
'skip-preprocessor': ['preprocessor'],
|
||||||
'skip-preprocessor': ['preprocessor'],
|
'skip-rule': ['rule'],
|
||||||
'skip-rule': ['rule'],
|
'generate-ignore-file': true,
|
||||||
'generate-ignore-file': true,
|
});
|
||||||
},
|
|
||||||
versionMock
|
|
||||||
);
|
|
||||||
expect(ConfigFixture.styleguide.skipRules).toHaveBeenCalledWith(['rule']);
|
expect(ConfigFixture.styleguide.skipRules).toHaveBeenCalledWith(['rule']);
|
||||||
expect(ConfigFixture.styleguide.skipPreprocessors).toHaveBeenCalledWith(['preprocessor']);
|
expect(ConfigFixture.styleguide.skipPreprocessors).toHaveBeenCalledWith(['preprocessor']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call formatProblems and getExecutionTime with argv', async () => {
|
it('should call formatProblems and getExecutionTime with argv', async () => {
|
||||||
(lint as jest.Mock<any, any>).mockResolvedValueOnce(['problem']);
|
(lint as jest.Mock<any, any>).mockResolvedValueOnce(['problem']);
|
||||||
await handleLint({ ...argvMock, 'max-problems': 2, format: 'stylish' }, versionMock);
|
await commandWrapper(handleLint)({ ...argvMock, 'max-problems': 2, format: 'stylish' });
|
||||||
expect(getTotals).toHaveBeenCalledWith(['problem']);
|
expect(getTotals).toHaveBeenCalledWith(['problem']);
|
||||||
expect(formatProblems).toHaveBeenCalledWith(['problem'], {
|
expect(formatProblems).toHaveBeenCalledWith(['problem'], {
|
||||||
format: 'stylish',
|
format: 'stylish',
|
||||||
maxProblems: 2,
|
maxProblems: 2,
|
||||||
totals: { errors: 0 },
|
totals: { errors: 0 },
|
||||||
version: versionMock,
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
expect(getExecutionTime).toHaveBeenCalledWith(42);
|
expect(getExecutionTime).toHaveBeenCalledWith(42);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should catch error in handleError if something fails', async () => {
|
it('should catch error in handleError if something fails', async () => {
|
||||||
(lint as jest.Mock<any, any>).mockRejectedValueOnce('error');
|
(lint as jest.Mock<any, any>).mockRejectedValueOnce('error');
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
expect(handleError).toHaveBeenCalledWith('error', 'openapi.yaml');
|
expect(handleError).toHaveBeenCalledWith('error', 'openapi.yaml');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('erros and warning handle after lint stage', () => {
|
describe('erros and warning handle after lint stage', () => {
|
||||||
it('should call printLintTotals and printLintTotals', async () => {
|
it('should call printLintTotals and printLintTotals', async () => {
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
expect(printUnusedWarnings).toHaveBeenCalled();
|
expect(printUnusedWarnings).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call exit with 0 if no errors', async () => {
|
it('should call exit with 0 if no errors', async () => {
|
||||||
await handleLint(argvMock, versionMock);
|
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
||||||
exitCb?.();
|
return { ...ConfigFixture };
|
||||||
|
});
|
||||||
|
await commandWrapper(handleLint)(argvMock);
|
||||||
|
await exitCb?.();
|
||||||
expect(processExitMock).toHaveBeenCalledWith(0);
|
expect(processExitMock).toHaveBeenCalledWith(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exit with 1 if tootals error > 0', async () => {
|
it('should exit with 1 if total errors > 0', async () => {
|
||||||
(getTotals as jest.Mock<any, any>).mockReturnValueOnce({ errors: 1 });
|
(getTotals as jest.Mock<any, any>).mockReturnValueOnce({ errors: 1 });
|
||||||
await handleLint(argvMock, versionMock);
|
await commandWrapper(handleLint)(argvMock);
|
||||||
exitCb?.();
|
await exitCb?.();
|
||||||
expect(processExitMock).toHaveBeenCalledWith(1);
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use recommended fallback if no config', async () => {
|
||||||
|
(getMergedConfig as jest.Mock).mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
styleguide: {
|
||||||
|
recommendedFallback: true,
|
||||||
|
rules: {},
|
||||||
|
skipRules: jest.fn(),
|
||||||
|
skipPreprocessors: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await commandWrapper(handleLint)(argvMock);
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
|
`No configurations were provided -- using built in ${blue(
|
||||||
|
'recommended'
|
||||||
|
)} configuration by default.\n\n`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getMergedConfig } from '@redocly/openapi-core';
|
import { getMergedConfig } from '@redocly/openapi-core';
|
||||||
import { handlePush } from '../../commands/push';
|
import { handlePush } from '../../commands/push';
|
||||||
import { promptClientToken } from '../../commands/login';
|
import { promptClientToken } from '../../commands/login';
|
||||||
|
import { ConfigFixture } from '../fixtures/config';
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('fs');
|
||||||
jest.mock('node-fetch', () => ({
|
jest.mock('node-fetch', () => ({
|
||||||
@@ -27,24 +28,30 @@ describe('push-with-region', () => {
|
|||||||
|
|
||||||
it('should call login with default domain when region is US', async () => {
|
it('should call login with default domain when region is US', async () => {
|
||||||
redoclyClient.domain = 'redoc.ly';
|
redoclyClient.domain = 'redoc.ly';
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: '@org/my-api@1.0.0',
|
||||||
});
|
branchName: 'test',
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
expect(mockPromptClientToken).toBeCalledTimes(1);
|
expect(mockPromptClientToken).toBeCalledTimes(1);
|
||||||
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call login with EU domain when region is EU', async () => {
|
it('should call login with EU domain when region is EU', async () => {
|
||||||
redoclyClient.domain = 'eu.redocly.com';
|
redoclyClient.domain = 'eu.redocly.com';
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: '@org/my-api@1.0.0',
|
||||||
});
|
branchName: 'test',
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
expect(mockPromptClientToken).toBeCalledTimes(1);
|
expect(mockPromptClientToken).toBeCalledTimes(1);
|
||||||
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
expect(mockPromptClientToken).toHaveBeenCalledWith(redoclyClient.domain);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { Config, getMergedConfig } from '@redocly/openapi-core';
|
import { Config, getMergedConfig } from '@redocly/openapi-core';
|
||||||
import { exitWithError, loadConfigAndHandleErrors } from '../../utils';
|
import { exitWithError } from '../../utils';
|
||||||
import { getApiRoot, getDestinationProps, handlePush, transformPush } from '../../commands/push';
|
import { getApiRoot, getDestinationProps, handlePush, transformPush } from '../../commands/push';
|
||||||
import { ConfigFixture } from '../fixtures/config';
|
import { ConfigFixture } from '../fixtures/config';
|
||||||
import { yellow } from 'colorette';
|
import { yellow } from 'colorette';
|
||||||
@@ -25,15 +25,18 @@ describe('push', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('pushes definition', async () => {
|
it('pushes definition', async () => {
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: '@org/my-api@1.0.0',
|
||||||
public: true,
|
branchName: 'test',
|
||||||
'batch-id': '123',
|
public: true,
|
||||||
'batch-size': 2,
|
'batch-id': '123',
|
||||||
});
|
'batch-size': 2,
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
|
|
||||||
expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(1);
|
expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(1);
|
||||||
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
||||||
@@ -52,50 +55,61 @@ describe('push', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fails if batchId value is an empty string', async () => {
|
it('fails if batchId value is an empty string', async () => {
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: '@org/my-api@1.0.0',
|
||||||
public: true,
|
branchName: 'test',
|
||||||
'batch-id': ' ',
|
public: true,
|
||||||
'batch-size': 2,
|
'batch-id': ' ',
|
||||||
});
|
'batch-size': 2,
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
|
|
||||||
expect(exitWithError).toBeCalledTimes(1);
|
expect(exitWithError).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if batchSize value is less than 2', async () => {
|
it('fails if batchSize value is less than 2', async () => {
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: '@org/my-api@1.0.0',
|
||||||
public: true,
|
branchName: 'test',
|
||||||
'batch-id': '123',
|
public: true,
|
||||||
'batch-size': 1,
|
'batch-id': '123',
|
||||||
});
|
'batch-size': 1,
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
|
|
||||||
expect(exitWithError).toBeCalledTimes(1);
|
expect(exitWithError).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('push with --files', async () => {
|
it('push with --files', async () => {
|
||||||
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(({ files }) => {
|
// (loadConfigAndHandleErrors as jest.Mock).mockImplementation(({ files }) => {
|
||||||
return { ...ConfigFixture, files };
|
// return { ...ConfigFixture, files };
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
const mockConfig = { ...ConfigFixture, files: ['./resouces/1.md', './resouces/2.md'] } as any;
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
fs.statSync.mockImplementation(() => {
|
fs.statSync.mockImplementation(() => {
|
||||||
return { isDirectory: () => false, size: 10 };
|
return { isDirectory: () => false, size: 10 };
|
||||||
});
|
});
|
||||||
|
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: '@org/my-api@1.0.0',
|
api: 'spec.json',
|
||||||
public: true,
|
destination: '@org/my-api@1.0.0',
|
||||||
files: ['./resouces/1.md', './resouces/2.md'],
|
public: true,
|
||||||
});
|
files: ['./resouces/1.md', './resouces/2.md'],
|
||||||
|
},
|
||||||
|
mockConfig
|
||||||
|
);
|
||||||
|
|
||||||
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
||||||
filePaths: ['filePath', 'filePath', 'filePath'],
|
filePaths: ['filePath', 'filePath', 'filePath'],
|
||||||
@@ -110,15 +124,18 @@ describe('push', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('push should fail if organization not provided', async () => {
|
it('push should fail if organization not provided', async () => {
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
api: 'spec.json',
|
upsert: true,
|
||||||
destination: 'test@v1',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
destination: 'test@v1',
|
||||||
public: true,
|
branchName: 'test',
|
||||||
'batch-id': '123',
|
public: true,
|
||||||
'batch-size': 2,
|
'batch-id': '123',
|
||||||
});
|
'batch-size': 2,
|
||||||
|
},
|
||||||
|
ConfigFixture as any
|
||||||
|
);
|
||||||
|
|
||||||
expect(exitWithError).toBeCalledTimes(1);
|
expect(exitWithError).toBeCalledTimes(1);
|
||||||
expect(exitWithError).toBeCalledWith(
|
expect(exitWithError).toBeCalledWith(
|
||||||
@@ -129,18 +146,19 @@ describe('push', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('push should work with organization in config', async () => {
|
it('push should work with organization in config', async () => {
|
||||||
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
const mockConfig = { ...ConfigFixture, organization: 'test_org' } as any;
|
||||||
return { ...ConfigFixture, organization: 'test_org' };
|
await handlePush(
|
||||||
});
|
{
|
||||||
await handlePush({
|
upsert: true,
|
||||||
upsert: true,
|
api: 'spec.json',
|
||||||
api: 'spec.json',
|
destination: 'my-api@1.0.0',
|
||||||
destination: 'my-api@1.0.0',
|
branchName: 'test',
|
||||||
branchName: 'test',
|
public: true,
|
||||||
public: true,
|
'batch-id': '123',
|
||||||
'batch-id': '123',
|
'batch-size': 2,
|
||||||
'batch-size': 2,
|
},
|
||||||
});
|
mockConfig
|
||||||
|
);
|
||||||
|
|
||||||
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
||||||
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
|
||||||
@@ -158,36 +176,40 @@ describe('push', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('push should work if destination not provided and api in config is provided', async () => {
|
it('push should work if destination not provided and api in config is provided', async () => {
|
||||||
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
const mockConfig = {
|
||||||
return {
|
...ConfigFixture,
|
||||||
...ConfigFixture,
|
organization: 'test_org',
|
||||||
organization: 'test_org',
|
apis: { 'my-api@1.0.0': { root: 'path' } },
|
||||||
apis: { 'my-api@1.0.0': { root: 'path' } },
|
} as any;
|
||||||
};
|
|
||||||
});
|
await handlePush(
|
||||||
await handlePush({
|
{
|
||||||
upsert: true,
|
upsert: true,
|
||||||
api: 'spec.json',
|
api: 'spec.json',
|
||||||
branchName: 'test',
|
branchName: 'test',
|
||||||
public: true,
|
public: true,
|
||||||
'batch-id': '123',
|
'batch-id': '123',
|
||||||
'batch-size': 2,
|
'batch-size': 2,
|
||||||
});
|
},
|
||||||
|
mockConfig
|
||||||
|
);
|
||||||
|
|
||||||
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('push should fail if destination and apis not provided', async () => {
|
it('push should fail if destination and apis not provided', async () => {
|
||||||
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
const mockConfig = { organization: 'test_org', apis: {} } as any;
|
||||||
return { organization: 'test_org', apis: {} };
|
|
||||||
});
|
await handlePush(
|
||||||
await handlePush({
|
{
|
||||||
upsert: true,
|
upsert: true,
|
||||||
branchName: 'test',
|
branchName: 'test',
|
||||||
public: true,
|
public: true,
|
||||||
'batch-id': '123',
|
'batch-id': '123',
|
||||||
'batch-size': 2,
|
'batch-size': 2,
|
||||||
});
|
},
|
||||||
|
mockConfig
|
||||||
|
);
|
||||||
|
|
||||||
expect(exitWithError).toBeCalledTimes(1);
|
expect(exitWithError).toBeCalledTimes(1);
|
||||||
expect(exitWithError).toHaveBeenLastCalledWith(
|
expect(exitWithError).toHaveBeenLastCalledWith(
|
||||||
@@ -197,21 +219,24 @@ describe('push', () => {
|
|||||||
|
|
||||||
it('push should work and encode name with spaces', async () => {
|
it('push should work and encode name with spaces', async () => {
|
||||||
const encodeURIComponentSpy = jest.spyOn(global, 'encodeURIComponent');
|
const encodeURIComponentSpy = jest.spyOn(global, 'encodeURIComponent');
|
||||||
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
|
||||||
return {
|
const mockConfig = {
|
||||||
...ConfigFixture,
|
...ConfigFixture,
|
||||||
organization: 'test_org',
|
organization: 'test_org',
|
||||||
apis: { 'my test api@v1': { root: 'path' } },
|
apis: { 'my test api@v1': { root: 'path' } },
|
||||||
};
|
} as any;
|
||||||
});
|
|
||||||
await handlePush({
|
await handlePush(
|
||||||
upsert: true,
|
{
|
||||||
destination: 'my test api@v1',
|
upsert: true,
|
||||||
branchName: 'test',
|
destination: 'my test api@v1',
|
||||||
public: true,
|
branchName: 'test',
|
||||||
'batch-id': '123',
|
public: true,
|
||||||
'batch-size': 2,
|
'batch-id': '123',
|
||||||
});
|
'batch-size': 2,
|
||||||
|
},
|
||||||
|
mockConfig
|
||||||
|
);
|
||||||
|
|
||||||
expect(encodeURIComponentSpy).toHaveReturnedWith('my%20test%20api');
|
expect(encodeURIComponentSpy).toHaveReturnedWith('my%20test%20api');
|
||||||
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
||||||
@@ -221,66 +246,96 @@ describe('push', () => {
|
|||||||
describe('transformPush', () => {
|
describe('transformPush', () => {
|
||||||
it('should adapt the existing syntax', () => {
|
it('should adapt the existing syntax', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({
|
transformPush(cb)(
|
||||||
maybeApiOrDestination: 'openapi.yaml',
|
{
|
||||||
maybeDestination: '@testing_org/main@v1',
|
maybeApiOrDestination: 'openapi.yaml',
|
||||||
});
|
maybeDestination: '@testing_org/main@v1',
|
||||||
expect(cb).toBeCalledWith({
|
},
|
||||||
api: 'openapi.yaml',
|
{} as any
|
||||||
destination: '@testing_org/main@v1',
|
);
|
||||||
});
|
expect(cb).toBeCalledWith(
|
||||||
|
{
|
||||||
|
api: 'openapi.yaml',
|
||||||
|
destination: '@testing_org/main@v1',
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should adapt the existing syntax (including branchName)', () => {
|
it('should adapt the existing syntax (including branchName)', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({
|
transformPush(cb)(
|
||||||
maybeApiOrDestination: 'openapi.yaml',
|
{
|
||||||
maybeDestination: '@testing_org/main@v1',
|
maybeApiOrDestination: 'openapi.yaml',
|
||||||
maybeBranchName: 'other',
|
maybeDestination: '@testing_org/main@v1',
|
||||||
});
|
maybeBranchName: 'other',
|
||||||
expect(cb).toBeCalledWith({
|
},
|
||||||
api: 'openapi.yaml',
|
{} as any
|
||||||
destination: '@testing_org/main@v1',
|
);
|
||||||
branchName: 'other',
|
expect(cb).toBeCalledWith(
|
||||||
});
|
{
|
||||||
|
api: 'openapi.yaml',
|
||||||
|
destination: '@testing_org/main@v1',
|
||||||
|
branchName: 'other',
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should use --branch option firstly', () => {
|
it('should use --branch option firstly', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({
|
transformPush(cb)(
|
||||||
maybeApiOrDestination: 'openapi.yaml',
|
{
|
||||||
maybeDestination: '@testing_org/main@v1',
|
maybeApiOrDestination: 'openapi.yaml',
|
||||||
maybeBranchName: 'other',
|
maybeDestination: '@testing_org/main@v1',
|
||||||
branch: 'priority-branch',
|
maybeBranchName: 'other',
|
||||||
});
|
branch: 'priority-branch',
|
||||||
expect(cb).toBeCalledWith({
|
},
|
||||||
api: 'openapi.yaml',
|
{} as any
|
||||||
destination: '@testing_org/main@v1',
|
);
|
||||||
branchName: 'priority-branch',
|
expect(cb).toBeCalledWith(
|
||||||
});
|
{
|
||||||
|
api: 'openapi.yaml',
|
||||||
|
destination: '@testing_org/main@v1',
|
||||||
|
branchName: 'priority-branch',
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should work for a destination only', () => {
|
it('should work for a destination only', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({
|
transformPush(cb)(
|
||||||
maybeApiOrDestination: '@testing_org/main@v1',
|
{
|
||||||
});
|
maybeApiOrDestination: '@testing_org/main@v1',
|
||||||
expect(cb).toBeCalledWith({
|
},
|
||||||
destination: '@testing_org/main@v1',
|
{} as any
|
||||||
});
|
);
|
||||||
|
expect(cb).toBeCalledWith(
|
||||||
|
{
|
||||||
|
destination: '@testing_org/main@v1',
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should accept aliases for the old syntax', () => {
|
it('should accept aliases for the old syntax', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({
|
transformPush(cb)(
|
||||||
maybeApiOrDestination: 'alias',
|
{
|
||||||
maybeDestination: '@testing_org/main@v1',
|
maybeApiOrDestination: 'alias',
|
||||||
});
|
maybeDestination: '@testing_org/main@v1',
|
||||||
expect(cb).toBeCalledWith({
|
},
|
||||||
destination: '@testing_org/main@v1',
|
{} as any
|
||||||
api: 'alias',
|
);
|
||||||
});
|
expect(cb).toBeCalledWith(
|
||||||
|
{
|
||||||
|
destination: '@testing_org/main@v1',
|
||||||
|
api: 'alias',
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should accept no arguments at all', () => {
|
it('should accept no arguments at all', () => {
|
||||||
const cb = jest.fn();
|
const cb = jest.fn();
|
||||||
transformPush(cb)({});
|
transformPush(cb)({}, {} as any);
|
||||||
expect(cb).toBeCalledWith({});
|
expect(cb).toBeCalledWith({}, {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import {
|
|||||||
CircularJSONNotSupportedError,
|
CircularJSONNotSupportedError,
|
||||||
sortTopLevelKeysForOas,
|
sortTopLevelKeysForOas,
|
||||||
cleanColors,
|
cleanColors,
|
||||||
|
HandledError,
|
||||||
|
cleanArgs,
|
||||||
|
cleanRawInput,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
ResolvedApi,
|
ResolvedApi,
|
||||||
@@ -24,6 +27,7 @@ import * as process from 'process';
|
|||||||
|
|
||||||
jest.mock('os');
|
jest.mock('os');
|
||||||
jest.mock('colorette');
|
jest.mock('colorette');
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('fs');
|
||||||
|
|
||||||
describe('isSubdir', () => {
|
describe('isSubdir', () => {
|
||||||
@@ -150,23 +154,34 @@ describe('getFallbackApisOrExit', () => {
|
|||||||
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||||
jest.spyOn(process, 'exit').mockImplementation();
|
jest.spyOn(process, 'exit').mockImplementation();
|
||||||
});
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should exit with error because no path provided', async () => {
|
it('should exit with error because no path provided', async () => {
|
||||||
const apisConfig = {
|
const apisConfig = {
|
||||||
apis: {},
|
apis: {},
|
||||||
};
|
};
|
||||||
await getFallbackApisOrExit([''], apisConfig);
|
expect.assertions(1);
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
try {
|
||||||
|
await getFallbackApisOrExit([''], apisConfig);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toEqual('Path cannot be empty.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error if file from config do not exist', async () => {
|
it('should error if file from config do not exist', async () => {
|
||||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
||||||
await getFallbackApisOrExit(undefined, config);
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
await getFallbackApisOrExit(undefined, config);
|
||||||
'\n someFile.yaml does not exist or is invalid. Please provide a valid path. \n\n'
|
} catch (e) {
|
||||||
);
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
'\nsomeFile.yaml does not exist or is invalid.\n\n'
|
||||||
|
);
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
|
||||||
|
expect(e.message).toEqual('Please provide a valid path.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return valid array with results if such file exist', async () => {
|
it('should return valid array with results if such file exist', async () => {
|
||||||
@@ -189,12 +204,17 @@ describe('getFallbackApisOrExit', () => {
|
|||||||
apis: {},
|
apis: {},
|
||||||
};
|
};
|
||||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
||||||
await getFallbackApisOrExit(['someFile.yaml'], apisConfig);
|
expect.assertions(3);
|
||||||
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
try {
|
||||||
'\n someFile.yaml does not exist or is invalid. Please provide a valid path. \n\n'
|
await getFallbackApisOrExit(['someFile.yaml'], apisConfig);
|
||||||
);
|
} catch (e) {
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
|
'\nsomeFile.yaml does not exist or is invalid.\n\n'
|
||||||
|
);
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
|
||||||
|
expect(e.message).toEqual('Please provide a valid path.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exit with error in case if invalid 2 path provided as args', async () => {
|
it('should exit with error in case if invalid 2 path provided as args', async () => {
|
||||||
@@ -202,12 +222,16 @@ describe('getFallbackApisOrExit', () => {
|
|||||||
apis: {},
|
apis: {},
|
||||||
};
|
};
|
||||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => false);
|
||||||
await getFallbackApisOrExit(['someFile.yaml', 'someFile2.yaml'], apisConfig);
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
expect(process.stderr.write).lastCalledWith(
|
await getFallbackApisOrExit(['someFile.yaml', 'someFile2.yaml'], apisConfig);
|
||||||
'\n someFile2.yaml does not exist or is invalid. Please provide a valid path. \n\n'
|
} catch (e) {
|
||||||
);
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
'\nsomeFile.yaml does not exist or is invalid.\n\n'
|
||||||
|
);
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
|
||||||
|
expect(e.message).toEqual('Please provide a valid path.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exit with error if only one file exist ', async () => {
|
it('should exit with error if only one file exist ', async () => {
|
||||||
@@ -220,14 +244,23 @@ describe('getFallbackApisOrExit', () => {
|
|||||||
};
|
};
|
||||||
const configStub = { apis: apisStub };
|
const configStub = { apis: apisStub };
|
||||||
|
|
||||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce((path) => path === 'someFile.yaml');
|
const existSyncMock = (existsSync as jest.Mock<any, any>).mockImplementation((path) =>
|
||||||
|
path.endsWith('someFile.yaml')
|
||||||
await getFallbackApisOrExit(undefined, configStub);
|
|
||||||
|
|
||||||
expect(process.stderr.write).toBeCalledWith(
|
|
||||||
'\n notExist.yaml does not exist or is invalid. Please provide a valid path. \n\n'
|
|
||||||
);
|
);
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
|
||||||
|
expect.assertions(4);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await getFallbackApisOrExit(undefined, configStub);
|
||||||
|
} catch (e) {
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
|
'\nnotExist.yaml does not exist or is invalid.\n\n'
|
||||||
|
);
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
|
||||||
|
expect(process.stderr.write).toHaveBeenCalledTimes(2);
|
||||||
|
expect(e.message).toEqual('Please provide a valid path.');
|
||||||
|
}
|
||||||
|
existSyncMock.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work ok if it is url passed', async () => {
|
it('should work ok if it is url passed', async () => {
|
||||||
@@ -245,7 +278,6 @@ describe('getFallbackApisOrExit', () => {
|
|||||||
const result = await getFallbackApisOrExit(undefined, apisConfig);
|
const result = await getFallbackApisOrExit(undefined, apisConfig);
|
||||||
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledTimes(0);
|
expect(process.stderr.write).toHaveBeenCalledTimes(0);
|
||||||
expect(process.exit).toHaveBeenCalledTimes(0);
|
|
||||||
expect(result).toStrictEqual([
|
expect(result).toStrictEqual([
|
||||||
{
|
{
|
||||||
alias: 'main',
|
alias: 'main',
|
||||||
@@ -356,11 +388,13 @@ describe('handleErrors', () => {
|
|||||||
const ref = 'openapi/test.yaml';
|
const ref = 'openapi/test.yaml';
|
||||||
|
|
||||||
const redColoretteMocks = red as jest.Mock<any, any>;
|
const redColoretteMocks = red as jest.Mock<any, any>;
|
||||||
|
const blueColoretteMocks = blue as jest.Mock<any, any>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||||
jest.spyOn(process, 'exit').mockImplementation((code) => code as never);
|
jest.spyOn(process, 'exit').mockImplementation((code) => code as never);
|
||||||
redColoretteMocks.mockImplementation((text) => text);
|
redColoretteMocks.mockImplementation((text) => text);
|
||||||
|
blueColoretteMocks.mockImplementation((text) => text);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -369,9 +403,8 @@ describe('handleErrors', () => {
|
|||||||
|
|
||||||
it('should handle ResolveError', () => {
|
it('should handle ResolveError', () => {
|
||||||
const resolveError = new ResolveError(new Error('File not found'));
|
const resolveError = new ResolveError(new Error('File not found'));
|
||||||
handleError(resolveError, ref);
|
expect(() => handleError(resolveError, ref)).toThrowError(HandledError);
|
||||||
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
`Failed to resolve api definition at openapi/test.yaml:\n\n - File not found.\n\n`
|
`Failed to resolve api definition at openapi/test.yaml:\n\n - File not found.\n\n`
|
||||||
);
|
);
|
||||||
@@ -379,9 +412,8 @@ describe('handleErrors', () => {
|
|||||||
|
|
||||||
it('should handle YamlParseError', () => {
|
it('should handle YamlParseError', () => {
|
||||||
const yamlParseError = new YamlParseError(new Error('Invalid yaml'), {} as any);
|
const yamlParseError = new YamlParseError(new Error('Invalid yaml'), {} as any);
|
||||||
handleError(yamlParseError, ref);
|
expect(() => handleError(yamlParseError, ref)).toThrowError(HandledError);
|
||||||
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
`Failed to parse api definition at openapi/test.yaml:\n\n - Invalid yaml.\n\n`
|
`Failed to parse api definition at openapi/test.yaml:\n\n - Invalid yaml.\n\n`
|
||||||
);
|
);
|
||||||
@@ -389,8 +421,7 @@ describe('handleErrors', () => {
|
|||||||
|
|
||||||
it('should handle CircularJSONNotSupportedError', () => {
|
it('should handle CircularJSONNotSupportedError', () => {
|
||||||
const circularError = new CircularJSONNotSupportedError(new Error('Circular json'));
|
const circularError = new CircularJSONNotSupportedError(new Error('Circular json'));
|
||||||
handleError(circularError, ref);
|
expect(() => handleError(circularError, ref)).toThrowError(HandledError);
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
`Detected circular reference which can't be converted to JSON.\n` +
|
`Detected circular reference which can't be converted to JSON.\n` +
|
||||||
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.\n\n`
|
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.\n\n`
|
||||||
@@ -400,12 +431,7 @@ describe('handleErrors', () => {
|
|||||||
it('should handle SyntaxError', () => {
|
it('should handle SyntaxError', () => {
|
||||||
const testError = new SyntaxError('Unexpected identifier');
|
const testError = new SyntaxError('Unexpected identifier');
|
||||||
testError.stack = 'test stack';
|
testError.stack = 'test stack';
|
||||||
try {
|
expect(() => handleError(testError, ref)).toThrowError(HandledError);
|
||||||
handleError(testError, ref);
|
|
||||||
} catch (e) {
|
|
||||||
expect(e).toEqual(testError);
|
|
||||||
}
|
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
'Syntax error: Unexpected identifier test stack\n\n'
|
'Syntax error: Unexpected identifier test stack\n\n'
|
||||||
);
|
);
|
||||||
@@ -413,11 +439,7 @@ describe('handleErrors', () => {
|
|||||||
|
|
||||||
it('should throw unknown error', () => {
|
it('should throw unknown error', () => {
|
||||||
const testError = new Error('Test error');
|
const testError = new Error('Test error');
|
||||||
try {
|
expect(() => handleError(testError, ref)).toThrowError(HandledError);
|
||||||
handleError(testError, ref);
|
|
||||||
} catch (e) {
|
|
||||||
expect(e).toEqual(testError);
|
|
||||||
}
|
|
||||||
expect(process.stderr.write).toHaveBeenCalledWith(
|
expect(process.stderr.write).toHaveBeenCalledWith(
|
||||||
`Something went wrong when processing openapi/test.yaml:\n\n - Test error.\n\n`
|
`Something went wrong when processing openapi/test.yaml:\n\n - Test error.\n\n`
|
||||||
);
|
);
|
||||||
@@ -433,24 +455,24 @@ describe('checkIfRulesetExist', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exit if rules not provided', () => {
|
it('should throw an error if rules are not provided', () => {
|
||||||
const rules = {
|
const rules = {
|
||||||
oas2: {},
|
oas2: {},
|
||||||
oas3_0: {},
|
oas3_0: {},
|
||||||
oas3_1: {},
|
oas3_1: {},
|
||||||
};
|
};
|
||||||
checkIfRulesetExist(rules);
|
expect(() => checkIfRulesetExist(rules)).toThrowError(
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
'⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not exit if rules provided', () => {
|
it('should not throw an error if rules are provided', () => {
|
||||||
const rules = {
|
const rules = {
|
||||||
oas2: { 'operation-4xx-response': 'error' },
|
oas2: { 'operation-4xx-response': 'error' },
|
||||||
oas3_0: {},
|
oas3_0: {},
|
||||||
oas3_1: {},
|
oas3_1: {},
|
||||||
} as any;
|
} as any;
|
||||||
checkIfRulesetExist(rules);
|
checkIfRulesetExist(rules);
|
||||||
expect(process.exit).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -462,3 +484,49 @@ describe('cleanColors', () => {
|
|||||||
expect(result).not.toMatch(/\x1b\[\d+m/g);
|
expect(result).not.toMatch(/\x1b\[\d+m/g);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cleanArgs', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
isAbsoluteUrl = jest.requireActual('@redocly/openapi-core').isAbsoluteUrl;
|
||||||
|
});
|
||||||
|
it('should remove potentially sensitive data from args', () => {
|
||||||
|
const testArgs = {
|
||||||
|
config: 'some-folder/redocly.yaml',
|
||||||
|
apis: ['main@v1', 'openapi.yaml', 'http://some.url/openapi.yaml'],
|
||||||
|
format: 'codeframe',
|
||||||
|
};
|
||||||
|
expect(cleanArgs(testArgs)).toEqual({
|
||||||
|
config: '***.yaml',
|
||||||
|
apis: ['main@v1', '***.yaml', 'http://***'],
|
||||||
|
format: 'codeframe',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should remove potentially sensitive data from a push destination', () => {
|
||||||
|
const testArgs = {
|
||||||
|
destination: '@org/name@version',
|
||||||
|
};
|
||||||
|
expect(cleanArgs(testArgs)).toEqual({
|
||||||
|
destination: '@***/name@version',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleanRawInput', () => {
|
||||||
|
it('should remove potentially sensitive data from raw CLI input', () => {
|
||||||
|
// @ts-ignore
|
||||||
|
isAbsoluteUrl = jest.requireActual('@redocly/openapi-core').isAbsoluteUrl;
|
||||||
|
|
||||||
|
const rawInput = [
|
||||||
|
'redocly',
|
||||||
|
'lint',
|
||||||
|
'main@v1',
|
||||||
|
'openapi.yaml',
|
||||||
|
'http://some.url/openapi.yaml',
|
||||||
|
'--config=some-folder/redocly.yaml',
|
||||||
|
];
|
||||||
|
expect(cleanRawInput(rawInput)).toEqual(
|
||||||
|
'redocly lint main@v1 ***.yaml http://*** --config=***.yaml'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
43
packages/cli/src/__tests__/wrapper.test.ts
Normal file
43
packages/cli/src/__tests__/wrapper.test.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { loadConfigAndHandleErrors, sendTelemetry } from '../utils';
|
||||||
|
import * as process from 'process';
|
||||||
|
import { commandWrapper } from '../wrapper';
|
||||||
|
import { handleLint } from '../commands/lint';
|
||||||
|
import nodeFetch from 'node-fetch';
|
||||||
|
|
||||||
|
jest.mock('node-fetch');
|
||||||
|
jest.mock('../utils', () => ({
|
||||||
|
sendTelemetry: jest.fn(),
|
||||||
|
loadConfigAndHandleErrors: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('../commands/lint', () => ({
|
||||||
|
handleLint: jest.fn(),
|
||||||
|
lintConfigCallback: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('commandWrapper', () => {
|
||||||
|
it('should send telemetry if there is "telemetry: on" in the config', async () => {
|
||||||
|
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
||||||
|
return { telemetry: 'on', styleguide: { recommendedFallback: true } };
|
||||||
|
});
|
||||||
|
process.env.REDOCLY_TELEMETRY = 'on';
|
||||||
|
|
||||||
|
const wrappedHandler = commandWrapper(handleLint);
|
||||||
|
await wrappedHandler({} as any);
|
||||||
|
expect(handleLint).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendTelemetry).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendTelemetry).toHaveBeenCalledWith({}, 0, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT send telemetry if there is "telemetry: off" in the config', async () => {
|
||||||
|
(loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
|
||||||
|
return { telemetry: 'off', styleguide: { recommendedFallback: true } };
|
||||||
|
});
|
||||||
|
process.env.REDOCLY_TELEMETRY = 'on';
|
||||||
|
|
||||||
|
const wrappedHandler = commandWrapper(handleLint);
|
||||||
|
await wrappedHandler({} as any);
|
||||||
|
expect(handleLint).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(sendTelemetry).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,18 +5,12 @@ import { performance } from 'perf_hooks';
|
|||||||
|
|
||||||
import { getObjectOrJSON, getPageHTML } from './utils';
|
import { getObjectOrJSON, getPageHTML } from './utils';
|
||||||
import type { BuildDocsArgv } from './types';
|
import type { BuildDocsArgv } from './types';
|
||||||
import { getMergedConfig, isAbsoluteUrl } from '@redocly/openapi-core';
|
import { Config, getMergedConfig, isAbsoluteUrl } from '@redocly/openapi-core';
|
||||||
import {
|
import { exitWithError, getExecutionTime, getFallbackApisOrExit } from '../../utils';
|
||||||
exitWithError,
|
|
||||||
getExecutionTime,
|
|
||||||
getFallbackApisOrExit,
|
|
||||||
loadConfigAndHandleErrors,
|
|
||||||
} from '../../utils';
|
|
||||||
|
|
||||||
export const handlerBuildCommand = async (argv: BuildDocsArgv) => {
|
export const handlerBuildCommand = async (argv: BuildDocsArgv, configFromFile: Config) => {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
|
|
||||||
const configFromFile = await loadConfigAndHandleErrors({ configPath: argv.config });
|
|
||||||
const config = getMergedConfig(configFromFile, argv.api);
|
const config = getMergedConfig(configFromFile, argv.api);
|
||||||
|
|
||||||
const apis = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
const apis = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { formatProblems, getTotals, getMergedConfig, lint, bundle } from '@redocly/openapi-core';
|
import {
|
||||||
|
formatProblems,
|
||||||
|
getTotals,
|
||||||
|
getMergedConfig,
|
||||||
|
lint,
|
||||||
|
bundle,
|
||||||
|
Config,
|
||||||
|
OutputFormat,
|
||||||
|
} from '@redocly/openapi-core';
|
||||||
import {
|
import {
|
||||||
dumpBundle,
|
dumpBundle,
|
||||||
getExecutionTime,
|
getExecutionTime,
|
||||||
@@ -8,32 +16,31 @@ import {
|
|||||||
printUnusedWarnings,
|
printUnusedWarnings,
|
||||||
saveBundle,
|
saveBundle,
|
||||||
printLintTotals,
|
printLintTotals,
|
||||||
loadConfigAndHandleErrors,
|
|
||||||
checkIfRulesetExist,
|
checkIfRulesetExist,
|
||||||
sortTopLevelKeysForOas,
|
sortTopLevelKeysForOas,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import type { CommonOptions, OutputExtensions, Skips, Totals } from '../types';
|
import type { OutputExtensions, Skips, Totals } from '../types';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { blue, gray, green, yellow } from 'colorette';
|
import { blue, gray, green, yellow } from 'colorette';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
|
|
||||||
export type BundleOptions = CommonOptions &
|
export type BundleOptions = {
|
||||||
Skips & {
|
apis?: string[];
|
||||||
output?: string;
|
'max-problems': number;
|
||||||
ext: OutputExtensions;
|
extends?: string[];
|
||||||
dereferenced?: boolean;
|
config?: string;
|
||||||
force?: boolean;
|
format: OutputFormat;
|
||||||
lint?: boolean;
|
output?: string;
|
||||||
metafile?: string;
|
ext: OutputExtensions;
|
||||||
'remove-unused-components'?: boolean;
|
dereferenced?: boolean;
|
||||||
'keep-url-references'?: boolean;
|
force?: boolean;
|
||||||
};
|
lint?: boolean;
|
||||||
|
metafile?: string;
|
||||||
|
'remove-unused-components'?: boolean;
|
||||||
|
'keep-url-references'?: boolean;
|
||||||
|
} & Skips;
|
||||||
|
|
||||||
export async function handleBundle(argv: BundleOptions, version: string) {
|
export async function handleBundle(argv: BundleOptions, config: Config, version: string) {
|
||||||
const config = await loadConfigAndHandleErrors({
|
|
||||||
configPath: argv.config,
|
|
||||||
customExtends: argv.extends,
|
|
||||||
});
|
|
||||||
const removeUnusedComponents =
|
const removeUnusedComponents =
|
||||||
argv['remove-unused-components'] ||
|
argv['remove-unused-components'] ||
|
||||||
config.rawConfig?.styleguide?.decorators?.hasOwnProperty('remove-unused-components');
|
config.rawConfig?.styleguide?.decorators?.hasOwnProperty('remove-unused-components');
|
||||||
@@ -164,7 +171,7 @@ export async function handleBundle(argv: BundleOptions, version: string) {
|
|||||||
|
|
||||||
printUnusedWarnings(config.styleguide);
|
printUnusedWarnings(config.styleguide);
|
||||||
|
|
||||||
// defer process exit to allow STDOUT pipe to flush
|
if (!(totals.errors === 0 || argv.force)) {
|
||||||
// see https://github.com/nodejs/node-v0.x-archive/issues/3737#issuecomment-19156072
|
throw new Error('Bundle failed.');
|
||||||
process.once('exit', () => process.exit(totals.errors === 0 || argv.force ? 0 : 1));
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
printLintTotals,
|
printLintTotals,
|
||||||
writeYaml,
|
writeYaml,
|
||||||
exitWithError,
|
exitWithError,
|
||||||
loadConfigAndHandleErrors,
|
|
||||||
sortTopLevelKeysForOas,
|
sortTopLevelKeysForOas,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { isObject, isString, keysOf } from '../js-utils';
|
import { isObject, isString, keysOf } from '../js-utils';
|
||||||
@@ -48,7 +47,7 @@ type JoinDocumentContext = {
|
|||||||
componentsPrefix: string | undefined;
|
componentsPrefix: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type JoinArgv = {
|
export type JoinOptions = {
|
||||||
apis: string[];
|
apis: string[];
|
||||||
lint?: boolean;
|
lint?: boolean;
|
||||||
decorate?: boolean;
|
decorate?: boolean;
|
||||||
@@ -58,9 +57,12 @@ type JoinArgv = {
|
|||||||
'prefix-components-with-info-prop'?: string;
|
'prefix-components-with-info-prop'?: string;
|
||||||
'without-x-tag-groups'?: boolean;
|
'without-x-tag-groups'?: boolean;
|
||||||
output?: string;
|
output?: string;
|
||||||
|
config?: string;
|
||||||
|
extends?: undefined;
|
||||||
|
'lint-config'?: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
if (argv.apis.length < 2) {
|
if (argv.apis.length < 2) {
|
||||||
return exitWithError(`At least 2 apis should be provided. \n\n`);
|
return exitWithError(`At least 2 apis should be provided. \n\n`);
|
||||||
@@ -86,7 +88,6 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Config = await loadConfigAndHandleErrors();
|
|
||||||
const apis = await getFallbackApisOrExit(argv.apis, config);
|
const apis = await getFallbackApisOrExit(argv.apis, config);
|
||||||
const externalRefResolver = new BaseResolver(config.resolve);
|
const externalRefResolver = new BaseResolver(config.resolve);
|
||||||
const documents = await Promise.all(
|
const documents = await Promise.all(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
doesYamlFileExist,
|
|
||||||
findConfig,
|
findConfig,
|
||||||
formatProblems,
|
formatProblems,
|
||||||
getMergedConfig,
|
getMergedConfig,
|
||||||
@@ -8,9 +7,6 @@ import {
|
|||||||
lint,
|
lint,
|
||||||
lintConfig,
|
lintConfig,
|
||||||
makeDocumentFromString,
|
makeDocumentFromString,
|
||||||
ProblemSeverity,
|
|
||||||
RawConfig,
|
|
||||||
RuleSeverity,
|
|
||||||
stringifyYaml,
|
stringifyYaml,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
import {
|
import {
|
||||||
@@ -19,37 +15,31 @@ import {
|
|||||||
getExecutionTime,
|
getExecutionTime,
|
||||||
getFallbackApisOrExit,
|
getFallbackApisOrExit,
|
||||||
handleError,
|
handleError,
|
||||||
loadConfigAndHandleErrors,
|
|
||||||
pluralize,
|
pluralize,
|
||||||
printConfigLintTotals,
|
printConfigLintTotals,
|
||||||
printLintTotals,
|
printLintTotals,
|
||||||
printUnusedWarnings,
|
printUnusedWarnings,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import type { CommonOptions, Skips, Totals } from '../types';
|
import type { OutputFormat, ProblemSeverity, RawConfig, RuleSeverity } from '@redocly/openapi-core';
|
||||||
|
import type { CommandOptions, Skips, Totals } from '../types';
|
||||||
import { blue, gray } from 'colorette';
|
import { blue, gray } from 'colorette';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
|
||||||
export type LintOptions = CommonOptions &
|
export type LintOptions = {
|
||||||
Omit<Skips, 'skip-decorator'> & {
|
apis?: string[];
|
||||||
'generate-ignore-file'?: boolean;
|
'max-problems': number;
|
||||||
'lint-config': RuleSeverity;
|
extends?: string[];
|
||||||
};
|
config?: string;
|
||||||
|
format: OutputFormat;
|
||||||
export async function handleLint(argv: LintOptions, version: string) {
|
'generate-ignore-file'?: boolean;
|
||||||
if (argv.config && !doesYamlFileExist(argv.config)) {
|
'lint-config'?: RuleSeverity;
|
||||||
return exitWithError('Please, provide valid path to the configuration file');
|
} & Omit<Skips, 'skip-decorator'>;
|
||||||
}
|
|
||||||
|
|
||||||
const config: Config = await loadConfigAndHandleErrors({
|
|
||||||
configPath: argv.config,
|
|
||||||
customExtends: argv.extends,
|
|
||||||
processRawConfig: lintConfigCallback(argv, version),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
export async function handleLint(argv: LintOptions, config: Config, version: string) {
|
||||||
const apis = await getFallbackApisOrExit(argv.apis, config);
|
const apis = await getFallbackApisOrExit(argv.apis, config);
|
||||||
|
|
||||||
if (!apis.length) {
|
if (!apis.length) {
|
||||||
return exitWithError('No APIs were provided');
|
exitWithError('No APIs were provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv['generate-ignore-file']) {
|
if (argv['generate-ignore-file']) {
|
||||||
@@ -120,14 +110,15 @@ export async function handleLint(argv: LintOptions, version: string) {
|
|||||||
|
|
||||||
printUnusedWarnings(config.styleguide);
|
printUnusedWarnings(config.styleguide);
|
||||||
|
|
||||||
// defer process exit to allow STDOUT pipe to flush
|
if (!(totals.errors === 0 || argv['generate-ignore-file'])) {
|
||||||
// see https://github.com/nodejs/node-v0.x-archive/issues/3737#issuecomment-19156072
|
throw new Error('Lint failed.');
|
||||||
process.once('exit', () =>
|
}
|
||||||
process.exit(totals.errors === 0 || argv['generate-ignore-file'] ? 0 : 1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function lintConfigCallback(argv: LintOptions, version: string) {
|
export function lintConfigCallback(
|
||||||
|
argv: CommandOptions & Record<string, undefined>,
|
||||||
|
version: string
|
||||||
|
) {
|
||||||
if (argv['lint-config'] === 'off') {
|
if (argv['lint-config'] === 'off') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -138,7 +129,6 @@ function lintConfigCallback(argv: LintOptions, version: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async (config: RawConfig) => {
|
return async (config: RawConfig) => {
|
||||||
const { 'max-problems': maxProblems, format } = argv;
|
|
||||||
const configPath = findConfig(argv.config) || '';
|
const configPath = findConfig(argv.config) || '';
|
||||||
const stringYaml = stringifyYaml(config);
|
const stringYaml = stringifyYaml(config);
|
||||||
const configContent = makeDocumentFromString(stringYaml, configPath);
|
const configContent = makeDocumentFromString(stringYaml, configPath);
|
||||||
@@ -150,8 +140,8 @@ function lintConfigCallback(argv: LintOptions, version: string) {
|
|||||||
const fileTotals = getTotals(problems);
|
const fileTotals = getTotals(problems);
|
||||||
|
|
||||||
formatProblems(problems, {
|
formatProblems(problems, {
|
||||||
format,
|
format: argv.format,
|
||||||
maxProblems,
|
maxProblems: argv['max-problems'],
|
||||||
totals: fileTotals,
|
totals: fileTotals,
|
||||||
version,
|
version,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Region, RedoclyClient } from '@redocly/openapi-core';
|
import { Region, RedoclyClient, Config } from '@redocly/openapi-core';
|
||||||
import { blue, green, gray } from 'colorette';
|
import { blue, green, gray } from 'colorette';
|
||||||
import { loadConfigAndHandleErrors, promptUser } from '../utils';
|
import { promptUser } from '../utils';
|
||||||
|
|
||||||
export function promptClientToken(domain: string) {
|
export function promptClientToken(domain: string) {
|
||||||
return promptUser(
|
return promptUser(
|
||||||
@@ -11,8 +11,14 @@ export function promptClientToken(domain: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleLogin(argv: { verbose?: boolean; region?: Region }) {
|
export type LoginOptions = {
|
||||||
const region = argv.region || (await loadConfigAndHandleErrors()).region;
|
verbose?: boolean;
|
||||||
|
region?: Region;
|
||||||
|
config?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function handleLogin(argv: LoginOptions, config: Config) {
|
||||||
|
const region = argv.region || config.region;
|
||||||
const client = new RedoclyClient(region);
|
const client = new RedoclyClient(region);
|
||||||
const clientToken = await promptClientToken(client.domain);
|
const clientToken = await promptClientToken(client.domain);
|
||||||
process.stdout.write(gray('\n Logging in...\n'));
|
process.stdout.write(gray('\n Logging in...\n'));
|
||||||
|
|||||||
@@ -7,24 +7,25 @@ import {
|
|||||||
RedoclyClient,
|
RedoclyClient,
|
||||||
getTotals,
|
getTotals,
|
||||||
getMergedConfig,
|
getMergedConfig,
|
||||||
|
Config,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
import { getFallbackApisOrExit, loadConfigAndHandleErrors } from '../../utils';
|
import { getFallbackApisOrExit, loadConfigAndHandleErrors } from '../../utils';
|
||||||
import startPreviewServer from './preview-server/preview-server';
|
import startPreviewServer from './preview-server/preview-server';
|
||||||
import type { Skips } from '../../types';
|
import type { Skips } from '../../types';
|
||||||
|
|
||||||
export async function previewDocs(
|
export type PreviewDocsOptions = {
|
||||||
argv: {
|
port: number;
|
||||||
port: number;
|
host: string;
|
||||||
host: string;
|
'use-community-edition'?: boolean;
|
||||||
'use-community-edition'?: boolean;
|
config?: string;
|
||||||
config?: string;
|
api?: string;
|
||||||
api?: string;
|
force?: boolean;
|
||||||
force?: boolean;
|
} & Omit<Skips, 'skip-rule'>;
|
||||||
} & Omit<Skips, 'skip-rule'>
|
|
||||||
) {
|
export async function previewDocs(argv: PreviewDocsOptions, configFromFile: Config) {
|
||||||
let isAuthorizedWithRedocly = false;
|
let isAuthorizedWithRedocly = false;
|
||||||
let redocOptions: any = {};
|
let redocOptions: any = {};
|
||||||
let config = await reloadConfig();
|
let config = await reloadConfig(configFromFile);
|
||||||
|
|
||||||
const apis = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
const apis = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
||||||
const api = apis[0];
|
const api = apis[0];
|
||||||
@@ -127,8 +128,14 @@ export async function previewDocs(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function reloadConfig() {
|
async function reloadConfig(config?: Config) {
|
||||||
const config = await loadConfigAndHandleErrors({ configPath: argv.config });
|
if (!config) {
|
||||||
|
try {
|
||||||
|
config = (await loadConfigAndHandleErrors({ configPath: argv.config })) as Config;
|
||||||
|
} catch (err) {
|
||||||
|
config = new Config({ apis: {}, styleguide: {} });
|
||||||
|
}
|
||||||
|
}
|
||||||
const redoclyClient = new RedoclyClient();
|
const redoclyClient = new RedoclyClient();
|
||||||
isAuthorizedWithRedocly = await redoclyClient.isAuthorizedWithRedocly();
|
isAuthorizedWithRedocly = await redoclyClient.isAuthorizedWithRedocly();
|
||||||
const resolvedConfig = getMergedConfig(config, argv.api);
|
const resolvedConfig = getMergedConfig(config, argv.api);
|
||||||
|
|||||||
@@ -21,16 +21,15 @@ import {
|
|||||||
getFallbackApisOrExit,
|
getFallbackApisOrExit,
|
||||||
pluralize,
|
pluralize,
|
||||||
dumpBundle,
|
dumpBundle,
|
||||||
loadConfigAndHandleErrors,
|
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { promptClientToken } from './login';
|
import { promptClientToken } from './login';
|
||||||
|
|
||||||
const DEFAULT_VERSION = 'latest';
|
const DEFAULT_VERSION = 'latest';
|
||||||
|
|
||||||
const DESTINATION_REGEX =
|
export const DESTINATION_REGEX =
|
||||||
/^(@(?<organizationId>[\w\-\s]+)\/)?(?<name>[^@]*)@(?<version>[\w\.\-]+)$/;
|
/^(@(?<organizationId>[\w\-\s]+)\/)?(?<name>[^@]*)@(?<version>[\w\.\-]+)$/;
|
||||||
|
|
||||||
type PushArgs = {
|
export type PushOptions = {
|
||||||
api?: string;
|
api?: string;
|
||||||
destination?: string;
|
destination?: string;
|
||||||
branchName?: string;
|
branchName?: string;
|
||||||
@@ -41,10 +40,10 @@ type PushArgs = {
|
|||||||
'skip-decorator'?: string[];
|
'skip-decorator'?: string[];
|
||||||
public?: boolean;
|
public?: boolean;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
|
config?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function handlePush(argv: PushArgs): Promise<void> {
|
export async function handlePush(argv: PushOptions, config: Config): Promise<void> {
|
||||||
const config = await loadConfigAndHandleErrors({ region: argv.region, files: argv.files });
|
|
||||||
const client = new RedoclyClient(config.region);
|
const client = new RedoclyClient(config.region);
|
||||||
const isAuthorized = await client.isAuthorizedWithRedoclyByRegion();
|
const isAuthorized = await client.isAuthorizedWithRedoclyByRegion();
|
||||||
if (!isAuthorized) {
|
if (!isAuthorized) {
|
||||||
@@ -329,7 +328,7 @@ export function getDestinationProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BarePushArgs = Omit<PushArgs, 'api' | 'destination' | 'branchName'> & {
|
type BarePushArgs = Omit<PushOptions, 'api' | 'destination' | 'branchName'> & {
|
||||||
maybeApiOrDestination?: string;
|
maybeApiOrDestination?: string;
|
||||||
maybeDestination?: string;
|
maybeDestination?: string;
|
||||||
maybeBranchName?: string;
|
maybeBranchName?: string;
|
||||||
@@ -338,7 +337,10 @@ type BarePushArgs = Omit<PushArgs, 'api' | 'destination' | 'branchName'> & {
|
|||||||
|
|
||||||
export const transformPush =
|
export const transformPush =
|
||||||
(callback: typeof handlePush) =>
|
(callback: typeof handlePush) =>
|
||||||
({ maybeApiOrDestination, maybeDestination, maybeBranchName, branch, ...rest }: BarePushArgs) => {
|
(
|
||||||
|
{ maybeApiOrDestination, maybeDestination, maybeBranchName, branch, ...rest }: BarePushArgs,
|
||||||
|
config: Config
|
||||||
|
) => {
|
||||||
if (maybeBranchName) {
|
if (maybeBranchName) {
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
yellow(
|
yellow(
|
||||||
@@ -348,12 +350,15 @@ export const transformPush =
|
|||||||
}
|
}
|
||||||
const api = maybeDestination ? maybeApiOrDestination : undefined;
|
const api = maybeDestination ? maybeApiOrDestination : undefined;
|
||||||
const destination = maybeDestination || maybeApiOrDestination;
|
const destination = maybeDestination || maybeApiOrDestination;
|
||||||
return callback({
|
return callback(
|
||||||
...rest,
|
{
|
||||||
destination,
|
...rest,
|
||||||
api,
|
destination,
|
||||||
branchName: branch ?? maybeBranchName,
|
api,
|
||||||
});
|
branchName: branch ?? maybeBranchName,
|
||||||
|
},
|
||||||
|
config
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getApiRoot({
|
export function getApiRoot({
|
||||||
|
|||||||
@@ -35,7 +35,14 @@ import {
|
|||||||
Referenced,
|
Referenced,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export async function handleSplit(argv: { api: string; outDir: string; separator: string }) {
|
export type SplitOptions = {
|
||||||
|
api: string;
|
||||||
|
outDir: string;
|
||||||
|
separator: string;
|
||||||
|
config?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function handleSplit(argv: SplitOptions) {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
const { api, outDir, separator } = argv;
|
const { api, outDir, separator } = argv;
|
||||||
validateDefinitionFileName(api!);
|
validateDefinitionFileName(api!);
|
||||||
|
|||||||
@@ -6,22 +6,19 @@ import {
|
|||||||
normalizeTypes,
|
normalizeTypes,
|
||||||
Oas3Types,
|
Oas3Types,
|
||||||
Oas2Types,
|
Oas2Types,
|
||||||
StatsAccumulator,
|
|
||||||
StatsName,
|
|
||||||
BaseResolver,
|
BaseResolver,
|
||||||
resolveDocument,
|
resolveDocument,
|
||||||
detectOpenAPI,
|
detectOpenAPI,
|
||||||
OasMajorVersion,
|
OasMajorVersion,
|
||||||
openAPIMajor,
|
openAPIMajor,
|
||||||
normalizeVisitors,
|
normalizeVisitors,
|
||||||
WalkContext,
|
|
||||||
walkDocument,
|
walkDocument,
|
||||||
Stats,
|
Stats,
|
||||||
bundle,
|
bundle,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
|
import { getFallbackApisOrExit } from '../utils';
|
||||||
import { getFallbackApisOrExit, loadConfigAndHandleErrors } from '../utils';
|
|
||||||
import { printExecutionTime } from '../utils';
|
import { printExecutionTime } from '../utils';
|
||||||
|
import type { StatsAccumulator, StatsName, WalkContext, OutputFormat } from '@redocly/openapi-core';
|
||||||
|
|
||||||
const statsAccumulator: StatsAccumulator = {
|
const statsAccumulator: StatsAccumulator = {
|
||||||
refs: { metric: '🚗 References', total: 0, color: 'red', items: new Set() },
|
refs: { metric: '🚗 References', total: 0, color: 'red', items: new Set() },
|
||||||
@@ -64,8 +61,13 @@ function printStats(statsAccumulator: StatsAccumulator, api: string, format: str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleStats(argv: { config?: string; api?: string; format: string }) {
|
export type StatsOptions = {
|
||||||
const config: Config = await loadConfigAndHandleErrors({ configPath: argv.config });
|
api?: string;
|
||||||
|
format: OutputFormat;
|
||||||
|
config?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function handleStats(argv: StatsOptions, config: Config) {
|
||||||
const [{ path }] = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
const [{ path }] = await getFallbackApisOrExit(argv.api ? [argv.api] : [], config);
|
||||||
const externalRefResolver = new BaseResolver(config.resolve);
|
const externalRefResolver = new BaseResolver(config.resolve);
|
||||||
const { bundle: document } = await bundle({ config, ref: path });
|
const { bundle: document } = await bundle({ config, ref: path });
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import './assert-node-version';
|
import './assert-node-version';
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
import { outputExtensions, regionChoices } from './types';
|
import { outputExtensions, regionChoices } from './types';
|
||||||
import { RedoclyClient, OutputFormat, RuleSeverity } from '@redocly/openapi-core';
|
import { RedoclyClient } from '@redocly/openapi-core';
|
||||||
import { previewDocs } from './commands/preview-docs';
|
import { previewDocs } from './commands/preview-docs';
|
||||||
import { handleStats } from './commands/stats';
|
import { handleStats } from './commands/stats';
|
||||||
import { handleSplit } from './commands/split';
|
import { handleSplit } from './commands/split';
|
||||||
@@ -13,17 +13,19 @@ import { handleLint } from './commands/lint';
|
|||||||
import { handleBundle } from './commands/bundle';
|
import { handleBundle } from './commands/bundle';
|
||||||
import { handleLogin } from './commands/login';
|
import { handleLogin } from './commands/login';
|
||||||
import { handlerBuildCommand } from './commands/build-docs';
|
import { handlerBuildCommand } from './commands/build-docs';
|
||||||
import type { BuildDocsArgv } from './commands/build-docs/types';
|
|
||||||
import { cacheLatestVersion, notifyUpdateCliVersion } from './update-version-notifier';
|
import { cacheLatestVersion, notifyUpdateCliVersion } from './update-version-notifier';
|
||||||
|
import { commandWrapper } from './wrapper';
|
||||||
const version = require('../package.json').version;
|
import { version } from './update-version-notifier';
|
||||||
|
import type { Arguments } from 'yargs';
|
||||||
|
import type { OutputFormat, RuleSeverity } from '@redocly/openapi-core';
|
||||||
|
import type { BuildDocsArgv } from './commands/build-docs/types';
|
||||||
|
|
||||||
cacheLatestVersion();
|
cacheLatestVersion();
|
||||||
|
|
||||||
yargs
|
yargs
|
||||||
.version('version', 'Show version number.', version)
|
.version('version', 'Show version number.', version)
|
||||||
.help('help', 'Show help.')
|
.help('help', 'Show help.')
|
||||||
.parserConfiguration({ 'greedy-arrays': false })
|
.parserConfiguration({ 'greedy-arrays': false, 'camel-case-expansion': false })
|
||||||
.command(
|
.command(
|
||||||
'stats [api]',
|
'stats [api]',
|
||||||
'Gathering statistics for a document.',
|
'Gathering statistics for a document.',
|
||||||
@@ -38,7 +40,7 @@ yargs
|
|||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'stats';
|
process.env.REDOCLY_CLI_COMMAND = 'stats';
|
||||||
handleStats(argv);
|
commandWrapper(handleStats)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -62,11 +64,16 @@ yargs
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
default: '_',
|
default: '_',
|
||||||
},
|
},
|
||||||
|
config: {
|
||||||
|
description: 'Specify path to the config file.',
|
||||||
|
requiresArg: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.demandOption('api'),
|
.demandOption('api'),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'split';
|
process.env.REDOCLY_CLI_COMMAND = 'split';
|
||||||
handleSplit(argv);
|
commandWrapper(handleSplit)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -108,10 +115,15 @@ yargs
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'openapi.yaml',
|
default: 'openapi.yaml',
|
||||||
},
|
},
|
||||||
|
config: {
|
||||||
|
description: 'Specify path to the config file.',
|
||||||
|
requiresArg: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'join';
|
process.env.REDOCLY_CLI_COMMAND = 'join';
|
||||||
handleJoin(argv, version);
|
commandWrapper(handleJoin)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -151,12 +163,17 @@ yargs
|
|||||||
array: true,
|
array: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
config: {
|
||||||
|
description: 'Specify path to the config file.',
|
||||||
|
requiresArg: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.implies('batch-id', 'batch-size')
|
.implies('batch-id', 'batch-size')
|
||||||
.implies('batch-size', 'batch-id'),
|
.implies('batch-size', 'batch-id'),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'push';
|
process.env.REDOCLY_CLI_COMMAND = 'push';
|
||||||
transformPush(handlePush)(argv);
|
commandWrapper(transformPush(handlePush))(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -215,7 +232,7 @@ yargs
|
|||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'lint';
|
process.env.REDOCLY_CLI_COMMAND = 'lint';
|
||||||
handleLint(argv, version);
|
commandWrapper(handleLint)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -297,7 +314,7 @@ yargs
|
|||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'bundle';
|
process.env.REDOCLY_CLI_COMMAND = 'bundle';
|
||||||
handleBundle(argv, version);
|
commandWrapper(handleBundle)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -314,21 +331,28 @@ yargs
|
|||||||
alias: 'r',
|
alias: 'r',
|
||||||
choices: regionChoices,
|
choices: regionChoices,
|
||||||
},
|
},
|
||||||
|
config: {
|
||||||
|
description: 'Specify path to the config file.',
|
||||||
|
requiresArg: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'login';
|
process.env.REDOCLY_CLI_COMMAND = 'login';
|
||||||
handleLogin(argv);
|
commandWrapper(handleLogin)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
'logout',
|
'logout',
|
||||||
'Clear your stored credentials for the Redocly API registry.',
|
'Clear your stored credentials for the Redocly API registry.',
|
||||||
(yargs) => yargs,
|
(yargs) => yargs,
|
||||||
async () => {
|
async (argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'logout';
|
process.env.REDOCLY_CLI_COMMAND = 'logout';
|
||||||
const client = new RedoclyClient();
|
await commandWrapper(async () => {
|
||||||
client.logout();
|
const client = new RedoclyClient();
|
||||||
process.stdout.write('Logged out from the Redocly account. ✋\n');
|
client.logout();
|
||||||
|
process.stdout.write('Logged out from the Redocly account. ✋\n');
|
||||||
|
})(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -374,7 +398,7 @@ yargs
|
|||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'preview-docs';
|
process.env.REDOCLY_CLI_COMMAND = 'preview-docs';
|
||||||
previewDocs(argv);
|
commandWrapper(previewDocs)(argv);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -428,7 +452,7 @@ yargs
|
|||||||
}),
|
}),
|
||||||
async (argv) => {
|
async (argv) => {
|
||||||
process.env.REDOCLY_CLI_COMMAND = 'build-docs';
|
process.env.REDOCLY_CLI_COMMAND = 'build-docs';
|
||||||
handlerBuildCommand(argv as unknown as BuildDocsArgv);
|
commandWrapper(handlerBuildCommand)(argv as Arguments<BuildDocsArgv>);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.completion('completion', 'Generate completion script.')
|
.completion('completion', 'Generate completion script.')
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
import type { BundleOutputFormat, OutputFormat, Region, Config } from '@redocly/openapi-core';
|
import type { BundleOutputFormat, Region, Config } from '@redocly/openapi-core';
|
||||||
|
import type { LintOptions } from './commands/lint';
|
||||||
|
import type { BundleOptions } from './commands/bundle';
|
||||||
|
import type { JoinOptions } from './commands/join';
|
||||||
|
import type { LoginOptions } from './commands/login';
|
||||||
|
import type { PushOptions } from './commands/push';
|
||||||
|
import type { StatsOptions } from './commands/stats';
|
||||||
|
import type { SplitOptions } from './commands/split';
|
||||||
|
import type { PreviewDocsOptions } from './commands/preview-docs';
|
||||||
|
import type { BuildDocsArgv } from './commands/build-docs/types';
|
||||||
|
|
||||||
export type Totals = {
|
export type Totals = {
|
||||||
errors: number;
|
errors: number;
|
||||||
@@ -12,13 +21,16 @@ export type Entrypoint = {
|
|||||||
export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
||||||
export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
|
export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
|
||||||
export const regionChoices = ['us', 'eu'] as ReadonlyArray<Region>;
|
export const regionChoices = ['us', 'eu'] as ReadonlyArray<Region>;
|
||||||
export type CommonOptions = {
|
export type CommandOptions =
|
||||||
apis: string[];
|
| StatsOptions
|
||||||
'max-problems'?: number;
|
| SplitOptions
|
||||||
extends?: string[];
|
| JoinOptions
|
||||||
config?: string;
|
| PushOptions
|
||||||
format: OutputFormat;
|
| LintOptions
|
||||||
};
|
| BundleOptions
|
||||||
|
| LoginOptions
|
||||||
|
| PreviewDocsOptions
|
||||||
|
| BuildDocsArgv;
|
||||||
export type Skips = {
|
export type Skips = {
|
||||||
'skip-rule'?: string[];
|
'skip-rule'?: string[];
|
||||||
'skip-decorator'?: string[];
|
'skip-decorator'?: string[];
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import fetch from 'node-fetch';
|
|||||||
import { cyan, green, yellow } from 'colorette';
|
import { cyan, green, yellow } from 'colorette';
|
||||||
import { cleanColors } from './utils';
|
import { cleanColors } from './utils';
|
||||||
|
|
||||||
const { version, name } = require('../package.json');
|
export const { version, name } = require('../package.json');
|
||||||
|
|
||||||
const VERSION_CACHE_FILE = 'redocly-cli-version';
|
const VERSION_CACHE_FILE = 'redocly-cli-version';
|
||||||
const SPACE_TO_BORDER = 4;
|
const SPACE_TO_BORDER = 4;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import fetch from 'node-fetch';
|
||||||
import { basename, dirname, extname, join, resolve, relative, isAbsolute } from 'path';
|
import { basename, dirname, extname, join, resolve, relative, isAbsolute } from 'path';
|
||||||
import { blue, gray, green, red, yellow } from 'colorette';
|
import { blue, gray, green, red, yellow } from 'colorette';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
@@ -20,9 +21,13 @@ import {
|
|||||||
Config,
|
Config,
|
||||||
Oas3Definition,
|
Oas3Definition,
|
||||||
Oas2Definition,
|
Oas2Definition,
|
||||||
|
RedoclyClient,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
import { Totals, outputExtensions, Entrypoint, ConfigApis } from './types';
|
import { Totals, outputExtensions, Entrypoint, ConfigApis, CommandOptions } from './types';
|
||||||
import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
|
import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
|
||||||
|
import { Arguments } from 'yargs';
|
||||||
|
import { version } from './update-version-notifier';
|
||||||
|
import { DESTINATION_REGEX } from './commands/push';
|
||||||
|
|
||||||
export async function getFallbackApisOrExit(
|
export async function getFallbackApisOrExit(
|
||||||
argsApis: string[] | undefined,
|
argsApis: string[] | undefined,
|
||||||
@@ -39,14 +44,10 @@ export async function getFallbackApisOrExit(
|
|||||||
if (isNotEmptyArray(filteredInvalidEntrypoints)) {
|
if (isNotEmptyArray(filteredInvalidEntrypoints)) {
|
||||||
for (const { path } of filteredInvalidEntrypoints) {
|
for (const { path } of filteredInvalidEntrypoints) {
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
yellow(
|
yellow(`\n${relative(process.cwd(), path)} ${red(`does not exist or is invalid.\n\n`)}`)
|
||||||
`\n ${relative(process.cwd(), path)} ${red(
|
|
||||||
`does not exist or is invalid. Please provide a valid path. \n\n`
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
exitWithError('Please provide a valid path.');
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -227,30 +228,30 @@ export function pluralize(label: string, num: number) {
|
|||||||
|
|
||||||
export function handleError(e: Error, ref: string) {
|
export function handleError(e: Error, ref: string) {
|
||||||
switch (e.constructor) {
|
switch (e.constructor) {
|
||||||
|
case HandledError: {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
case ResolveError:
|
case ResolveError:
|
||||||
return exitWithError(`Failed to resolve api definition at ${ref}:\n\n - ${e.message}.`);
|
return exitWithError(`Failed to resolve api definition at ${ref}:\n\n - ${e.message}.`);
|
||||||
case YamlParseError:
|
case YamlParseError:
|
||||||
return exitWithError(`Failed to parse api definition at ${ref}:\n\n - ${e.message}.`);
|
return exitWithError(`Failed to parse api definition at ${ref}:\n\n - ${e.message}.`);
|
||||||
// TODO: codeframe
|
// TODO: codeframe
|
||||||
case CircularJSONNotSupportedError: {
|
case CircularJSONNotSupportedError: {
|
||||||
process.stderr.write(
|
return exitWithError(
|
||||||
red(`Detected circular reference which can't be converted to JSON.\n`) +
|
`Detected circular reference which can't be converted to JSON.\n` +
|
||||||
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.\n\n`
|
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.`
|
||||||
);
|
);
|
||||||
return process.exit(1);
|
|
||||||
}
|
}
|
||||||
case SyntaxError:
|
case SyntaxError:
|
||||||
return exitWithError(`Syntax error: ${e.message} ${e.stack?.split('\n\n')?.[0]}`);
|
return exitWithError(`Syntax error: ${e.message} ${e.stack?.split('\n\n')?.[0]}`);
|
||||||
default: {
|
default: {
|
||||||
process.stderr.write(
|
exitWithError(`Something went wrong when processing ${ref}:\n\n - ${e.message}.`);
|
||||||
red(`Something went wrong when processing ${ref}:\n\n - ${e.message}.\n\n`)
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HandledError extends Error {}
|
||||||
|
|
||||||
export function printLintTotals(totals: Totals, definitionsCount: number) {
|
export function printLintTotals(totals: Totals, definitionsCount: number) {
|
||||||
const ignored = totals.ignored
|
const ignored = totals.ignored
|
||||||
? yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
|
? yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
|
||||||
@@ -373,7 +374,7 @@ export function printUnusedWarnings(config: StyleguideConfig) {
|
|||||||
|
|
||||||
export function exitWithError(message: string) {
|
export function exitWithError(message: string) {
|
||||||
process.stderr.write(red(message) + '\n\n');
|
process.stderr.write(red(message) + '\n\n');
|
||||||
process.exit(1);
|
throw new HandledError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -392,12 +393,11 @@ export async function loadConfigAndHandleErrors(
|
|||||||
files?: string[];
|
files?: string[];
|
||||||
region?: Region;
|
region?: Region;
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<Config> {
|
): Promise<Config | void> {
|
||||||
try {
|
try {
|
||||||
return await loadConfig(options);
|
return await loadConfig(options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e, '');
|
handleError(e, '');
|
||||||
return new Config({ apis: {}, styleguide: {} });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,3 +479,96 @@ export function cleanColors(input: string): string {
|
|||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
return input.replace(/\x1b\[\d+m/g, '');
|
return input.replace(/\x1b\[\d+m/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendTelemetry(
|
||||||
|
argv: Arguments | undefined,
|
||||||
|
exit_code: ExitCode,
|
||||||
|
has_config: boolean | undefined
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!argv) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
_: [command],
|
||||||
|
$0: _,
|
||||||
|
...args
|
||||||
|
} = argv;
|
||||||
|
const event_time = new Date().toISOString();
|
||||||
|
const redoclyClient = new RedoclyClient();
|
||||||
|
const node_version = process.version;
|
||||||
|
const logged_in = await redoclyClient.isAuthorizedWithRedoclyByRegion();
|
||||||
|
const data: Analytics = {
|
||||||
|
event: 'cli_command',
|
||||||
|
event_time,
|
||||||
|
logged_in,
|
||||||
|
command,
|
||||||
|
arguments: cleanArgs(args),
|
||||||
|
node_version,
|
||||||
|
version,
|
||||||
|
exit_code,
|
||||||
|
environment: process.env.REDOCLY_ENVIRONMENT,
|
||||||
|
raw_input: cleanRawInput(process.argv.slice(2)),
|
||||||
|
has_config,
|
||||||
|
};
|
||||||
|
await fetch(`https://api.redocly.com/registry/telemetry/cli`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExitCode = 0 | 1 | 2;
|
||||||
|
|
||||||
|
export type Analytics = {
|
||||||
|
event: string;
|
||||||
|
event_time: string;
|
||||||
|
logged_in: boolean;
|
||||||
|
command: string | number;
|
||||||
|
arguments: Record<string, unknown>;
|
||||||
|
node_version: string;
|
||||||
|
version: string;
|
||||||
|
exit_code: ExitCode;
|
||||||
|
environment?: string;
|
||||||
|
raw_input: string;
|
||||||
|
has_config?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function cleanString(value?: string): string | undefined {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (isAbsoluteUrl(value)) {
|
||||||
|
return value.split('://')[0] + '://***';
|
||||||
|
}
|
||||||
|
if (value.endsWith('.json') || value.endsWith('.yaml') || value.endsWith('.yml')) {
|
||||||
|
return value.replace(/^(.*)\.(yaml|yml|json)$/gi, (_, __, ext) => '***.' + ext);
|
||||||
|
}
|
||||||
|
if (DESTINATION_REGEX.test(value)) {
|
||||||
|
return value.replace(/^@[\w\-\s]+\//, () => '@***/');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanArgs(args: CommandOptions) {
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
for (const [key, value] of Object.entries(args)) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
result[key] = cleanString(value);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
result[key] = value.map(cleanString);
|
||||||
|
} else {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanRawInput(argv: string[]) {
|
||||||
|
return argv.map((entry) => entry.split('=').map(cleanString).join('=')).join(' ');
|
||||||
|
}
|
||||||
|
|||||||
42
packages/cli/src/wrapper.ts
Normal file
42
packages/cli/src/wrapper.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Config, Region, doesYamlFileExist } from '@redocly/openapi-core';
|
||||||
|
import type { Arguments } from 'yargs';
|
||||||
|
import { version } from './update-version-notifier';
|
||||||
|
import { ExitCode, exitWithError, loadConfigAndHandleErrors, sendTelemetry } from './utils';
|
||||||
|
import { lintConfigCallback } from './commands/lint';
|
||||||
|
import type { CommandOptions } from './types';
|
||||||
|
|
||||||
|
export function commandWrapper<T extends CommandOptions>(
|
||||||
|
commandHandler: (argv: T, config: Config, version: string) => Promise<void>
|
||||||
|
) {
|
||||||
|
return async (argv: Arguments<T>) => {
|
||||||
|
let code: ExitCode = 2;
|
||||||
|
let hasConfig;
|
||||||
|
let telemetry;
|
||||||
|
try {
|
||||||
|
if (argv.config && !doesYamlFileExist(argv.config)) {
|
||||||
|
exitWithError('Please, provide valid path to the configuration file');
|
||||||
|
}
|
||||||
|
const config: Config = (await loadConfigAndHandleErrors({
|
||||||
|
configPath: argv.config,
|
||||||
|
customExtends: argv.extends as string[] | undefined,
|
||||||
|
region: argv.region as Region,
|
||||||
|
files: argv.file as string[] | undefined,
|
||||||
|
processRawConfig: lintConfigCallback(argv as T & Record<string, undefined>, version),
|
||||||
|
})) as Config;
|
||||||
|
telemetry = config.telemetry;
|
||||||
|
hasConfig = !config.styleguide.recommendedFallback;
|
||||||
|
code = 1;
|
||||||
|
await commandHandler(argv, config, version);
|
||||||
|
code = 0;
|
||||||
|
} catch (err) {
|
||||||
|
// Do nothing
|
||||||
|
} finally {
|
||||||
|
if (process.env.REDOCLY_TELEMETRY !== 'off' && telemetry !== 'off') {
|
||||||
|
await sendTelemetry(argv, code, hasConfig);
|
||||||
|
}
|
||||||
|
process.once('beforeExit', () => {
|
||||||
|
process.exit(code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ const testConfig: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
organization: 'redocly-test',
|
organization: 'redocly-test',
|
||||||
|
telemetry: 'on',
|
||||||
styleguide: {
|
styleguide: {
|
||||||
rules: { 'operation-summary': 'error', 'no-empty-servers': 'error' },
|
rules: { 'operation-summary': 'error', 'no-empty-servers': 'error' },
|
||||||
plugins: [],
|
plugins: [],
|
||||||
@@ -101,6 +102,7 @@ describe('getMergedConfig', () => {
|
|||||||
"operation-summary": "warn",
|
"operation-summary": "warn",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"telemetry": "on",
|
||||||
"theme": Object {},
|
"theme": Object {},
|
||||||
},
|
},
|
||||||
"region": undefined,
|
"region": undefined,
|
||||||
@@ -149,6 +151,7 @@ describe('getMergedConfig', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"telemetry": "on",
|
||||||
"theme": Object {},
|
"theme": Object {},
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@@ -199,6 +202,7 @@ describe('getMergedConfig', () => {
|
|||||||
"operation-summary": "error",
|
"operation-summary": "error",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"telemetry": "on",
|
||||||
"theme": Object {},
|
"theme": Object {},
|
||||||
},
|
},
|
||||||
"region": undefined,
|
"region": undefined,
|
||||||
@@ -252,6 +256,7 @@ describe('getMergedConfig', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"telemetry": "on",
|
||||||
"theme": Object {},
|
"theme": Object {},
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type {
|
|||||||
ResolvedStyleguideConfig,
|
ResolvedStyleguideConfig,
|
||||||
RuleConfig,
|
RuleConfig,
|
||||||
RuleSettings,
|
RuleSettings,
|
||||||
|
Telemetry,
|
||||||
ThemeRawConfig,
|
ThemeRawConfig,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { getResolveConfig } from './utils';
|
import { getResolveConfig } from './utils';
|
||||||
@@ -317,6 +318,7 @@ export class Config {
|
|||||||
theme: ThemeRawConfig;
|
theme: ThemeRawConfig;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
files: string[];
|
files: string[];
|
||||||
|
telemetry?: Telemetry;
|
||||||
constructor(public rawConfig: ResolvedConfig, public configFile?: string) {
|
constructor(public rawConfig: ResolvedConfig, public configFile?: string) {
|
||||||
this.apis = rawConfig.apis || {};
|
this.apis = rawConfig.apis || {};
|
||||||
this.styleguide = new StyleguideConfig(rawConfig.styleguide || {}, configFile);
|
this.styleguide = new StyleguideConfig(rawConfig.styleguide || {}, configFile);
|
||||||
@@ -325,5 +327,6 @@ export class Config {
|
|||||||
this.region = rawConfig.region;
|
this.region = rawConfig.region;
|
||||||
this.organization = rawConfig.organization;
|
this.organization = rawConfig.organization;
|
||||||
this.files = rawConfig.files || [];
|
this.files = rawConfig.files || [];
|
||||||
|
this.telemetry = rawConfig.telemetry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export type ResolveConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type Region = 'us' | 'eu';
|
export type Region = 'us' | 'eu';
|
||||||
|
export type Telemetry = 'on' | 'off';
|
||||||
|
|
||||||
export type AccessTokens = { [region in Region]?: string };
|
export type AccessTokens = { [region in Region]?: string };
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ export type RawConfig = {
|
|||||||
region?: Region;
|
region?: Region;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
|
telemetry?: Telemetry;
|
||||||
} & ThemeConfig;
|
} & ThemeConfig;
|
||||||
|
|
||||||
export type FlatApi = Omit<Api, 'styleguide'> &
|
export type FlatApi = Omit<Api, 'styleguide'> &
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
import type { AccessTokens, Region } from '../config/types';
|
import type { AccessTokens, Region } from '../config/types';
|
||||||
import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
||||||
import { isNotEmptyObject } from '../utils';
|
import { isNotEmptyObject } from '../utils';
|
||||||
|
|
||||||
const version = require('../../package.json').version;
|
const version = require('../../package.json').version;
|
||||||
|
|
||||||
export class RegistryApi {
|
export class RegistryApi {
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ const ConfigRoot: NodeType = {
|
|||||||
'features.openapi': 'ConfigReferenceDocs', // deprecated
|
'features.openapi': 'ConfigReferenceDocs', // deprecated
|
||||||
'features.mockServer': 'ConfigMockServer', // deprecated
|
'features.mockServer': 'ConfigMockServer', // deprecated
|
||||||
region: { enum: ['us', 'eu'] },
|
region: { enum: ['us', 'eu'] },
|
||||||
|
telemetry: { enum: ['on', 'off'] },
|
||||||
resolve: {
|
resolve: {
|
||||||
properties: {
|
properties: {
|
||||||
http: 'ConfigHTTP',
|
http: 'ConfigHTTP',
|
||||||
|
|||||||
Reference in New Issue
Block a user