Repository: Redocly/redocly-cli
Pull request #1305 (2023-10-19 08:20:26)

feat: extend split and join commands to produce JSON output
## What/Why/How?
 
Extend `split` and `join` commands to optionally produce JSON output.

### Split

Determine the output format by the extension of the input file, if the extension is not supported use `yaml`
Supported file extensions: `json`, `yaml`, `yml`

### Join 

Determine the output format by the extension of the file from `output` option, if output option is not provided use extension of first entry point, if extension not supported use `yaml`.

Supported file extensions: `json`, `yaml`, `yml`


## Reference

Related: https://github.com/Redocly/redocly-cli/issues/1205

## Testing

## Screenshots (optional)

## Check yourself

- [x] Code is linted
- [ ] Tested with redoc/reference-docs/workflows (internal)
- [x] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 53b3e9f6a6032b1314af1e806c7b0b07a69f5349

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 2 packages</summary>

  | Name                  | Type  |
| --------------------- | ----- |
| @redocly/cli          | Patch |
| @redocly/openapi-core | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/Redocly/redocly-cli/new/feat/split-and-join-output?filename=.changeset/smart-comics-breathe.md&value=---%0A%22%40redocly%2Fcli%22%3A%20patch%0A---%0A%0Afeat%3A%20extend%20split%20and%20join%20commands%20to%20produce%20JSON%20output%0A)


github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 



| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 76.07%     |    4034/5303    |
|                                                                           🟡                                                                           | Branches   | 66%        |    2147/3253    |
|                                                                           🟡                                                                           | Functions  | 68.16%     |     655/961     |
|                                                                           🟡                                                                           | Lines      | 76.27%     |    3782/4959    |


## Test suite run success
647 tests passing in 93 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from 53b3e9f6a6032b1314af1e806c7b0b07a69f5349</p>


Patch:
diff --git a/.changeset/plenty-mugs-suffer.md b/.changeset/plenty-mugs-suffer.md
new file mode 100644
index 00000000..d585dd51
--- /dev/null
+++ b/.changeset/plenty-mugs-suffer.md
@@ -0,0 +1,5 @@
+---
+'@redocly/cli': minor
+---
+
+Add JSON output support to the `split` and `join` commands.
diff --git a/docs/commands/join.md b/docs/commands/join.md
index 8e5c24d3..1e8c5bc3 100644
--- a/docs/commands/join.md
+++ b/docs/commands/join.md
@@ -14,9 +14,9 @@ With Redocly CLI, you can solve this problem by using the `join` command that ca
 
 To easily distinguish the origin of OpenAPI objects and properties, you can optionally instruct the `join` command to append custom prefixes to them.
 
-The `join` command accepts both YAML and JSON files, which you can mix in the resulting `openapi.yaml` file. Setting a custom name for this file can be achieved by providing it through the `--output` argument. Any existing file is overwritten.
+The `join` command accepts both YAML and JSON files, which you can mix in the resulting `openapi.yaml` or `openapi.json` file. Setting a custom name and extension for this file can be achieved by providing it through the `--output` argument. Any existing file is overwritten. If the `--output` option is not provided, the command uses the extension of the first entry point file.
 
-Apart from providing individual API description files as the input, you can also specify the path to a folder that contains multiple API description files and match them with a wildcard (for example, `myproject/openapi/*.yaml`). The `join` command collects all matching files and combines them into one file.
+Apart from providing individual API description files as the input, you can also specify the path to a folder that contains multiple API description files and match them with a wildcard (for example, `myproject/openapi/*.(yaml/json)`). The `join` command collects all matching files and combines them into one file.
 
 ### Usage
 
@@ -43,7 +43,7 @@ redocly join --version
 | --help                             | boolean  | Show help.                                                                                                                                                                                                 |
 | --lint                             | boolean  | Lint API description files.                                                                                                                                                                                |
 | --lint-config                      | string   | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`.                                                                         |
-| --output, -o                       | string   | Name for the joined output file. Defaults to `openapi.yaml`. **If the file already exists, it's overwritten.**                                                                                             |
+| --output, -o                       | string   | Name for the joined output file. Defaults to `openapi.yaml` or `openapi.json` (Depends on the extension of the first input file). **If the file already exists, it's overwritten.**                        |
 | --prefix-components-with-info-prop | string   | Prefix components with property value from info object. See the [prefix-components-with-info-prop section](#prefix-components-with-info-prop) below.                                                       |
 | --prefix-tags-with-filename        | string   | Prefix tags with property value from file name. See the [prefix-tags-with-filename section](#prefix-tags-with-filename) below.                                                                             |
 | --prefix-tags-with-info-prop       | boolean  | Prefix tags with property value from info object. See the [prefix-tags-with-info-prop](#prefix-tags-with-info-prop) section.                                                                               |
@@ -274,7 +274,7 @@ components:
 
 ### Custom output file
 
-By default, the CLI tool writes the joined file as `openapi.yaml` in the current working directory. Use the optional `--output` argument to provide an alternative output file path.
+By default, the CLI tool writes the joined file as `openapi.yaml` or `openapi.json` in the current working directory. Use the optional `--output` argument to provide an alternative output file path.
 
 ```bash Command
 redocly join --output=openapi-custom.yaml
diff --git a/packages/cli/src/__mocks__/@redocly/openapi-core.ts b/packages/cli/src/__mocks__/@redocly/openapi-core.ts
index b0164e26..8347ad5b 100644
--- a/packages/cli/src/__mocks__/@redocly/openapi-core.ts
+++ b/packages/cli/src/__mocks__/@redocly/openapi-core.ts
@@ -31,6 +31,7 @@ export const doesYamlFileExist = jest.fn();
 export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
 export const detectSpec = jest.fn();
 export const isAbsoluteUrl = jest.fn();
+export const stringifyYaml = jest.fn((data) => data);
 
 export class BaseResolver {
   cache = new Map<string, Promise<Document | ResolveError>>();
diff --git a/packages/cli/src/__mocks__/utils.ts b/packages/cli/src/__mocks__/utils.ts
index bebdc01d..83824b25 100644
--- a/packages/cli/src/__mocks__/utils.ts
+++ b/packages/cli/src/__mocks__/utils.ts
@@ -17,3 +17,5 @@ export const writeYaml = jest.fn();
 export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
 export const checkIfRulesetExist = jest.fn();
 export const sortTopLevelKeysForOas = jest.fn((document) => document);
+export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
+export const writeToFileByExtension = jest.fn();
diff --git a/packages/cli/src/commands/join.ts b/packages/cli/src/commands/join.ts
index 087e4bef..49e23001 100644
--- a/packages/cli/src/commands/join.ts
+++ b/packages/cli/src/commands/join.ts
@@ -25,9 +25,10 @@ import {
   printExecutionTime,
   handleError,
   printLintTotals,
-  writeYaml,
   exitWithError,
   sortTopLevelKeysForOas,
+  getAndValidateFileExtension,
+  writeToFileByExtension,
 } from '../utils';
 import { isObject, isString, keysOf } from '../js-utils';
 import {
@@ -70,16 +71,19 @@ export type JoinOptions = {
 
 export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
   const startedAt = performance.now();
+
   if (argv.apis.length < 2) {
     return exitWithError(`At least 2 apis should be provided. \n\n`);
   }
 
+  const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
+
   const {
     'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
     'prefix-tags-with-filename': prefixTagsWithFilename,
     'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
     'without-x-tag-groups': withoutXTagGroups,
-    output: specFilename = 'openapi.yaml',
+    output: specFilename = `openapi.${fileExtension}`,
   } = argv;
 
   const usedTagsOptions = [
@@ -229,7 +233,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
     return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
   }
 
-  writeYaml(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
+  writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
+
   printExecutionTime('join', startedAt, specFilename);
 
   function populateTags({
diff --git a/packages/cli/src/commands/split/index.ts b/packages/cli/src/commands/split/index.ts
index 1f22ae3f..758d30ac 100644
--- a/packages/cli/src/commands/split/index.ts
+++ b/packages/cli/src/commands/split/index.ts
@@ -10,10 +10,11 @@ import {
   printExecutionTime,
   pathToFilename,
   readYaml,
-  writeYaml,
   exitWithError,
   escapeLanguageName,
   langToExt,
+  writeToFileByExtension,
+  getAndValidateFileExtension,
 } from '../../utils';
 import { isString, isObject, isEmptyObject } from '../../js-utils';
 import {
@@ -46,8 +47,9 @@ export async function handleSplit(argv: SplitOptions) {
   const startedAt = performance.now();
   const { api, outDir, separator } = argv;
   validateDefinitionFileName(api!);
+  const ext = getAndValidateFileExtension(api);
   const openapi = readYaml(api!) as Oas3Definition | Oas3_1Definition;
-  splitDefinition(openapi, outDir, separator);
+  splitDefinition(openapi, outDir, separator, ext);
   process.stderr.write(
     `🪓 Document: ${blue(api!)} ${green('is successfully split')}
     and all related files are saved to the directory: ${blue(outDir)} \n`
@@ -58,18 +60,21 @@ export async function handleSplit(argv: SplitOptions) {
 function splitDefinition(
   openapi: Oas3Definition | Oas3_1Definition,
   openapiDir: string,
-  pathSeparator: string
+  pathSeparator: string,
+  ext: string
 ) {
   fs.mkdirSync(openapiDir, { recursive: true });
 
   const componentsFiles: ComponentsFiles = {};
-  iterateComponents(openapi, openapiDir, componentsFiles);
+  iterateComponents(openapi, openapiDir, componentsFiles, ext);
   iteratePathItems(
     openapi.paths,
     openapiDir,
     path.join(openapiDir, 'paths'),
     componentsFiles,
-    pathSeparator
+    pathSeparator,
+    undefined,
+    ext
   );
   const webhooks =
     (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
@@ -80,11 +85,12 @@ function splitDefinition(
     path.join(openapiDir, 'webhooks'),
     componentsFiles,
     pathSeparator,
-    'webhook_'
+    'webhook_',
+    ext
   );
 
   replace$Refs(openapi, openapiDir, componentsFiles);
-  writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
+  writeToFileByExtension(openapi, path.join(openapiDir, `openapi.${ext}`));
 }
 
 function isStartsWithComponents(node: string) {
@@ -135,7 +141,7 @@ function traverseDirectoryDeepCallback(
   if (isNotYaml(filename)) return;
   const pathData = readYaml(filename);
   replace$Refs(pathData, directory, componentsFiles);
-  writeYaml(pathData, filename);
+  writeToFileByExtension(pathData, filename);
 }
 
 function crawl(object: any, visitor: any) {
@@ -251,8 +257,8 @@ function extractFileNameFromPath(filename: string) {
   return path.basename(filename, path.extname(filename));
 }
 
-function getFileNamePath(componentDirPath: string, componentName: string) {
-  return path.join(componentDirPath, componentName) + '.yaml';
+function getFileNamePath(componentDirPath: string, componentName: string, ext: string) {
+  return path.join(componentDirPath, componentName) + `.${ext}`;
 }
 
 function gatherComponentsFiles(
@@ -278,13 +284,14 @@ function iteratePathItems(
   outDir: string,
   componentsFiles: object,
   pathSeparator: string,
-  codeSamplesPathPrefix: string = ''
+  codeSamplesPathPrefix: string = '',
+  ext: string
 ) {
   if (!pathItems) return;
   fs.mkdirSync(outDir, { recursive: true });
 
   for (const pathName of Object.keys(pathItems)) {
-    const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.yaml`;
+    const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.${ext}`;
     const pathData = pathItems[pathName] as Oas3PathItem;
 
     if (isRef(pathData)) continue;
@@ -314,7 +321,7 @@ function iteratePathItems(
         };
       }
     }
-    writeYaml(pathData, pathFile);
+    writeToFileByExtension(pathData, pathFile);
     pathItems[pathName] = {
       $ref: slash(path.relative(openapiDir, pathFile)),
     };
@@ -326,7 +333,8 @@ function iteratePathItems(
 function iterateComponents(
   openapi: Oas3Definition | Oas3_1Definition,
   openapiDir: string,
-  componentsFiles: ComponentsFiles
+  componentsFiles: ComponentsFiles,
+  ext: string
 ) {
   const { components } = openapi;
   if (components) {
@@ -340,7 +348,7 @@ function iterateComponents(
     function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
       const componentDirPath = path.join(componentsDir, componentType);
       for (const componentName of Object.keys(components?.[componentType] || {})) {
-        const filename = getFileNamePath(componentDirPath, componentName);
+        const filename = getFileNamePath(componentDirPath, componentName, ext);
         gatherComponentsFiles(components!, componentsFiles, componentType, componentName, filename);
       }
     }
@@ -350,7 +358,7 @@ function iterateComponents(
       const componentDirPath = path.join(componentsDir, componentType);
       createComponentDir(componentDirPath, componentType);
       for (const componentName of Object.keys(components?.[componentType] || {})) {
-        const filename = getFileNamePath(componentDirPath, componentName);
+        const filename = getFileNamePath(componentDirPath, componentName, ext);
         const componentData = components?.[componentType]?.[componentName];
         replace$Refs(componentData, path.dirname(filename), componentsFiles);
         implicitlyReferenceDiscriminator(
@@ -369,7 +377,7 @@ function iterateComponents(
             )
           );
         } else {
-          writeYaml(componentData, filename);
+          writeToFileByExtension(componentData, filename);
         }
 
         if (isNotSecurityComponentType(componentType)) {
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index 0243c756..37369c54 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -127,7 +127,6 @@ yargs
             describe: 'Output file',
             alias: 'o',
             type: 'string',
-            default: 'openapi.yaml',
           },
           config: {
             description: 'Path to the config file.',
diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts
index 0e3fe865..27431653 100644
--- a/packages/cli/src/utils.ts
+++ b/packages/cli/src/utils.ts
@@ -25,7 +25,14 @@ import {
   RedoclyClient,
 } from '@redocly/openapi-core';
 import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
-import { Totals, outputExtensions, Entrypoint, ConfigApis, CommandOptions } from './types';
+import {
+  Totals,
+  outputExtensions,
+  Entrypoint,
+  ConfigApis,
+  CommandOptions,
+  OutputExtensions,
+} from './types';
 import { isEmptyObject } from '@redocly/openapi-core/lib/utils';
 import { Arguments } from 'yargs';
 import { version } from './update-version-notifier';
@@ -209,6 +216,17 @@ export function readYaml(filename: string) {
   return parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
 }
 
+export function writeToFileByExtension(data: unknown, filePath: string, noRefs?: boolean) {
+  const ext = getAndValidateFileExtension(filePath);
+
+  if (ext === 'json') {
+    writeJson(data, filePath);
+    return;
+  }
+
+  writeYaml(data, filePath, noRefs);
+}
+
 export function writeYaml(data: any, filename: string, noRefs = false) {
   const content = stringifyYaml(data, { noRefs });
 
@@ -220,6 +238,27 @@ export function writeYaml(data: any, filename: string, noRefs = false) {
   fs.writeFileSync(filename, content);
 }
 
+export function writeJson(data: unknown, filename: string) {
+  const content = JSON.stringify(data, null, 2);
+
+  if (process.env.NODE_ENV === 'test') {
+    process.stderr.write(content);
+    return;
+  }
+  fs.mkdirSync(dirname(filename), { recursive: true });
+  fs.writeFileSync(filename, content);
+}
+
+export function getAndValidateFileExtension(fileName: string): NonNullable<OutputExtensions> {
+  const ext = fileName.split('.').pop();
+
+  if (['yaml', 'yml', 'json'].includes(ext!)) {
+    return ext as NonNullable<OutputExtensions>;
+  }
+  process.stderr.write(yellow(`Unsupported file extension: ${ext}. Using yaml.\n`));
+  return 'yaml';
+}
+
 export function pluralize(label: string, num: number) {
   if (label.endsWith('is')) {
     [label] = label.split(' ');


Test patch:
diff --git a/__tests__/commands.test.ts b/__tests__/commands.test.ts
index e88f475c..fb672288 100644
--- a/__tests__/commands.test.ts
+++ b/__tests__/commands.test.ts
@@ -161,6 +161,19 @@ describe('E2E', () => {
       const result = getCommandOutput(args, folderPath);
       (<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
     });
+
+    test('openapi json file', () => {
+      const folderPath = join(__dirname, `split/openapi-json-file`);
+      const file = '../../../__tests__/split/openapi-json-file/openapi.json';
+
+      const args = getParams('../../../packages/cli/src/index.ts', 'split', [
+        file,
+        '--outDir=output',
+      ]);
+
+      const result = getCommandOutput(args, folderPath);
+      (<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
+    });
   });
 
   describe('join', () => {
@@ -214,6 +227,46 @@ describe('E2E', () => {
       const result = getCommandOutput(args, testPath);
       (<any>expect(result)).toMatchSpecificSnapshot(join(testPath, 'snapshot.js'));
     });
+
+    describe('files with different extensions', () => {
+      const joinParameters: {
+        name: string;
+        folder: string;
+        entrypoints: string[];
+        snapshot: string;
+        output?: string;
+      }[] = [
+        {
+          name: 'first entrypoint is a json file',
+          folder: 'json-and-yaml-input',
+          entrypoints: ['foo.json', 'bar.yaml'],
+          snapshot: 'json-output.snapshot.js',
+        },
+        {
+          name: 'first entrypoint is a yaml file',
+          folder: 'json-and-yaml-input',
+          entrypoints: ['bar.yaml', 'foo.json'],
+          snapshot: 'yaml-output.snapshot.js',
+        },
+        {
+          name: 'json output file',
+          folder: 'yaml-input-and-json-output',
+          entrypoints: ['foo.yaml', 'bar.yaml'],
+          output: 'openapi.json',
+          snapshot: 'snapshot.js',
+        },
+      ];
+
+      test.each(joinParameters)('test with option: %s', (parameters) => {
+        const testPath = join(__dirname, `join/${parameters.folder}`);
+        const argsWithOption = parameters.output
+          ? [...parameters.entrypoints, ...[`-o=${parameters.output}`]]
+          : parameters.entrypoints;
+        const args = getParams('../../../packages/cli/src/index.ts', 'join', argsWithOption);
+        const result = getCommandOutput(args, testPath);
+        (<any>expect(result)).toMatchSpecificSnapshot(join(testPath, parameters.snapshot));
+      });
+    });
   });
 
   describe('bundle', () => {
diff --git a/__tests__/join/json-and-yaml-input/bar.yaml b/__tests__/join/json-and-yaml-input/bar.yaml
new file mode 100644
index 00000000..8f24d577
--- /dev/null
+++ b/__tests__/join/json-and-yaml-input/bar.yaml
@@ -0,0 +1,23 @@
+openapi: 3.0.0
+info:
+  title: Example API
+  description: This is an example API.
+  version: 1.0.0
+servers:
+  - url: https://redocly-example.com/api
+paths:
+  /users/{userId}:
+    parameters:
+      - name: userId
+        in: path
+        description: ID of the user
+        required: true
+        schema:
+          type: integer
+    get:
+      summary: Get user by ID
+      responses:
+        '200':
+          description: OK
+        '404':
+          description: Not found
diff --git a/__tests__/join/json-and-yaml-input/foo.json b/__tests__/join/json-and-yaml-input/foo.json
new file mode 100644
index 00000000..433ff97f
--- /dev/null
+++ b/__tests__/join/json-and-yaml-input/foo.json
@@ -0,0 +1,49 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Example API",
+    "description": "This is an example API.",
+    "version": "1.0.0"
+  },
+  "servers": [
+    {
+      "url": "https://redocly-example.com/api"
+    }
+  ],
+  "paths": {
+    "/users/{userId}/orders/{orderId}": {
+      "parameters": [
+        {
+          "name": "userId",
+          "in": "path",
+          "description": "ID of the user",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        },
+        {
+          "name": "orderId",
+          "in": "path",
+          "description": "ID of the order",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        }
+      ],
+      "get": {
+        "x-private": true,
+        "summary": "Get an order by ID for a specific user",
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "404": {
+            "description": "Not found"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/__tests__/join/json-and-yaml-input/json-output.snapshot.js b/__tests__/join/json-and-yaml-input/json-output.snapshot.js
new file mode 100644
index 00000000..7a6c9b21
--- /dev/null
+++ b/__tests__/join/json-and-yaml-input/json-output.snapshot.js
@@ -0,0 +1,117 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E join files with different extensions test with option: {
+  name: 'first entrypoint is a json file',
+  folder: 'json-and-yaml-input',
+  entrypoints: [Array],
+  snapshot: 'json-output.snapshot.js'
+} 1`] = `
+
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Example API",
+    "description": "This is an example API.",
+    "version": "<version>"
+  },
+  "servers": [
+    {
+      "url": "https://redocly-example.com/api"
+    }
+  ],
+  "tags": [
+    {
+      "name": "foo_other",
+      "x-displayName": "other"
+    },
+    {
+      "name": "bar_other",
+      "x-displayName": "other"
+    }
+  ],
+  "paths": {
+    "/users/{userId}/orders/{orderId}": {
+      "parameters": [
+        {
+          "name": "userId",
+          "in": "path",
+          "description": "ID of the user",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        },
+        {
+          "name": "orderId",
+          "in": "path",
+          "description": "ID of the order",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        }
+      ],
+      "get": {
+        "x-private": true,
+        "summary": "Get an order by ID for a specific user",
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "404": {
+            "description": "Not found"
+          }
+        },
+        "tags": [
+          "foo_other"
+        ]
+      }
+    },
+    "/users/{userId}": {
+      "parameters": [
+        {
+          "name": "userId",
+          "in": "path",
+          "description": "ID of the user",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        }
+      ],
+      "get": {
+        "summary": "Get user by ID",
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "404": {
+            "description": "Not found"
+          }
+        },
+        "tags": [
+          "bar_other"
+        ]
+      }
+    }
+  },
+  "components": {},
+  "x-tagGroups": [
+    {
+      "name": "foo",
+      "tags": [
+        "foo_other"
+      ]
+    },
+    {
+      "name": "bar",
+      "tags": [
+        "bar_other"
+      ]
+    }
+  ]
+}
+openapi.json: join processed in <test>ms
+
+
+`;
diff --git a/__tests__/join/json-and-yaml-input/yaml-output.snapshot.js b/__tests__/join/json-and-yaml-input/yaml-output.snapshot.js
new file mode 100644
index 00000000..87fcac64
--- /dev/null
+++ b/__tests__/join/json-and-yaml-input/yaml-output.snapshot.js
@@ -0,0 +1,76 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E join files with different extensions test with option: {
+  name: 'first entrypoint is a yaml file',
+  folder: 'json-and-yaml-input',
+  entrypoints: [Array],
+  snapshot: 'yaml-output.snapshot.js'
+} 1`] = `
+
+openapi: 3.0.0
+info:
+  title: Example API
+  description: This is an example API.
+  version: 1.0.0
+servers:
+  - url: https://redocly-example.com/api
+tags:
+  - name: bar_other
+    x-displayName: other
+  - name: foo_other
+    x-displayName: other
+paths:
+  /users/{userId}:
+    parameters:
+      - name: userId
+        in: path
+        description: ID of the user
+        required: true
+        schema:
+          type: integer
+    get:
+      summary: Get user by ID
+      responses:
+        '200':
+          description: OK
+        '404':
+          description: Not found
+      tags:
+        - bar_other
+  /users/{userId}/orders/{orderId}:
+    parameters:
+      - name: userId
+        in: path
+        description: ID of the user
+        required: true
+        schema:
+          type: integer
+      - name: orderId
+        in: path
+        description: ID of the order
+        required: true
+        schema:
+          type: integer
+    get:
+      x-private: true
+      summary: Get an order by ID for a specific user
+      responses:
+        '200':
+          description: OK
+        '404':
+          description: Not found
+      tags:
+        - foo_other
+components: {}
+x-tagGroups:
+  - name: bar
+    tags:
+      - bar_other
+  - name: foo
+    tags:
+      - foo_other
+
+openapi.yaml: join processed in <test>ms
+
+
+`;
diff --git a/__tests__/join/multi-references-to-one-file/openapi.yaml b/__tests__/join/multi-references-to-one-file/openapi.yaml
new file mode 100644
index 00000000..ce36283a
--- /dev/null
+++ b/__tests__/join/multi-references-to-one-file/openapi.yaml
@@ -0,0 +1,84 @@
+openapi: 3.0.3
+info:
+  title: Sample API
+  description: My sample api
+  version: 0.0.1
+  license:
+    name: Internal
+    url: https://mycompany.com/license
+tags:
+  - name: GetSingleFoo
+    description: Get a single foo
+    x-displayName: GetSingleFoo
+  - name: Foo
+    description: All foo operations
+    x-displayName: Foo
+  - name: foo_other
+    x-displayName: other
+  - name: CreateBar
+    description: Create a new Bar
+    x-displayName: CreateBar
+  - name: bar_other
+    x-displayName: other
+paths:
+  /foo/{id}:
+    get:
+      summary: Returns a single foo
+      operationId: getFoo
+      responses:
+        '200':
+          description: One single Food
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Response'
+      tags:
+        - foo_other
+  /bar/:
+    post:
+      summary: Create a single bar
+      operationId: createBar
+      responses:
+        '200':
+          description: One single bar
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Response'
+      tags:
+        - bar_other
+components:
+  schemas:
+    FooObject:
+      type: object
+      properties:
+        x:
+          type: string
+        'y':
+          type: string
+    Response:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: string
+          format: uuid
+        name:
+          type: string
+        description:
+          type: string
+        subFoo:
+          $ref: '#/components/schemas/FooObject'
+x-tagGroups:
+  - name: foo
+    tags:
+      - GetSingleFoo
+      - Foo
+      - foo_other
+    description: My sample api
+  - name: bar
+    tags:
+      - CreateBar
+      - bar_other
diff --git a/__tests__/join/yaml-input-and-json-output/bar.yaml b/__tests__/join/yaml-input-and-json-output/bar.yaml
new file mode 100644
index 00000000..8f24d577
--- /dev/null
+++ b/__tests__/join/yaml-input-and-json-output/bar.yaml
@@ -0,0 +1,23 @@
+openapi: 3.0.0
+info:
+  title: Example API
+  description: This is an example API.
+  version: 1.0.0
+servers:
+  - url: https://redocly-example.com/api
+paths:
+  /users/{userId}:
+    parameters:
+      - name: userId
+        in: path
+        description: ID of the user
+        required: true
+        schema:
+          type: integer
+    get:
+      summary: Get user by ID
+      responses:
+        '200':
+          description: OK
+        '404':
+          description: Not found
diff --git a/__tests__/join/yaml-input-and-json-output/foo.yaml b/__tests__/join/yaml-input-and-json-output/foo.yaml
new file mode 100644
index 00000000..2ecbd051
--- /dev/null
+++ b/__tests__/join/yaml-input-and-json-output/foo.yaml
@@ -0,0 +1,30 @@
+openapi: 3.0.0
+info:
+  title: Example API
+  description: This is an example API.
+  version: 1.0.0
+servers:
+  - url: https://redocly-example.com/api
+paths:
+  /users/{userId}/orders/{orderId}:
+    parameters:
+      - name: userId
+        in: path
+        description: ID of the user
+        required: true
+        schema:
+          type: integer
+      - name: orderId
+        in: path
+        description: ID of the order
+        required: true
+        schema:
+          type: integer
+    get:
+      x-private: true
+      summary: Get an order by ID for a specific user
+      responses:
+        '200':
+          description: OK
+        '404':
+          description: Not found
diff --git a/__tests__/join/yaml-input-and-json-output/snapshot.js b/__tests__/join/yaml-input-and-json-output/snapshot.js
new file mode 100644
index 00000000..0fe3348f
--- /dev/null
+++ b/__tests__/join/yaml-input-and-json-output/snapshot.js
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E join files with different extensions test with option: {
+  name: 'json output file',
+  folder: 'yaml-input-and-json-output',
+  entrypoints: [Array],
+  output: 'openapi.json',
+  snapshot: 'snapshot.js'
+} 1`] = `
+
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Example API",
+    "description": "This is an example API.",
+    "version": "<version>"
+  },
+  "servers": [
+    {
+      "url": "https://redocly-example.com/api"
+    }
+  ],
+  "tags": [
+    {
+      "name": "foo_other",
+      "x-displayName": "other"
+    },
+    {
+      "name": "bar_other",
+      "x-displayName": "other"
+    }
+  ],
+  "paths": {
+    "/users/{userId}/orders/{orderId}": {
+      "parameters": [
+        {
+          "name": "userId",
+          "in": "path",
+          "description": "ID of the user",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        },
+        {
+          "name": "orderId",
+          "in": "path",
+          "description": "ID of the order",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        }
+      ],
+      "get": {
+        "x-private": true,
+        "summary": "Get an order by ID for a specific user",
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "404": {
+            "description": "Not found"
+          }
+        },
+        "tags": [
+          "foo_other"
+        ]
+      }
+    },
+    "/users/{userId}": {
+      "parameters": [
+        {
+          "name": "userId",
+          "in": "path",
+          "description": "ID of the user",
+          "required": true,
+          "schema": {
+            "type": "integer"
+          }
+        }
+      ],
+      "get": {
+        "summary": "Get user by ID",
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "404": {
+            "description": "Not found"
+          }
+        },
+        "tags": [
+          "bar_other"
+        ]
+      }
+    }
+  },
+  "components": {},
+  "x-tagGroups": [
+    {
+      "name": "foo",
+      "tags": [
+        "foo_other"
+      ]
+    },
+    {
+      "name": "bar",
+      "tags": [
+        "bar_other"
+      ]
+    }
+  ]
+}
+openapi.json: join processed in <test>ms
+
+
+`;
diff --git a/__tests__/split/openapi-json-file/openapi.json b/__tests__/split/openapi-json-file/openapi.json
new file mode 100644
index 00000000..ece4f7be
--- /dev/null
+++ b/__tests__/split/openapi-json-file/openapi.json
@@ -0,0 +1,165 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "version": "1.0.0",
+    "title": "Swagger Petstore",
+    "license": {
+      "name": "MIT"
+    }
+  },
+  "servers": [
+    {
+      "url": "http://petstore.swagger.io/v1"
+    }
+  ],
+  "paths": {
+    "/pets": {
+      "get": {
+        "summary": "List all pets",
+        "operationId": "listPets",
+        "tags": ["pets"],
+        "parameters": [
+          {
+            "name": "limit",
+            "in": "query",
+            "description": "How many items to return at one time (max 100)",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "A paged array of pets",
+            "headers": {
+              "x-next": {
+                "description": "A link to the next page of responses",
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Pets"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "unexpected error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                }
+              }
+            }
+          }
+        }
+      },
+      "post": {
+        "summary": "Create a pet",
+        "operationId": "createPets",
+        "tags": ["pets"],
+        "responses": {
+          "201": {
+            "description": "Null response"
+          },
+          "default": {
+            "description": "unexpected error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/pets/{petId}": {
+      "get": {
+        "summary": "Info for a specific pet",
+        "operationId": "showPetById",
+        "tags": ["pets"],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "required": true,
+            "description": "The id of the pet to retrieve",
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Expected response to a valid request",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Pet"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "unexpected error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Pet": {
+        "type": "object",
+        "required": ["id", "name"],
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int64"
+          },
+          "name": {
+            "type": "string"
+          },
+          "tag": {
+            "type": "string"
+          }
+        }
+      },
+      "Pets": {
+        "type": "array",
+        "items": {
+          "$ref": "#/components/schemas/Pet"
+        }
+      },
+      "Error": {
+        "type": "object",
+        "required": ["code", "message"],
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "message": {
+            "type": "string"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/__tests__/split/openapi-json-file/snapshot.js b/__tests__/split/openapi-json-file/snapshot.js
new file mode 100644
index 00000000..4eddec7d
--- /dev/null
+++ b/__tests__/split/openapi-json-file/snapshot.js
@@ -0,0 +1,184 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E split openapi json file 1`] = `
+
+{
+  "type": "object",
+  "required": [
+    "id",
+    "name"
+  ],
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int64"
+    },
+    "name": {
+      "type": "string"
+    },
+    "tag": {
+      "type": "string"
+    }
+  }
+}{
+  "type": "array",
+  "items": {
+    "$ref": "./Pet.json"
+  }
+}{
+  "type": "object",
+  "required": [
+    "code",
+    "message"
+  ],
+  "properties": {
+    "code": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "message": {
+      "type": "string"
+    }
+  }
+}{
+  "get": {
+    "summary": "List all pets",
+    "operationId": "listPets",
+    "tags": [
+      "pets"
+    ],
+    "parameters": [
+      {
+        "name": "limit",
+        "in": "query",
+        "description": "How many items to return at one time (max 100)",
+        "required": false,
+        "schema": {
+          "type": "integer",
+          "format": "int32"
+        }
+      }
+    ],
+    "responses": {
+      "200": {
+        "description": "A paged array of pets",
+        "headers": {
+          "x-next": {
+            "description": "A link to the next page of responses",
+            "schema": {
+              "type": "string"
+            }
+          }
+        },
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Pets"
+            }
+          }
+        }
+      },
+      "default": {
+        "description": "unexpected error",
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Error"
+            }
+          }
+        }
+      }
+    }
+  },
+  "post": {
+    "summary": "Create a pet",
+    "operationId": "createPets",
+    "tags": [
+      "pets"
+    ],
+    "responses": {
+      "201": {
+        "description": "Null response"
+      },
+      "default": {
+        "description": "unexpected error",
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Error"
+            }
+          }
+        }
+      }
+    }
+  }
+}{
+  "get": {
+    "summary": "Info for a specific pet",
+    "operationId": "showPetById",
+    "tags": [
+      "pets"
+    ],
+    "parameters": [
+      {
+        "name": "petId",
+        "in": "path",
+        "required": true,
+        "description": "The id of the pet to retrieve",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    "responses": {
+      "200": {
+        "description": "Expected response to a valid request",
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Pet"
+            }
+          }
+        }
+      },
+      "default": {
+        "description": "unexpected error",
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Error"
+            }
+          }
+        }
+      }
+    }
+  }
+}{
+  "openapi": "3.0.0",
+  "info": {
+    "version": "<version>",
+    "title": "Swagger Petstore",
+    "license": {
+      "name": "MIT"
+    }
+  },
+  "servers": [
+    {
+      "url": "http://petstore.swagger.io/v1"
+    }
+  ],
+  "paths": {
+    "/pets": {
+      "$ref": "paths/pets.json"
+    },
+    "/pets/{petId}": {
+      "$ref": "paths/pets_{petId}.json"
+    }
+  }
+}🪓 Document: ../../../__tests__/split/openapi-json-file/openapi.json is successfully split
+    and all related files are saved to the directory: output 
+
+../../../__tests__/split/openapi-json-file/openapi.json: split processed in <test>ms
+
+
+`;
diff --git a/packages/cli/src/__tests__/commands/join.test.ts b/packages/cli/src/__tests__/commands/join.test.ts
index 22a604f4..e9f995fc 100644
--- a/packages/cli/src/__tests__/commands/join.test.ts
+++ b/packages/cli/src/__tests__/commands/join.test.ts
@@ -1,11 +1,12 @@
 import { handleJoin } from '../../commands/join';
-import { exitWithError, writeYaml } from '../../utils';
+import { exitWithError, writeToFileByExtension, writeYaml } from '../../utils';
 import { yellow } from 'colorette';
 import { detectSpec } from '@redocly/openapi-core';
 import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
 import { ConfigFixture } from '../fixtures/config';
 
 jest.mock('../../utils');
+
 jest.mock('colorette');
 
 describe('handleJoin fails', () => {
@@ -80,7 +81,7 @@ describe('handleJoin fails', () => {
     );
   });
 
-  it('should call writeYaml function', async () => {
+  it('should call writeToFileByExtension function', async () => {
     (detectSpec as jest.Mock).mockReturnValue('oas3_0');
     await handleJoin(
       {
@@ -90,10 +91,14 @@ describe('handleJoin fails', () => {
       'cli-version'
     );
 
-    expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'openapi.yaml', expect.any(Boolean));
+    expect(writeToFileByExtension).toHaveBeenCalledWith(
+      expect.any(Object),
+      'openapi.yaml',
+      expect.any(Boolean)
+    );
   });
 
-  it('should call writeYaml function for OpenAPI 3.1', async () => {
+  it('should call writeToFileByExtension function for OpenAPI 3.1', async () => {
     (detectSpec as jest.Mock).mockReturnValue('oas3_1');
     await handleJoin(
       {
@@ -103,10 +108,14 @@ describe('handleJoin fails', () => {
       'cli-version'
     );
 
-    expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'openapi.yaml', expect.any(Boolean));
+    expect(writeToFileByExtension).toHaveBeenCalledWith(
+      expect.any(Object),
+      'openapi.yaml',
+      expect.any(Boolean)
+    );
   });
 
-  it('should call writeYaml function with custom output file', async () => {
+  it('should call writeToFileByExtension function with custom output file', async () => {
     (detectSpec as jest.Mock).mockReturnValue('oas3_0');
     await handleJoin(
       {
@@ -117,7 +126,28 @@ describe('handleJoin fails', () => {
       'cli-version'
     );
 
-    expect(writeYaml).toHaveBeenCalledWith(expect.any(Object), 'output.yml', expect.any(Boolean));
+    expect(writeToFileByExtension).toHaveBeenCalledWith(
+      expect.any(Object),
+      'output.yml',
+      expect.any(Boolean)
+    );
+  });
+
+  it('should call writeToFileByExtension function with json file extension', async () => {
+    (detectSpec as jest.Mock).mockReturnValue('oas3_0');
+    await handleJoin(
+      {
+        apis: ['first.json', 'second.yaml'],
+      },
+      ConfigFixture as any,
+      'cli-version'
+    );
+
+    expect(writeToFileByExtension).toHaveBeenCalledWith(
+      expect.any(Object),
+      'openapi.json',
+      expect.any(Boolean)
+    );
   });
 
   it('should call skipDecorators and skipPreprocessors', async () => {
diff --git a/packages/cli/src/__tests__/utils.test.ts b/packages/cli/src/__tests__/utils.test.ts
index 48cfde5e..1817bb52 100644
--- a/packages/cli/src/__tests__/utils.test.ts
+++ b/packages/cli/src/__tests__/utils.test.ts
@@ -12,6 +12,10 @@ import {
   HandledError,
   cleanArgs,
   cleanRawInput,
+  getAndValidateFileExtension,
+  writeYaml,
+  writeJson,
+  writeToFileByExtension,
 } from '../utils';
 import {
   ResolvedApi,
@@ -19,11 +23,13 @@ import {
   isAbsoluteUrl,
   ResolveError,
   YamlParseError,
+  stringifyYaml,
 } from '@redocly/openapi-core';
 import { blue, red, yellow } from 'colorette';
-import { existsSync, statSync } from 'fs';
+import { existsSync, statSync, writeFileSync } from 'fs';
 import * as path from 'path';
 import * as process from 'process';
+import * as utils from '../utils';
 
 jest.mock('os');
 jest.mock('colorette');
@@ -554,4 +560,42 @@ describe('cleanRawInput', () => {
       'redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response'
     );
   });
+
+  describe('validateFileExtension', () => {
+    it('should return current file extension', () => {
+      expect(getAndValidateFileExtension('test.json')).toEqual('json');
+    });
+
+    it('should return yaml and print warning if file extension does not supported', () => {
+      const stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
+      (yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
+
+      expect(getAndValidateFileExtension('test.xml')).toEqual('yaml');
+      expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
+    });
+  });
+
+  describe('writeToFileByExtension', () => {
+    beforeEach(() => {
+      jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
+      (yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
+    });
+
+    afterEach(() => {
+      jest.restoreAllMocks();
+    });
+
+    it('should call stringifyYaml function', () => {
+      writeToFileByExtension('test data', 'test.yaml');
+      expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
+      expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
+    });
+
+    it('should call JSON.stringify function', () => {
+      const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
+      writeToFileByExtension('test data', 'test.json');
+      expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
+      expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
+    });
+  });
 });
diff --git a/packages/cli/src/commands/split/__tests__/index.test.ts b/packages/cli/src/commands/split/__tests__/index.test.ts
index 70096244..beb2fe37 100644
--- a/packages/cli/src/commands/split/__tests__/index.test.ts
+++ b/packages/cli/src/commands/split/__tests__/index.test.ts
@@ -3,12 +3,13 @@ import * as path from 'path';
 import * as openapiCore from '@redocly/openapi-core';
 import { ComponentsFiles } from '../types';
 import { blue, green } from 'colorette';
+import { writeToFileByExtension } from '../../../utils';
 
 const utils = require('../../../utils');
 
 jest.mock('../../../utils', () => ({
   ...jest.requireActual('../../../utils'),
-  writeYaml: jest.fn(),
+  writeToFileByExtension: jest.fn(),
 }));
 
 jest.mock('@redocly/openapi-core', () => ({
@@ -65,7 +66,9 @@ describe('#split', () => {
       openapiDir,
       path.join(openapiDir, 'paths'),
       componentsFiles,
-      '_'
+      '_',
+      undefined,
+      'yaml'
     );
 
     expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
@@ -82,7 +85,9 @@ describe('#split', () => {
       openapiDir,
       path.join(openapiDir, 'webhooks'),
       componentsFiles,
-      'webhook_'
+      'webhook_',
+      undefined,
+      'yaml'
     );
 
     expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
@@ -99,7 +104,9 @@ describe('#split', () => {
       openapiDir,
       path.join(openapiDir, 'webhooks'),
       componentsFiles,
-      'webhook_'
+      'webhook_',
+      undefined,
+      'yaml'
     );
 
     expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
@@ -118,7 +125,9 @@ describe('#split', () => {
       openapiDir,
       path.join(openapiDir, 'paths'),
       componentsFiles,
-      '_'
+      '_',
+      undefined,
+      'yaml'
     );
 
     expect(utils.escapeLanguageName).nthCalledWith(1, 'C#');