Skip to content

Commit 03e6986

Browse files
committed
chore: restrict bundling with --output but without inline apis
1 parent 4cfa0ce commit 03e6986

File tree

7 files changed

+168
-82
lines changed

7 files changed

+168
-82
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', () => {

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)