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