Skip to content

Commit

Permalink
chore: restrict bundling with --output but without inline apis (#1799)
Browse files Browse the repository at this point in the history
* 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 <164185257+JLekawa@users.noreply.github.com>
  • Loading branch information
tatomyr and JLekawa authored Nov 26, 2024
1 parent c6e42d2 commit e4e6782
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 100 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
37 changes: 19 additions & 18 deletions docs/commands/bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <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. **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. <br/> **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

Expand All @@ -65,7 +65,7 @@ dist/openapi.json
dist/museum.json
</pre>

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
Expand All @@ -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

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

1 comment on commit e4e6782

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 78.82% 5029/6380
🟡 Branches 67.19% 2050/3051
🟡 Functions 73.35% 831/1133
🟡 Lines 79.12% 4743/5995

Test suite run success

814 tests passing in 121 suites.

Report generated by 🧪jest coverage report action from e4e6782

Please sign in to comment.