Skip to content

Commit e4e6782

Browse files
tatomyrJLekawa
andauthored
chore: restrict bundling with --output but without inline apis (#1799)
* docs: update bundle description * Apply suggestions from code review * apply prettier * chore: restrict bundling with --output but without inline apis --------- Co-authored-by: Jacek Łękawa <[email protected]>
1 parent c6e42d2 commit e4e6782

File tree

8 files changed

+187
-100
lines changed

8 files changed

+187
-100
lines changed

.changeset/fresh-spies-yawn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/cli": patch
3+
---
4+
5+
Clarified usage of the `--output` option in the `bundle` command.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apis:
2+
main:
3+
root: ./test.yaml
4+
rules:
5+
no-invalid-media-type-examples:
6+
severity: error
7+
allowAdditionalProperties: false
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`E2E bundle bundle should NOT be invoked IF no positional apis provided AND --output specified 1`] = `
4+
5+
index.ts bundle [apis...]
6+
7+
Bundle a multi-file API description to a single file.
8+
9+
Positionals:
10+
apis [array] [default: []]
11+
12+
Options:
13+
--version Show version number. [boolean]
14+
--help Show help. [boolean]
15+
-o, --output Output file or folder for inline APIs.[string]
16+
--ext Bundle file extension.
17+
[choices: "json", "yaml", "yml"]
18+
--skip-preprocessor Ignore certain preprocessors. [array]
19+
--skip-decorator Ignore certain decorators. [array]
20+
-d, --dereferenced Produce a fully dereferenced bundle. [boolean]
21+
-f, --force Produce bundle output even when errors occur.
22+
[boolean]
23+
--config Path to the config file. [string]
24+
--metafile Produce metadata about the bundle [string]
25+
--remove-unused-components Remove unused components.
26+
[boolean] [default: false]
27+
-k, --keep-url-references Keep absolute url references. [boolean]
28+
--lint-config Severity level for config file linting.
29+
[choices: "warn", "error", "off"] [default: "warn"]
30+
31+
At least one inline API must be specified when using --output.
32+
33+
`;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.1.0
2+
paths:
3+
/test-api:
4+
get:
5+
responses:
6+
'200':
7+
description: success
8+
content:
9+
application/json:
10+
schema:
11+
$defs:
12+
main_data:
13+
$anchor: main_data
14+
type: object
15+
properties:
16+
foo:
17+
type: string
18+
type: object
19+
oneOf:
20+
- properties:
21+
wrapper:
22+
$ref: '#main_data'
23+
- $ref: '#main_data'
24+
example:
25+
foo: TEST

__tests__/commands.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ describe('E2E', () => {
451451
'bundle-remove-unused-components',
452452
'bundle-remove-unused-components-from-config',
453453
'bundle-arazzo-valid-test-description',
454+
'bundle-no-output-without-inline-apis',
454455
];
455456
const folderPath = join(__dirname, 'bundle');
456457
const contents = readdirSync(folderPath).filter((folder) => !excludeFolders.includes(folder));
@@ -478,6 +479,13 @@ describe('E2E', () => {
478479
const result = getCommandOutput(args, testPath);
479480
(<any>expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js'));
480481
});
482+
483+
test('bundle should NOT be invoked IF no positional apis provided AND --output specified', () => {
484+
const testPath = join(folderPath, 'bundle-no-output-without-inline-apis');
485+
const args = getParams('../../../packages/cli/src/index.ts', 'bundle', ['--output=dist']);
486+
const result = getCommandOutput(args, testPath);
487+
(<any>expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js'));
488+
});
481489
});
482490

483491
describe('bundle with option: remove-unused-components', () => {

docs/commands/bundle.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,23 @@ redocly bundle --version
2222

2323
## Options
2424

25-
| Option | Type | Description |
26-
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
27-
| 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. |
28-
| --config | string | Specify the path to the [configuration file](#use-alternative-configuration-file). |
29-
| --dereferenced, -d | boolean | Generate fully dereferenced bundle. |
30-
| --ext | string | Specify the bundled file's extension. The possible values are `json`, `yaml`, or `yml`. Default value is `yaml`. |
31-
| --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. |
32-
| --force, -f | boolean | Generate a bundle output even when errors occur. |
33-
| --help | boolean | Show help. |
34-
| --keep-url-references, -k | boolean | Preserve absolute URL references. |
35-
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
36-
| --metafile | string | Path for the bundle metadata file. |
37-
| --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.** |
38-
| --remove-unused-components | boolean | Remove unused components from the `bundle` output. |
39-
| --skip-decorator | [string] | Ignore certain decorators. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
40-
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
41-
| --version | boolean | Show version number. |
25+
| Option | Type | Description |
26+
| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27+
| 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. |
28+
| --config | string | Specify the path to the [configuration file](#use-alternative-configuration-file). |
29+
| --dereferenced, -d | boolean | Generate fully dereferenced bundle. |
30+
| --ext | string | Specify the bundled file's extension. The possible values are `json`, `yaml`, or `yml`. The default value is `yaml`. |
31+
| --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. |
32+
| --force, -f | boolean | Generate a bundle output even when errors occur. |
33+
| --help | boolean | Show help. |
34+
| --keep-url-references, -k | boolean | Preserve absolute URL references. |
35+
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. The default value is `warn`. |
36+
| --metafile | string | Path for the bundle metadata file. |
37+
| --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.** |
38+
| --remove-unused-components | boolean | Remove unused components from the `bundle` output. |
39+
| --skip-decorator | [string] | Ignore certain decorators. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
40+
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor, rule, or decorator section](#skip-preprocessor-rule-or-decorator). |
41+
| --version | boolean | Show version number. |
4242

4343
## Examples
4444

@@ -65,7 +65,7 @@ dist/openapi.json
6565
dist/museum.json
6666
</pre>
6767

68-
You can specify the default `output` location for a bundled API in the `apis` section of your Redocly configuration file.
68+
Alternatively, you can specify the default `output` location for a bundled API in the `apis` section of your Redocly configuration file.
6969
This is especially useful when bundling multiple APIs.
7070

7171
```yaml
@@ -85,6 +85,7 @@ redocly bundle
8585
```
8686

8787
Please note, that providing an API to the `bundle` command results in the command bundling only the specified API.
88+
Additionally, the `--output` option is only meaningful when used with APIs specified in the command line.
8889

8990
### Create a fully dereferenced bundle
9091

packages/cli/src/__tests__/commands/bundle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('bundle', () => {
209209
expect(process.stdout.write).toHaveBeenCalledTimes(1);
210210
});
211211

212-
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 () => {
212+
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 () => {
213213
const apis = {
214214
foo: {
215215
root: 'foo.yaml',

packages/cli/src/index.ts

Lines changed: 89 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -473,86 +473,94 @@ yargs
473473
'bundle [apis...]',
474474
'Bundle a multi-file API description to a single file.',
475475
(yargs) =>
476-
yargs.positional('apis', { array: true, type: 'string', demandOption: true }).options({
477-
output: {
478-
type: 'string',
479-
description: 'Output file.',
480-
alias: 'o',
481-
},
482-
ext: {
483-
description: 'Bundle file extension.',
484-
requiresArg: true,
485-
choices: outputExtensions,
486-
},
487-
'skip-preprocessor': {
488-
description: 'Ignore certain preprocessors.',
489-
array: true,
490-
type: 'string',
491-
},
492-
'skip-decorator': {
493-
description: 'Ignore certain decorators.',
494-
array: true,
495-
type: 'string',
496-
},
497-
dereferenced: {
498-
alias: 'd',
499-
type: 'boolean',
500-
description: 'Produce a fully dereferenced bundle.',
501-
},
502-
force: {
503-
alias: 'f',
504-
type: 'boolean',
505-
description: 'Produce bundle output even when errors occur.',
506-
},
507-
config: {
508-
description: 'Path to the config file.',
509-
type: 'string',
510-
},
511-
metafile: {
512-
description: 'Produce metadata about the bundle',
513-
type: 'string',
514-
},
515-
extends: {
516-
description: 'Override extends configurations (defaults or config file settings).',
517-
requiresArg: true,
518-
array: true,
519-
type: 'string',
520-
hidden: true,
521-
},
522-
'remove-unused-components': {
523-
description: 'Remove unused components.',
524-
type: 'boolean',
525-
default: false,
526-
},
527-
'keep-url-references': {
528-
description: 'Keep absolute url references.',
529-
type: 'boolean',
530-
alias: 'k',
531-
},
532-
'lint-config': {
533-
description: 'Severity level for config file linting.',
534-
choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
535-
default: 'warn' as RuleSeverity,
536-
},
537-
format: {
538-
hidden: true,
539-
deprecated: true,
540-
},
541-
lint: {
542-
hidden: true,
543-
deprecated: true,
544-
},
545-
'skip-rule': {
546-
hidden: true,
547-
deprecated: true,
548-
array: true,
549-
type: 'string',
550-
},
551-
'max-problems': {
552-
hidden: true,
553-
deprecated: true,
554-
},
555-
}),
476+
yargs
477+
.positional('apis', { array: true, type: 'string', demandOption: true })
478+
.options({
479+
output: {
480+
type: 'string',
481+
description: 'Output file or folder for inline APIs.',
482+
alias: 'o',
483+
},
484+
ext: {
485+
description: 'Bundle file extension.',
486+
requiresArg: true,
487+
choices: outputExtensions,
488+
},
489+
'skip-preprocessor': {
490+
description: 'Ignore certain preprocessors.',
491+
array: true,
492+
type: 'string',
493+
},
494+
'skip-decorator': {
495+
description: 'Ignore certain decorators.',
496+
array: true,
497+
type: 'string',
498+
},
499+
dereferenced: {
500+
alias: 'd',
501+
type: 'boolean',
502+
description: 'Produce a fully dereferenced bundle.',
503+
},
504+
force: {
505+
alias: 'f',
506+
type: 'boolean',
507+
description: 'Produce bundle output even when errors occur.',
508+
},
509+
config: {
510+
description: 'Path to the config file.',
511+
type: 'string',
512+
},
513+
metafile: {
514+
description: 'Produce metadata about the bundle',
515+
type: 'string',
516+
},
517+
extends: {
518+
description: 'Override extends configurations (defaults or config file settings).',
519+
requiresArg: true,
520+
array: true,
521+
type: 'string',
522+
hidden: true,
523+
},
524+
'remove-unused-components': {
525+
description: 'Remove unused components.',
526+
type: 'boolean',
527+
default: false,
528+
},
529+
'keep-url-references': {
530+
description: 'Keep absolute url references.',
531+
type: 'boolean',
532+
alias: 'k',
533+
},
534+
'lint-config': {
535+
description: 'Severity level for config file linting.',
536+
choices: ['warn', 'error', 'off'] as ReadonlyArray<RuleSeverity>,
537+
default: 'warn' as RuleSeverity,
538+
},
539+
format: {
540+
hidden: true,
541+
deprecated: true,
542+
},
543+
lint: {
544+
hidden: true,
545+
deprecated: true,
546+
},
547+
'skip-rule': {
548+
hidden: true,
549+
deprecated: true,
550+
array: true,
551+
type: 'string',
552+
},
553+
'max-problems': {
554+
hidden: true,
555+
deprecated: true,
556+
},
557+
})
558+
.check((argv) => {
559+
if (argv.output && (!argv.apis || argv.apis.length === 0)) {
560+
throw new Error('At least one inline API must be specified when using --output.');
561+
}
562+
return true;
563+
}),
556564
(argv) => {
557565
const DEPRECATED_OPTIONS = ['lint', 'format', 'skip-rule', 'max-problems'];
558566
const LINT_AND_BUNDLE_DOCUMENTATION_LINK =
@@ -778,7 +786,7 @@ yargs
778786
})
779787
.check((argv: any) => {
780788
if (argv.theme && !argv.theme?.openapi)
781-
throw Error('Invalid option: theme.openapi not set');
789+
throw Error('Invalid option: theme.openapi not set.');
782790
return true;
783791
}),
784792
async (argv) => {

0 commit comments

Comments
 (0)