fix: bundling multiple files specified as CLI arguments (#1717)

This commit is contained in:
Andrew Tatomyr
2024-09-06 18:52:09 +03:00
committed by GitHub
parent 0cf2ee6ffc
commit 0eee406b98
5 changed files with 180 additions and 108 deletions

View File

@@ -0,0 +1,5 @@
---
"@redocly/cli": patch
---
Fixed a bug where bundling multiple API description files specified as CLI arguments, along with the `--output` option, stored the result in a single file instead of a folder.

View File

@@ -23,18 +23,18 @@ 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). |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| apis | [string] | List of API description root filenames or names assigned in the `apis` section of your Redocly configuration file. Default values are names defined in the `apis` section of your configuration file. |
| --config | string | Specify the 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. |
| --ext | string | Specify the bundled file's extension. The 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). The default values are taken from the Redocly configuration file. |
| --force, -f | boolean | Generate a bundle output even when errors occur. |
| --help | boolean | Show help. |
| --keep-url-references, -k | boolean | Keep absolute url references. |
| --keep-url-references, -k | boolean | Preserve 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. |
| --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. **Overwrites existing bundler output file.** |
| --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). |

View File

@@ -127,6 +127,7 @@ describe('bundle', () => {
expect(handleError).toHaveBeenCalledTimes(0);
});
describe('per api output', () => {
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: {
@@ -208,7 +209,6 @@ describe('bundle', () => {
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: {
@@ -240,5 +240,46 @@ describe('bundle', () => {
expect(saveBundle).toBeCalledTimes(0);
expect(process.stdout.write).toHaveBeenCalledTimes(1);
});
it('should store bundled API descriptions in the directory specified in argv IF multiple positional apis provided AND --output specified', 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: ['foo.yaml', 'bar.yaml'], output: 'dist' }, // cli options
version: 'test',
config,
});
expect(saveBundle).toBeCalledTimes(2);
expect(saveBundle).toHaveBeenNthCalledWith(1, 'dist/foo.yaml', expect.any(String));
expect(saveBundle).toHaveBeenNthCalledWith(2, 'dist/bar.yaml', expect.any(String));
});
});
});

View File

@@ -70,7 +70,13 @@ export async function handleBundle({
});
const fileTotals = getTotals(problems);
const { outputFile, ext } = getOutputFileName(path, output || argv.output, argv.ext);
const { outputFile, ext } = getOutputFileName({
entrypoint: path,
output,
argvOutput: argv.output,
ext: argv.ext,
entries: argv?.apis?.length || 0,
});
if (fileTotals.errors === 0 || argv.force) {
if (!outputFile) {

View File

@@ -363,20 +363,40 @@ export function printConfigLintTotals(totals: Totals, command?: string | number)
}
}
export function getOutputFileName(entrypoint: string, output?: string, ext?: BundleOutputFormat) {
let outputFile = output;
export function getOutputFileName({
entrypoint,
output,
argvOutput,
ext,
entries,
}: {
entrypoint: string;
output?: string;
argvOutput?: string;
ext?: BundleOutputFormat;
entries: number;
}) {
let outputFile = output || argvOutput;
if (!outputFile) {
return { ext: ext || 'yaml' };
}
if (outputFile) {
ext = ext || (extname(outputFile).substring(1) as BundleOutputFormat);
}
if (entries > 1 && argvOutput) {
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
if (!outputExtensions.includes(ext)) {
throw new Error(`Invalid file extension: ${ext}.`);
}
outputFile = join(argvOutput, basename(entrypoint, extname(entrypoint))) + '.' + ext;
} else {
ext =
ext ||
(extname(outputFile).substring(1) as BundleOutputFormat) ||
(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 };
}