Skip to content

Commit

Permalink
chore: restrict bundling with --output but without inline apis
Browse files Browse the repository at this point in the history
  • Loading branch information
tatomyr committed Nov 25, 2024
1 parent 4cfa0ce commit 03e6986
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-spies-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@redocly/cli": patch
---

Clarified usage of the `--output` option in the `bundle` command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apis:
main:
root: ./test.yaml
rules:
no-invalid-media-type-examples:
severity: error
allowAdditionalProperties: false
33 changes: 33 additions & 0 deletions __tests__/bundle/bundle-no-output-without-inline-apis/snapshot.js
Original file line number Diff line number Diff line change
@@ -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.
`;
25 changes: 25 additions & 0 deletions __tests__/bundle/bundle-no-output-without-inline-apis/test.yaml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions __tests__/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -478,6 +479,13 @@ describe('E2E', () => {
const result = getCommandOutput(args, testPath);
(<any>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);
(<any>expect(cleanupOutput(result))).toMatchSpecificSnapshot(join(testPath, 'snapshot.js'));
});
});

describe('bundle with option: remove-unused-components', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/__tests__/commands/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
170 changes: 89 additions & 81 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RuleSeverity>,
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<RuleSeverity>,
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 =
Expand Down Expand Up @@ -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) => {
Expand Down

0 comments on commit 03e6986

Please sign in to comment.