diff --git a/.changeset/fresh-spies-yawn.md b/.changeset/fresh-spies-yawn.md new file mode 100644 index 0000000000..dbdbabc17f --- /dev/null +++ b/.changeset/fresh-spies-yawn.md @@ -0,0 +1,5 @@ +--- +"@redocly/cli": patch +--- + +Clarified usage of the `--output` option in the `bundle` command. diff --git a/__tests__/bundle/bundle-no-output-without-inline-apis/redocly.yaml b/__tests__/bundle/bundle-no-output-without-inline-apis/redocly.yaml new file mode 100644 index 0000000000..569b57f4fe --- /dev/null +++ b/__tests__/bundle/bundle-no-output-without-inline-apis/redocly.yaml @@ -0,0 +1,7 @@ +apis: + main: + root: ./test.yaml + rules: + no-invalid-media-type-examples: + severity: error + allowAdditionalProperties: false diff --git a/__tests__/bundle/bundle-no-output-without-inline-apis/snapshot.js b/__tests__/bundle/bundle-no-output-without-inline-apis/snapshot.js new file mode 100644 index 0000000000..b25d9f6584 --- /dev/null +++ b/__tests__/bundle/bundle-no-output-without-inline-apis/snapshot.js @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`E2E bundle bundle should NOT be invoked IF no positional apis provided AND --output specified 1`] = ` + +index.ts bundle [apis...] + +Bundle a multi-file API description to a single file. + +Positionals: + apis [array] [default: []] + +Options: + --version Show version number. [boolean] + --help Show help. [boolean] + -o, --output Output file or folder for inline APIs.[string] + --ext Bundle file extension. + [choices: "json", "yaml", "yml"] + --skip-preprocessor Ignore certain preprocessors. [array] + --skip-decorator Ignore certain decorators. [array] + -d, --dereferenced Produce a fully dereferenced bundle. [boolean] + -f, --force Produce bundle output even when errors occur. + [boolean] + --config Path to the config file. [string] + --metafile Produce metadata about the bundle [string] + --remove-unused-components Remove unused components. + [boolean] [default: false] + -k, --keep-url-references Keep absolute url references. [boolean] + --lint-config Severity level for config file linting. + [choices: "warn", "error", "off"] [default: "warn"] + +At least one inline API must be specified when using --output. + +`; diff --git a/__tests__/bundle/bundle-no-output-without-inline-apis/test.yaml b/__tests__/bundle/bundle-no-output-without-inline-apis/test.yaml new file mode 100644 index 0000000000..03aaca027d --- /dev/null +++ b/__tests__/bundle/bundle-no-output-without-inline-apis/test.yaml @@ -0,0 +1,25 @@ +openapi: 3.1.0 +paths: + /test-api: + get: + responses: + '200': + description: success + content: + application/json: + schema: + $defs: + main_data: + $anchor: main_data + type: object + properties: + foo: + type: string + type: object + oneOf: + - properties: + wrapper: + $ref: '#main_data' + - $ref: '#main_data' + example: + foo: TEST diff --git a/__tests__/commands.test.ts b/__tests__/commands.test.ts index af20c65410..642660b7de 100644 --- a/__tests__/commands.test.ts +++ b/__tests__/commands.test.ts @@ -451,6 +451,7 @@ describe('E2E', () => { 'bundle-remove-unused-components', 'bundle-remove-unused-components-from-config', 'bundle-arazzo-valid-test-description', + 'bundle-no-output-without-inline-apis', ]; const folderPath = join(__dirname, 'bundle'); const contents = readdirSync(folderPath).filter((folder) => !excludeFolders.includes(folder)); @@ -478,6 +479,13 @@ describe('E2E', () => { const result = getCommandOutput(args, testPath); (expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js')); }); + + test('bundle should NOT be invoked IF no positional apis provided AND --output specified', () => { + const testPath = join(folderPath, 'bundle-no-output-without-inline-apis'); + const args = getParams('../../../packages/cli/src/index.ts', 'bundle', ['--output=dist']); + const result = getCommandOutput(args, testPath); + (expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js')); + }); }); describe('bundle with option: remove-unused-components', () => { diff --git a/docs/commands/bundle.md b/docs/commands/bundle.md index 062adc7f75..9deee04833 100644 --- a/docs/commands/bundle.md +++ b/docs/commands/bundle.md @@ -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 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 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 | Preserve absolute URL references. | -| --lint-config | string | Specify the severity level for the configuration file.
**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. **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). | -| --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 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 the bundled file's extension. The possible values are `json`, `yaml`, or `yml`. The 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 | Preserve absolute URL references. | +| --lint-config | string | Specify the severity level for the configuration file.
**Possible values:** `warn`, `error`, `off`. The default value is `warn`. | +| --metafile | string | Path for the bundle metadata file. | +| --output, -o | string | Name or folder for the bundle file specified using the command line. 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). | +| --version | boolean | Show version number. | ## Examples @@ -65,7 +65,7 @@ dist/openapi.json dist/museum.json -You can specify the default `output` location for a bundled API in the `apis` section of your Redocly configuration file. +Alternatively, 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 @@ -85,6 +85,7 @@ redocly bundle ``` Please note, that providing an API to the `bundle` command results in the command bundling only the specified API. +Additionally, the `--output` option is only meaningful when used with APIs specified in the command line. ### Create a fully dereferenced bundle diff --git a/packages/cli/src/__tests__/commands/bundle.test.ts b/packages/cli/src/__tests__/commands/bundle.test.ts index 640e1bded3..599ddbfe6c 100644 --- a/packages/cli/src/__tests__/commands/bundle.test.ts +++ b/packages/cli/src/__tests__/commands/bundle.test.ts @@ -209,7 +209,7 @@ describe('bundle', () => { expect(process.stdout.write).toHaveBeenCalledTimes(1); }); - 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 () => { + it('should NOT store bundled API descriptions in the output files described in the apis section of config IF there is a positional api provided', async () => { const apis = { foo: { root: 'foo.yaml', diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ab3f33df50..73b1a3f223 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -473,86 +473,94 @@ yargs 'bundle [apis...]', 'Bundle a multi-file API description to a single file.', (yargs) => - yargs.positional('apis', { array: true, type: 'string', demandOption: true }).options({ - output: { - type: 'string', - description: 'Output file.', - alias: 'o', - }, - ext: { - description: 'Bundle file extension.', - requiresArg: true, - choices: outputExtensions, - }, - 'skip-preprocessor': { - description: 'Ignore certain preprocessors.', - array: true, - type: 'string', - }, - 'skip-decorator': { - description: 'Ignore certain decorators.', - array: true, - type: 'string', - }, - dereferenced: { - alias: 'd', - type: 'boolean', - description: 'Produce a fully dereferenced bundle.', - }, - force: { - alias: 'f', - type: 'boolean', - description: 'Produce bundle output even when errors occur.', - }, - config: { - description: 'Path to the config file.', - type: 'string', - }, - metafile: { - description: 'Produce metadata about the bundle', - type: 'string', - }, - extends: { - description: 'Override extends configurations (defaults or config file settings).', - requiresArg: true, - array: true, - type: 'string', - hidden: true, - }, - 'remove-unused-components': { - description: 'Remove unused components.', - type: 'boolean', - default: false, - }, - 'keep-url-references': { - description: 'Keep absolute url references.', - type: 'boolean', - alias: 'k', - }, - 'lint-config': { - description: 'Severity level for config file linting.', - choices: ['warn', 'error', 'off'] as ReadonlyArray, - default: 'warn' as RuleSeverity, - }, - format: { - hidden: true, - deprecated: true, - }, - lint: { - hidden: true, - deprecated: true, - }, - 'skip-rule': { - hidden: true, - deprecated: true, - array: true, - type: 'string', - }, - 'max-problems': { - hidden: true, - deprecated: true, - }, - }), + yargs + .positional('apis', { array: true, type: 'string', demandOption: true }) + .options({ + output: { + type: 'string', + description: 'Output file or folder for inline APIs.', + alias: 'o', + }, + ext: { + description: 'Bundle file extension.', + requiresArg: true, + choices: outputExtensions, + }, + 'skip-preprocessor': { + description: 'Ignore certain preprocessors.', + array: true, + type: 'string', + }, + 'skip-decorator': { + description: 'Ignore certain decorators.', + array: true, + type: 'string', + }, + dereferenced: { + alias: 'd', + type: 'boolean', + description: 'Produce a fully dereferenced bundle.', + }, + force: { + alias: 'f', + type: 'boolean', + description: 'Produce bundle output even when errors occur.', + }, + config: { + description: 'Path to the config file.', + type: 'string', + }, + metafile: { + description: 'Produce metadata about the bundle', + type: 'string', + }, + extends: { + description: 'Override extends configurations (defaults or config file settings).', + requiresArg: true, + array: true, + type: 'string', + hidden: true, + }, + 'remove-unused-components': { + description: 'Remove unused components.', + type: 'boolean', + default: false, + }, + 'keep-url-references': { + description: 'Keep absolute url references.', + type: 'boolean', + alias: 'k', + }, + 'lint-config': { + description: 'Severity level for config file linting.', + choices: ['warn', 'error', 'off'] as ReadonlyArray, + default: 'warn' as RuleSeverity, + }, + format: { + hidden: true, + deprecated: true, + }, + lint: { + hidden: true, + deprecated: true, + }, + 'skip-rule': { + hidden: true, + deprecated: true, + array: true, + type: 'string', + }, + 'max-problems': { + hidden: true, + deprecated: true, + }, + }) + .check((argv) => { + if (argv.output && (!argv.apis || argv.apis.length === 0)) { + throw new Error('At least one inline API must be specified when using --output.'); + } + return true; + }), (argv) => { const DEPRECATED_OPTIONS = ['lint', 'format', 'skip-rule', 'max-problems']; const LINT_AND_BUNDLE_DOCUMENTATION_LINK = @@ -778,7 +786,7 @@ yargs }) .check((argv: any) => { if (argv.theme && !argv.theme?.openapi) - throw Error('Invalid option: theme.openapi not set'); + throw Error('Invalid option: theme.openapi not set.'); return true; }), async (argv) => {