mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: add output as a per-API configuration option (#1708)
This commit is contained in:
5
.changeset/afraid-melons-sparkle.md
Normal file
5
.changeset/afraid-melons-sparkle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@redocly/cli": minor
|
||||
---
|
||||
|
||||
Added support for the `output` option in the per-API configuration so that the destination file can be specified in configuration.
|
||||
@@ -22,23 +22,23 @@ redocly bundle --version
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Type | Description |
|
||||
| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| apis | [string] | List of API description root filenames or names assigned in the `apis` section of your Redocly configuration file. Default values are all names defined in the `apis` section within your configuration file. |
|
||||
| --config | string | Specify path to the [configuration file](#use-alternative-configuration-file). |
|
||||
| --dereferenced, -d | boolean | Generate fully dereferenced bundle. |
|
||||
| --ext | string | Specify bundled file extension. Possible values are `json`, `yaml`, or `yml`. Default value is `yaml`. |
|
||||
| --extends | [string] | Can be used in combination with `--lint` to [extend a specific configuration](./lint.md#extend-configuration). Default values are taken from the Redocly configuration file. |
|
||||
| --force, -f | boolean | Generate bundle output even when errors occur. |
|
||||
| --help | boolean | Show help. |
|
||||
| --keep-url-references, -k | boolean | Keep absolute url references. |
|
||||
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
|
||||
| --metafile | string | Path for the bundle metadata file. |
|
||||
| --output, -o | string | Name or folder for the bundle file. If you don't specify the file extension, `.yaml` is used by default. If the specified folder doesn't exist, it's created automatically. **If the file specified as the bundler's output already exists, it's overwritten.** |
|
||||
| --remove-unused-components | boolean | Remove unused components from the `bundle` output. |
|
||||
| --skip-decorator | [string] | Ignore certain decorators. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
|
||||
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
|
||||
| --version | boolean | Show version number. |
|
||||
| Option | Type | Description |
|
||||
| -------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| apis | [string] | List of API description root filenames or names assigned in the `apis` section of your Redocly configuration file. Default values are all names defined in the `apis` section within your configuration file. |
|
||||
| --config | string | Specify path to the [configuration file](#use-alternative-configuration-file). |
|
||||
| --dereferenced, -d | boolean | Generate fully dereferenced bundle. |
|
||||
| --ext | string | Specify bundled file extension. Possible values are `json`, `yaml`, or `yml`. Default value is `yaml`. |
|
||||
| --extends | [string] | Can be used in combination with `--lint` to [extend a specific configuration](./lint.md#extend-configuration). Default values are taken from the Redocly configuration file. |
|
||||
| --force, -f | boolean | Generate bundle output even when errors occur. |
|
||||
| --help | boolean | Show help. |
|
||||
| --keep-url-references, -k | boolean | Keep absolute url references. |
|
||||
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
|
||||
| --metafile | string | Path for the bundle metadata file. |
|
||||
| --output, -o | string | Name or folder for the bundle file. If you don't specify the file extension, `.yaml` is used by default. If the specified folder doesn't exist, it's created automatically. **If the file specified as the bundler's output already exists, it's overwritten.** Use this option when bundling a single API only; otherwise use the `output` option in per-API configuration. |
|
||||
| --remove-unused-components | boolean | Remove unused components from the `bundle` output. |
|
||||
| --skip-decorator | [string] | Ignore certain decorators. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
|
||||
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
|
||||
| --version | boolean | Show version number. |
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -65,6 +65,27 @@ dist/openapi.json
|
||||
dist/museum.json
|
||||
</pre>
|
||||
|
||||
You can specify the default `output` location for a bundled API in the `apis` section of your Redocly configuration file.
|
||||
This is especially useful when bundling multiple APIs.
|
||||
|
||||
```yaml
|
||||
apis:
|
||||
orders@v1:
|
||||
root: orders/openapi.yaml
|
||||
output: dist/orders.json
|
||||
accounts@v1:
|
||||
root: accounts/openapi.yaml
|
||||
output: dist/accounts.json
|
||||
```
|
||||
|
||||
Given the `redocly.yaml` configuration file above, the following command bundles the APIs `foo` and `bar` into the `dist/` folder.
|
||||
|
||||
```bash
|
||||
redocly bundle
|
||||
```
|
||||
|
||||
Please note, that providing an API to the `bundle` command results in the command bundling only the specified API.
|
||||
|
||||
### Create a fully dereferenced bundle
|
||||
|
||||
A fully dereferenced bundle does not use `$ref` at all, all the references are resolved and placed into the API description file. This can be useful if you need to prepare an OpenAPI file to be used by another tool that does not understand the `$ref` syntax.
|
||||
|
||||
@@ -52,6 +52,12 @@ If your project contains multiple APIs, the `apis` configuration section allows
|
||||
- [Decorators object](./decorators.md)
|
||||
- Preprocessors run before linting, and follow the same structure as decorators. We recommend the use of decorators over preprocessors in most cases.
|
||||
|
||||
---
|
||||
|
||||
- output
|
||||
- Output file path
|
||||
- When running `bundle` without specifying an API, the bundled API description is saved to this location.
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Examples
|
||||
@@ -73,6 +79,20 @@ apis:
|
||||
operation-summary: off
|
||||
```
|
||||
|
||||
The following example shows `redocly.yaml` configuration file with settings for multiple APIs outputs.
|
||||
|
||||
```yaml
|
||||
apis:
|
||||
main@v1:
|
||||
root: openapi-v1.yaml
|
||||
output: v1/bundled.yaml
|
||||
main@v2:
|
||||
root: openapi-v2.yaml
|
||||
output: v2/bundled.yaml
|
||||
```
|
||||
|
||||
When running `redocly bundle` with this config, the bundled API descriptions are saved to the corresponding location.
|
||||
|
||||
## Related options
|
||||
|
||||
- [extends](./extends.md) sets the base ruleset to use.
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { bundle, getTotals, getMergedConfig } from '@redocly/openapi-core';
|
||||
import { bundle, getTotals, getMergedConfig, Config } from '@redocly/openapi-core';
|
||||
|
||||
import { BundleOptions, handleBundle } from '../../commands/bundle';
|
||||
import { handleError } from '../../utils/miscellaneous';
|
||||
import {
|
||||
getFallbackApisOrExit,
|
||||
getOutputFileName,
|
||||
handleError,
|
||||
saveBundle,
|
||||
} from '../../utils/miscellaneous';
|
||||
import { commandWrapper } from '../../wrapper';
|
||||
import SpyInstance = jest.SpyInstance;
|
||||
import { Arguments } from 'yargs';
|
||||
@@ -9,24 +14,31 @@ import { Arguments } from 'yargs';
|
||||
jest.mock('@redocly/openapi-core');
|
||||
jest.mock('../../utils/miscellaneous');
|
||||
|
||||
// @ts-ignore
|
||||
getOutputFileName = jest.requireActual('../../utils/miscellaneous').getOutputFileName;
|
||||
|
||||
(getMergedConfig as jest.Mock).mockImplementation((config) => config);
|
||||
|
||||
describe('bundle', () => {
|
||||
let processExitMock: SpyInstance;
|
||||
let exitCb: any;
|
||||
|
||||
let stderrWriteMock: any;
|
||||
let stdoutWriteMock: any;
|
||||
beforeEach(() => {
|
||||
processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
||||
jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
|
||||
exitCb = cb;
|
||||
return process.on(_e, cb);
|
||||
});
|
||||
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||
stderrWriteMock = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
||||
stdoutWriteMock = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(bundle as jest.Mock).mockClear();
|
||||
(getTotals as jest.Mock).mockReset();
|
||||
stderrWriteMock.mockRestore();
|
||||
stdoutWriteMock.mockRestore();
|
||||
});
|
||||
|
||||
it('bundles definitions', async () => {
|
||||
@@ -114,4 +126,119 @@ describe('bundle', () => {
|
||||
|
||||
expect(handleError).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should store bundled API descriptions in the output files described in the apis section of config IF no positional apis provided AND output is specified for both apis', async () => {
|
||||
const apis = {
|
||||
foo: {
|
||||
root: 'foo.yaml',
|
||||
output: 'output/foo.yaml',
|
||||
},
|
||||
bar: {
|
||||
root: 'bar.yaml',
|
||||
output: 'output/bar.json',
|
||||
},
|
||||
};
|
||||
const config = {
|
||||
apis,
|
||||
styleguide: {
|
||||
skipPreprocessors: jest.fn(),
|
||||
skipDecorators: jest.fn(),
|
||||
},
|
||||
} as unknown as Config;
|
||||
// @ts-ignore
|
||||
getFallbackApisOrExit = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias }))
|
||||
);
|
||||
(getTotals as jest.Mock).mockReturnValue({
|
||||
errors: 0,
|
||||
warnings: 0,
|
||||
ignored: 0,
|
||||
});
|
||||
|
||||
await handleBundle({
|
||||
argv: { apis: [] }, // positional
|
||||
version: 'test',
|
||||
config,
|
||||
});
|
||||
|
||||
expect(saveBundle).toBeCalledTimes(2);
|
||||
expect(saveBundle).toHaveBeenNthCalledWith(1, 'output/foo.yaml', expect.any(String));
|
||||
expect(saveBundle).toHaveBeenNthCalledWith(2, 'output/bar.json', expect.any(String));
|
||||
});
|
||||
|
||||
it('should store bundled API descriptions in the output files described in the apis section of config AND print the bundled api without the output specified to the terminal IF no positional apis provided AND output is specified for one api', async () => {
|
||||
const apis = {
|
||||
foo: {
|
||||
root: 'foo.yaml',
|
||||
output: 'output/foo.yaml',
|
||||
},
|
||||
bar: {
|
||||
root: 'bar.yaml',
|
||||
},
|
||||
};
|
||||
const config = {
|
||||
apis,
|
||||
styleguide: {
|
||||
skipPreprocessors: jest.fn(),
|
||||
skipDecorators: jest.fn(),
|
||||
},
|
||||
} as unknown as Config;
|
||||
// @ts-ignore
|
||||
getFallbackApisOrExit = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
Object.entries(apis).map(([alias, { root, ...api }]) => ({ ...api, path: root, alias }))
|
||||
);
|
||||
(getTotals as jest.Mock).mockReturnValue({
|
||||
errors: 0,
|
||||
warnings: 0,
|
||||
ignored: 0,
|
||||
});
|
||||
|
||||
await handleBundle({
|
||||
argv: { apis: [] }, // positional
|
||||
version: 'test',
|
||||
config,
|
||||
});
|
||||
|
||||
expect(saveBundle).toBeCalledTimes(1);
|
||||
expect(saveBundle).toHaveBeenCalledWith('output/foo.yaml', expect.any(String));
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('per api output', () => {
|
||||
it('should NOT store bundled API descriptions in the output files described in the apis section of config IF no there is a positional api provided', async () => {
|
||||
const apis = {
|
||||
foo: {
|
||||
root: 'foo.yaml',
|
||||
output: 'output/foo.yaml',
|
||||
},
|
||||
};
|
||||
const config = {
|
||||
apis,
|
||||
styleguide: {
|
||||
skipPreprocessors: jest.fn(),
|
||||
skipDecorators: jest.fn(),
|
||||
},
|
||||
} as unknown as Config;
|
||||
// @ts-ignore
|
||||
getFallbackApisOrExit = jest.fn().mockResolvedValueOnce([{ path: 'openapi.yaml' }]);
|
||||
(getTotals as jest.Mock).mockReturnValue({
|
||||
errors: 0,
|
||||
warnings: 0,
|
||||
ignored: 0,
|
||||
});
|
||||
|
||||
await handleBundle({
|
||||
argv: { apis: ['openapi.yaml'] }, // positional
|
||||
version: 'test',
|
||||
config,
|
||||
});
|
||||
|
||||
expect(saveBundle).toBeCalledTimes(0);
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import { blue, red, yellow } from 'colorette';
|
||||
import { existsSync, statSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as process from 'process';
|
||||
import { ConfigApis } from '../types';
|
||||
|
||||
jest.mock('os');
|
||||
jest.mock('colorette');
|
||||
@@ -79,20 +80,6 @@ describe('pathToFilename', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFallbackApisOrExit', () => {
|
||||
it('should find alias by filename', async () => {
|
||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
||||
const entry = await getFallbackApisOrExit(['./test.yaml'], {
|
||||
apis: {
|
||||
main: {
|
||||
root: 'test.yaml',
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('printConfigLintTotals', () => {
|
||||
const totalProblemsMock: Totals = {
|
||||
errors: 1,
|
||||
@@ -190,6 +177,7 @@ describe('getFallbackApisOrExit', () => {
|
||||
{
|
||||
alias: 'main',
|
||||
path: 'someFile.yaml',
|
||||
output: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -277,6 +265,43 @@ describe('getFallbackApisOrExit', () => {
|
||||
{
|
||||
alias: 'main',
|
||||
path: 'https://someLinkt/petstore.yaml?main',
|
||||
output: undefined,
|
||||
},
|
||||
]);
|
||||
|
||||
(isAbsoluteUrl as jest.Mock<any, any>).mockReset();
|
||||
});
|
||||
|
||||
it('should find alias by filename', async () => {
|
||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
||||
const entry = await getFallbackApisOrExit(['./test.yaml'], {
|
||||
apis: {
|
||||
main: {
|
||||
root: 'test.yaml',
|
||||
styleguide: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(entry).toEqual([{ path: './test.yaml', alias: 'main' }]);
|
||||
});
|
||||
|
||||
it('should return apis from config with paths and outputs resolved relatively to the config location', async () => {
|
||||
(existsSync as jest.Mock<any, any>).mockImplementationOnce(() => true);
|
||||
const entry = await getFallbackApisOrExit(undefined, {
|
||||
apis: {
|
||||
main: {
|
||||
root: 'test.yaml',
|
||||
output: 'output/test.yaml',
|
||||
styleguide: {},
|
||||
},
|
||||
},
|
||||
configFile: 'project-folder/redocly.yaml',
|
||||
});
|
||||
expect(entry).toEqual([
|
||||
{
|
||||
path: expect.stringMatching(/project\-folder\/test\.yaml$/),
|
||||
output: expect.stringMatching(/project\-folder\/output\/test\.yaml$/),
|
||||
alias: 'main',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -591,28 +616,28 @@ describe('cleanRawInput', () => {
|
||||
expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeToFileByExtension', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
||||
(yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
|
||||
});
|
||||
describe('writeToFileByExtension', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
||||
(yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should call stringifyYaml function', () => {
|
||||
writeToFileByExtension('test data', 'test.yaml');
|
||||
expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
|
||||
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
||||
});
|
||||
it('should call stringifyYaml function', () => {
|
||||
writeToFileByExtension('test data', 'test.yaml');
|
||||
expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
|
||||
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
||||
});
|
||||
|
||||
it('should call JSON.stringify function', () => {
|
||||
const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
|
||||
writeToFileByExtension('test data', 'test.json');
|
||||
expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
|
||||
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
||||
});
|
||||
it('should call JSON.stringify function', () => {
|
||||
const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
|
||||
writeToFileByExtension('test data', 'test.json');
|
||||
expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
|
||||
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export type BundleOptions = {
|
||||
apis?: string[];
|
||||
extends?: string[];
|
||||
output?: string;
|
||||
ext: OutputExtensions;
|
||||
ext?: OutputExtensions;
|
||||
dereferenced?: boolean;
|
||||
force?: boolean;
|
||||
metafile?: string;
|
||||
@@ -45,7 +45,7 @@ export async function handleBundle({
|
||||
|
||||
checkForDeprecatedOptions(argv, deprecatedOptions);
|
||||
|
||||
for (const { path, alias } of apis) {
|
||||
for (const { path, alias, output } of apis) {
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
const resolvedConfig = getMergedConfig(config, alias);
|
||||
@@ -70,19 +70,19 @@ export async function handleBundle({
|
||||
});
|
||||
|
||||
const fileTotals = getTotals(problems);
|
||||
const { outputFile, ext } = getOutputFileName(path, apis.length, argv.output, argv.ext);
|
||||
const { outputFile, ext } = getOutputFileName(path, output || argv.output, argv.ext);
|
||||
|
||||
if (fileTotals.errors === 0 || argv.force) {
|
||||
if (!argv.output) {
|
||||
const output = dumpBundle(
|
||||
if (!outputFile) {
|
||||
const bundled = dumpBundle(
|
||||
sortTopLevelKeysForOas(result.parsed),
|
||||
argv.ext || 'yaml',
|
||||
argv.dereferenced
|
||||
);
|
||||
process.stdout.write(output);
|
||||
process.stdout.write(bundled);
|
||||
} else {
|
||||
const output = dumpBundle(sortTopLevelKeysForOas(result.parsed), ext, argv.dereferenced);
|
||||
saveBundle(outputFile, output);
|
||||
const bundled = dumpBundle(sortTopLevelKeysForOas(result.parsed), ext, argv.dereferenced);
|
||||
saveBundle(outputFile, bundled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +111,9 @@ export async function handleBundle({
|
||||
if (fileTotals.errors > 0) {
|
||||
if (argv.force) {
|
||||
process.stderr.write(
|
||||
`❓ Created a bundle for ${blue(path)} at ${blue(outputFile)} with errors ${green(
|
||||
elapsed
|
||||
)}.\n${yellow('Errors ignored because of --force')}.\n`
|
||||
`❓ Created a bundle for ${blue(path)} at ${blue(
|
||||
outputFile || 'stdout'
|
||||
)} with errors ${green(elapsed)}.\n${yellow('Errors ignored because of --force')}.\n`
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
@@ -124,7 +124,9 @@ export async function handleBundle({
|
||||
}
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`📦 Created a bundle for ${blue(path)} at ${blue(outputFile)} ${green(elapsed)}.\n`
|
||||
`📦 Created a bundle for ${blue(path)} at ${blue(outputFile || 'stdout')} ${green(
|
||||
elapsed
|
||||
)}.\n`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export type Totals = {
|
||||
export type Entrypoint = {
|
||||
path: string;
|
||||
alias?: string;
|
||||
output?: string;
|
||||
};
|
||||
export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
||||
export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
|
||||
|
||||
@@ -20,3 +20,4 @@ export const sortTopLevelKeysForOas = jest.fn((document) => document);
|
||||
export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
|
||||
export const writeToFileByExtension = jest.fn();
|
||||
export const checkForDeprecatedOptions = jest.fn();
|
||||
export const saveBundle = jest.fn();
|
||||
|
||||
@@ -16,7 +16,13 @@ import {
|
||||
loadConfig,
|
||||
RedoclyClient,
|
||||
} from '@redocly/openapi-core';
|
||||
import { isEmptyObject, isPlainObject, pluralize } from '@redocly/openapi-core/lib/utils';
|
||||
import {
|
||||
isEmptyObject,
|
||||
isNotEmptyArray,
|
||||
isNotEmptyObject,
|
||||
isPlainObject,
|
||||
pluralize,
|
||||
} from '@redocly/openapi-core/lib/utils';
|
||||
import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
|
||||
import { deprecatedRefDocsSchema } from '@redocly/config/lib/reference-docs-config-schema';
|
||||
import { outputExtensions } from '../types';
|
||||
@@ -42,8 +48,7 @@ export async function getFallbackApisOrExit(
|
||||
config: ConfigApis
|
||||
): Promise<Entrypoint[]> {
|
||||
const { apis } = config;
|
||||
const shouldFallbackToAllDefinitions =
|
||||
!isNotEmptyArray(argsApis) && apis && Object.keys(apis).length > 0;
|
||||
const shouldFallbackToAllDefinitions = !isNotEmptyArray(argsApis) && isNotEmptyObject(apis);
|
||||
const res = shouldFallbackToAllDefinitions
|
||||
? fallbackToAllDefinitions(apis, config)
|
||||
: await expandGlobsInEntrypoints(argsApis!, config);
|
||||
@@ -64,10 +69,6 @@ function getConfigDirectory(config: ConfigApis) {
|
||||
return config.configFile ? dirname(config.configFile) : process.cwd();
|
||||
}
|
||||
|
||||
function isNotEmptyArray<T>(args?: T[]): boolean {
|
||||
return Array.isArray(args) && !!args.length;
|
||||
}
|
||||
|
||||
function isApiPathValid(apiPath: string): string | void {
|
||||
if (!apiPath.trim()) {
|
||||
exitWithError('Path cannot be empty.');
|
||||
@@ -80,15 +81,21 @@ function fallbackToAllDefinitions(
|
||||
apis: Record<string, ResolvedApi>,
|
||||
config: ConfigApis
|
||||
): Entrypoint[] {
|
||||
return Object.entries(apis).map(([alias, { root }]) => ({
|
||||
return Object.entries(apis).map(([alias, { root, output }]) => ({
|
||||
path: isAbsoluteUrl(root) ? root : resolve(getConfigDirectory(config), root),
|
||||
alias,
|
||||
output: output && resolve(getConfigDirectory(config), output),
|
||||
}));
|
||||
}
|
||||
|
||||
function getAliasOrPath(config: ConfigApis, aliasOrPath: string): Entrypoint {
|
||||
return config.apis[aliasOrPath]
|
||||
? { path: config.apis[aliasOrPath]?.root, alias: aliasOrPath }
|
||||
const aliasApi = config.apis[aliasOrPath];
|
||||
return aliasApi
|
||||
? {
|
||||
path: aliasApi.root,
|
||||
alias: aliasOrPath,
|
||||
output: aliasApi.output,
|
||||
}
|
||||
: {
|
||||
path: aliasOrPath,
|
||||
// find alias by path, take the first match
|
||||
@@ -99,10 +106,10 @@ function getAliasOrPath(config: ConfigApis, aliasOrPath: string): Entrypoint {
|
||||
};
|
||||
}
|
||||
|
||||
async function expandGlobsInEntrypoints(args: string[], config: ConfigApis) {
|
||||
async function expandGlobsInEntrypoints(argApis: string[], config: ConfigApis) {
|
||||
return (
|
||||
await Promise.all(
|
||||
(args as string[]).map(async (aliasOrPath) => {
|
||||
argApis.map(async (aliasOrPath) => {
|
||||
return glob.hasMagic(aliasOrPath) && !isAbsoluteUrl(aliasOrPath)
|
||||
? (await promisify(glob)(aliasOrPath)).map((g: string) => getAliasOrPath(config, g))
|
||||
: getAliasOrPath(config, aliasOrPath);
|
||||
@@ -356,33 +363,20 @@ export function printConfigLintTotals(totals: Totals, command?: string | number)
|
||||
}
|
||||
}
|
||||
|
||||
export function getOutputFileName(
|
||||
entrypoint: string,
|
||||
entries: number,
|
||||
output?: string,
|
||||
ext?: BundleOutputFormat
|
||||
) {
|
||||
if (!output) {
|
||||
return { outputFile: 'stdout', ext: ext || 'yaml' };
|
||||
export function getOutputFileName(entrypoint: string, output?: string, ext?: BundleOutputFormat) {
|
||||
let outputFile = output;
|
||||
if (!outputFile) {
|
||||
return { ext: ext || 'yaml' };
|
||||
}
|
||||
|
||||
let outputFile = output;
|
||||
if (entries > 1) {
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(output, basename(entrypoint, extname(entrypoint))) + '.' + ext;
|
||||
} else {
|
||||
if (output) {
|
||||
ext = ext || (extname(output).substring(1) as BundleOutputFormat);
|
||||
}
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
|
||||
if (outputFile) {
|
||||
ext = ext || (extname(outputFile).substring(1) as BundleOutputFormat);
|
||||
}
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
|
||||
return { outputFile, ext };
|
||||
}
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ export type DeprecatedInRawConfig = {
|
||||
|
||||
export type Api = {
|
||||
root: string;
|
||||
output?: string;
|
||||
styleguide?: ApiStyleguideRawConfig;
|
||||
} & ThemeConfig;
|
||||
|
||||
|
||||
@@ -54,10 +54,18 @@ export function isEmptyObject(value: unknown): value is Record<string, unknown>
|
||||
return isPlainObject(value) && Object.keys(value).length === 0;
|
||||
}
|
||||
|
||||
export function isNotEmptyObject(obj: unknown): boolean {
|
||||
return isPlainObject(obj) && !isEmptyObject(obj);
|
||||
}
|
||||
|
||||
export function isEmptyArray(value: unknown) {
|
||||
return Array.isArray(value) && value.length === 0;
|
||||
}
|
||||
|
||||
export function isNotEmptyArray<T>(args?: T[]): boolean {
|
||||
return !!args && Array.isArray(args) && !!args.length;
|
||||
}
|
||||
|
||||
export async function readFileFromUrl(url: string, config: HttpResolveConfig) {
|
||||
const headers: Record<string, string> = {};
|
||||
for (const header of config.headers) {
|
||||
@@ -179,10 +187,6 @@ export function slash(path: string): string {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
export function isNotEmptyObject(obj: any) {
|
||||
return !!obj && Object.keys(obj).length > 0;
|
||||
}
|
||||
|
||||
// TODO: use it everywhere
|
||||
export function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
|
||||
Reference in New Issue
Block a user