mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: extend split and join commands to produce JSON output (#1305)
This commit is contained in:
5
.changeset/plenty-mugs-suffer.md
Normal file
5
.changeset/plenty-mugs-suffer.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@redocly/cli': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add JSON output support to the `split` and `join` commands.
|
||||||
@@ -161,6 +161,19 @@ describe('E2E', () => {
|
|||||||
const result = getCommandOutput(args, folderPath);
|
const result = getCommandOutput(args, folderPath);
|
||||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
(<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', () => {
|
describe('join', () => {
|
||||||
@@ -214,6 +227,46 @@ describe('E2E', () => {
|
|||||||
const result = getCommandOutput(args, testPath);
|
const result = getCommandOutput(args, testPath);
|
||||||
(<any>expect(result)).toMatchSpecificSnapshot(join(testPath, 'snapshot.js'));
|
(<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', () => {
|
describe('bundle', () => {
|
||||||
|
|||||||
23
__tests__/join/json-and-yaml-input/bar.yaml
Normal file
23
__tests__/join/json-and-yaml-input/bar.yaml
Normal file
@@ -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
|
||||||
49
__tests__/join/json-and-yaml-input/foo.json
Normal file
49
__tests__/join/json-and-yaml-input/foo.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
__tests__/join/json-and-yaml-input/json-output.snapshot.js
Normal file
117
__tests__/join/json-and-yaml-input/json-output.snapshot.js
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
76
__tests__/join/json-and-yaml-input/yaml-output.snapshot.js
Normal file
76
__tests__/join/json-and-yaml-input/yaml-output.snapshot.js
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
84
__tests__/join/multi-references-to-one-file/openapi.yaml
Normal file
84
__tests__/join/multi-references-to-one-file/openapi.yaml
Normal file
@@ -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
|
||||||
23
__tests__/join/yaml-input-and-json-output/bar.yaml
Normal file
23
__tests__/join/yaml-input-and-json-output/bar.yaml
Normal file
@@ -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
|
||||||
30
__tests__/join/yaml-input-and-json-output/foo.yaml
Normal file
30
__tests__/join/yaml-input-and-json-output/foo.yaml
Normal file
@@ -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
|
||||||
118
__tests__/join/yaml-input-and-json-output/snapshot.js
Normal file
118
__tests__/join/yaml-input-and-json-output/snapshot.js
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
165
__tests__/split/openapi-json-file/openapi.json
Normal file
165
__tests__/split/openapi-json-file/openapi.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
__tests__/split/openapi-json-file/snapshot.js
Normal file
184
__tests__/split/openapi-json-file/snapshot.js
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -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.
|
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
|
### Usage
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ redocly join --version
|
|||||||
| --help | boolean | Show help. |
|
| --help | boolean | Show help. |
|
||||||
| --lint | boolean | Lint API description files. |
|
| --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`. |
|
| --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-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-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. |
|
| --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
|
### 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
|
```bash Command
|
||||||
redocly join --output=openapi-custom.yaml
|
redocly join --output=openapi-custom.yaml
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const doesYamlFileExist = jest.fn();
|
|||||||
export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
|
export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
|
||||||
export const detectSpec = jest.fn();
|
export const detectSpec = jest.fn();
|
||||||
export const isAbsoluteUrl = jest.fn();
|
export const isAbsoluteUrl = jest.fn();
|
||||||
|
export const stringifyYaml = jest.fn((data) => data);
|
||||||
|
|
||||||
export class BaseResolver {
|
export class BaseResolver {
|
||||||
cache = new Map<string, Promise<Document | ResolveError>>();
|
cache = new Map<string, Promise<Document | ResolveError>>();
|
||||||
|
|||||||
@@ -17,3 +17,5 @@ export const writeYaml = jest.fn();
|
|||||||
export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
|
export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
|
||||||
export const checkIfRulesetExist = jest.fn();
|
export const checkIfRulesetExist = jest.fn();
|
||||||
export const sortTopLevelKeysForOas = jest.fn((document) => document);
|
export const sortTopLevelKeysForOas = jest.fn((document) => document);
|
||||||
|
export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
|
||||||
|
export const writeToFileByExtension = jest.fn();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { handleJoin } from '../../commands/join';
|
import { handleJoin } from '../../commands/join';
|
||||||
import { exitWithError, writeYaml } from '../../utils';
|
import { exitWithError, writeToFileByExtension, writeYaml } from '../../utils';
|
||||||
import { yellow } from 'colorette';
|
import { yellow } from 'colorette';
|
||||||
import { detectSpec } from '@redocly/openapi-core';
|
import { detectSpec } from '@redocly/openapi-core';
|
||||||
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
||||||
import { ConfigFixture } from '../fixtures/config';
|
import { ConfigFixture } from '../fixtures/config';
|
||||||
|
|
||||||
jest.mock('../../utils');
|
jest.mock('../../utils');
|
||||||
|
|
||||||
jest.mock('colorette');
|
jest.mock('colorette');
|
||||||
|
|
||||||
describe('handleJoin fails', () => {
|
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');
|
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
||||||
await handleJoin(
|
await handleJoin(
|
||||||
{
|
{
|
||||||
@@ -90,10 +91,14 @@ describe('handleJoin fails', () => {
|
|||||||
'cli-version'
|
'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');
|
(detectSpec as jest.Mock).mockReturnValue('oas3_1');
|
||||||
await handleJoin(
|
await handleJoin(
|
||||||
{
|
{
|
||||||
@@ -103,10 +108,14 @@ describe('handleJoin fails', () => {
|
|||||||
'cli-version'
|
'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');
|
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
||||||
await handleJoin(
|
await handleJoin(
|
||||||
{
|
{
|
||||||
@@ -117,7 +126,28 @@ describe('handleJoin fails', () => {
|
|||||||
'cli-version'
|
'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 () => {
|
it('should call skipDecorators and skipPreprocessors', async () => {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import {
|
|||||||
HandledError,
|
HandledError,
|
||||||
cleanArgs,
|
cleanArgs,
|
||||||
cleanRawInput,
|
cleanRawInput,
|
||||||
|
getAndValidateFileExtension,
|
||||||
|
writeYaml,
|
||||||
|
writeJson,
|
||||||
|
writeToFileByExtension,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
ResolvedApi,
|
ResolvedApi,
|
||||||
@@ -19,11 +23,13 @@ import {
|
|||||||
isAbsoluteUrl,
|
isAbsoluteUrl,
|
||||||
ResolveError,
|
ResolveError,
|
||||||
YamlParseError,
|
YamlParseError,
|
||||||
|
stringifyYaml,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
import { blue, red, yellow } from 'colorette';
|
import { blue, red, yellow } from 'colorette';
|
||||||
import { existsSync, statSync } from 'fs';
|
import { existsSync, statSync, writeFileSync } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as process from 'process';
|
import * as process from 'process';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
|
||||||
jest.mock('os');
|
jest.mock('os');
|
||||||
jest.mock('colorette');
|
jest.mock('colorette');
|
||||||
@@ -554,4 +560,42 @@ describe('cleanRawInput', () => {
|
|||||||
'redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response'
|
'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`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ import {
|
|||||||
printExecutionTime,
|
printExecutionTime,
|
||||||
handleError,
|
handleError,
|
||||||
printLintTotals,
|
printLintTotals,
|
||||||
writeYaml,
|
|
||||||
exitWithError,
|
exitWithError,
|
||||||
sortTopLevelKeysForOas,
|
sortTopLevelKeysForOas,
|
||||||
|
getAndValidateFileExtension,
|
||||||
|
writeToFileByExtension,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { isObject, isString, keysOf } from '../js-utils';
|
import { isObject, isString, keysOf } from '../js-utils';
|
||||||
import {
|
import {
|
||||||
@@ -70,16 +71,19 @@ export type JoinOptions = {
|
|||||||
|
|
||||||
export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
|
export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
|
|
||||||
if (argv.apis.length < 2) {
|
if (argv.apis.length < 2) {
|
||||||
return exitWithError(`At least 2 apis should be provided. \n\n`);
|
return exitWithError(`At least 2 apis should be provided. \n\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
|
'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
|
||||||
'prefix-tags-with-filename': prefixTagsWithFilename,
|
'prefix-tags-with-filename': prefixTagsWithFilename,
|
||||||
'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
|
'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
|
||||||
'without-x-tag-groups': withoutXTagGroups,
|
'without-x-tag-groups': withoutXTagGroups,
|
||||||
output: specFilename = 'openapi.yaml',
|
output: specFilename = `openapi.${fileExtension}`,
|
||||||
} = argv;
|
} = argv;
|
||||||
|
|
||||||
const usedTagsOptions = [
|
const usedTagsOptions = [
|
||||||
@@ -229,7 +233,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
|
|||||||
return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
|
return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeYaml(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
|
writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
|
||||||
|
|
||||||
printExecutionTime('join', startedAt, specFilename);
|
printExecutionTime('join', startedAt, specFilename);
|
||||||
|
|
||||||
function populateTags({
|
function populateTags({
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import * as path from 'path';
|
|||||||
import * as openapiCore from '@redocly/openapi-core';
|
import * as openapiCore from '@redocly/openapi-core';
|
||||||
import { ComponentsFiles } from '../types';
|
import { ComponentsFiles } from '../types';
|
||||||
import { blue, green } from 'colorette';
|
import { blue, green } from 'colorette';
|
||||||
|
import { writeToFileByExtension } from '../../../utils';
|
||||||
|
|
||||||
const utils = require('../../../utils');
|
const utils = require('../../../utils');
|
||||||
|
|
||||||
jest.mock('../../../utils', () => ({
|
jest.mock('../../../utils', () => ({
|
||||||
...jest.requireActual('../../../utils'),
|
...jest.requireActual('../../../utils'),
|
||||||
writeYaml: jest.fn(),
|
writeToFileByExtension: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@redocly/openapi-core', () => ({
|
jest.mock('@redocly/openapi-core', () => ({
|
||||||
@@ -65,7 +66,9 @@ describe('#split', () => {
|
|||||||
openapiDir,
|
openapiDir,
|
||||||
path.join(openapiDir, 'paths'),
|
path.join(openapiDir, 'paths'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
'_'
|
'_',
|
||||||
|
undefined,
|
||||||
|
'yaml'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
|
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
|
||||||
@@ -82,7 +85,9 @@ describe('#split', () => {
|
|||||||
openapiDir,
|
openapiDir,
|
||||||
path.join(openapiDir, 'webhooks'),
|
path.join(openapiDir, 'webhooks'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
'webhook_'
|
'webhook_',
|
||||||
|
undefined,
|
||||||
|
'yaml'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||||
@@ -99,7 +104,9 @@ describe('#split', () => {
|
|||||||
openapiDir,
|
openapiDir,
|
||||||
path.join(openapiDir, 'webhooks'),
|
path.join(openapiDir, 'webhooks'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
'webhook_'
|
'webhook_',
|
||||||
|
undefined,
|
||||||
|
'yaml'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||||
@@ -118,7 +125,9 @@ describe('#split', () => {
|
|||||||
openapiDir,
|
openapiDir,
|
||||||
path.join(openapiDir, 'paths'),
|
path.join(openapiDir, 'paths'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
'_'
|
'_',
|
||||||
|
undefined,
|
||||||
|
'yaml'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(utils.escapeLanguageName).nthCalledWith(1, 'C#');
|
expect(utils.escapeLanguageName).nthCalledWith(1, 'C#');
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import {
|
|||||||
printExecutionTime,
|
printExecutionTime,
|
||||||
pathToFilename,
|
pathToFilename,
|
||||||
readYaml,
|
readYaml,
|
||||||
writeYaml,
|
|
||||||
exitWithError,
|
exitWithError,
|
||||||
escapeLanguageName,
|
escapeLanguageName,
|
||||||
langToExt,
|
langToExt,
|
||||||
|
writeToFileByExtension,
|
||||||
|
getAndValidateFileExtension,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { isString, isObject, isEmptyObject } from '../../js-utils';
|
import { isString, isObject, isEmptyObject } from '../../js-utils';
|
||||||
import {
|
import {
|
||||||
@@ -46,8 +47,9 @@ export async function handleSplit(argv: SplitOptions) {
|
|||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
const { api, outDir, separator } = argv;
|
const { api, outDir, separator } = argv;
|
||||||
validateDefinitionFileName(api!);
|
validateDefinitionFileName(api!);
|
||||||
|
const ext = getAndValidateFileExtension(api);
|
||||||
const openapi = readYaml(api!) as Oas3Definition | Oas3_1Definition;
|
const openapi = readYaml(api!) as Oas3Definition | Oas3_1Definition;
|
||||||
splitDefinition(openapi, outDir, separator);
|
splitDefinition(openapi, outDir, separator, ext);
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
`🪓 Document: ${blue(api!)} ${green('is successfully split')}
|
`🪓 Document: ${blue(api!)} ${green('is successfully split')}
|
||||||
and all related files are saved to the directory: ${blue(outDir)} \n`
|
and all related files are saved to the directory: ${blue(outDir)} \n`
|
||||||
@@ -58,18 +60,21 @@ export async function handleSplit(argv: SplitOptions) {
|
|||||||
function splitDefinition(
|
function splitDefinition(
|
||||||
openapi: Oas3Definition | Oas3_1Definition,
|
openapi: Oas3Definition | Oas3_1Definition,
|
||||||
openapiDir: string,
|
openapiDir: string,
|
||||||
pathSeparator: string
|
pathSeparator: string,
|
||||||
|
ext: string
|
||||||
) {
|
) {
|
||||||
fs.mkdirSync(openapiDir, { recursive: true });
|
fs.mkdirSync(openapiDir, { recursive: true });
|
||||||
|
|
||||||
const componentsFiles: ComponentsFiles = {};
|
const componentsFiles: ComponentsFiles = {};
|
||||||
iterateComponents(openapi, openapiDir, componentsFiles);
|
iterateComponents(openapi, openapiDir, componentsFiles, ext);
|
||||||
iteratePathItems(
|
iteratePathItems(
|
||||||
openapi.paths,
|
openapi.paths,
|
||||||
openapiDir,
|
openapiDir,
|
||||||
path.join(openapiDir, 'paths'),
|
path.join(openapiDir, 'paths'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
pathSeparator
|
pathSeparator,
|
||||||
|
undefined,
|
||||||
|
ext
|
||||||
);
|
);
|
||||||
const webhooks =
|
const webhooks =
|
||||||
(openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
|
(openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
|
||||||
@@ -80,11 +85,12 @@ function splitDefinition(
|
|||||||
path.join(openapiDir, 'webhooks'),
|
path.join(openapiDir, 'webhooks'),
|
||||||
componentsFiles,
|
componentsFiles,
|
||||||
pathSeparator,
|
pathSeparator,
|
||||||
'webhook_'
|
'webhook_',
|
||||||
|
ext
|
||||||
);
|
);
|
||||||
|
|
||||||
replace$Refs(openapi, openapiDir, componentsFiles);
|
replace$Refs(openapi, openapiDir, componentsFiles);
|
||||||
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
|
writeToFileByExtension(openapi, path.join(openapiDir, `openapi.${ext}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStartsWithComponents(node: string) {
|
function isStartsWithComponents(node: string) {
|
||||||
@@ -135,7 +141,7 @@ function traverseDirectoryDeepCallback(
|
|||||||
if (isNotYaml(filename)) return;
|
if (isNotYaml(filename)) return;
|
||||||
const pathData = readYaml(filename);
|
const pathData = readYaml(filename);
|
||||||
replace$Refs(pathData, directory, componentsFiles);
|
replace$Refs(pathData, directory, componentsFiles);
|
||||||
writeYaml(pathData, filename);
|
writeToFileByExtension(pathData, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
function crawl(object: any, visitor: any) {
|
function crawl(object: any, visitor: any) {
|
||||||
@@ -251,8 +257,8 @@ function extractFileNameFromPath(filename: string) {
|
|||||||
return path.basename(filename, path.extname(filename));
|
return path.basename(filename, path.extname(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileNamePath(componentDirPath: string, componentName: string) {
|
function getFileNamePath(componentDirPath: string, componentName: string, ext: string) {
|
||||||
return path.join(componentDirPath, componentName) + '.yaml';
|
return path.join(componentDirPath, componentName) + `.${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gatherComponentsFiles(
|
function gatherComponentsFiles(
|
||||||
@@ -278,13 +284,14 @@ function iteratePathItems(
|
|||||||
outDir: string,
|
outDir: string,
|
||||||
componentsFiles: object,
|
componentsFiles: object,
|
||||||
pathSeparator: string,
|
pathSeparator: string,
|
||||||
codeSamplesPathPrefix: string = ''
|
codeSamplesPathPrefix: string = '',
|
||||||
|
ext: string
|
||||||
) {
|
) {
|
||||||
if (!pathItems) return;
|
if (!pathItems) return;
|
||||||
fs.mkdirSync(outDir, { recursive: true });
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
|
||||||
for (const pathName of Object.keys(pathItems)) {
|
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;
|
const pathData = pathItems[pathName] as Oas3PathItem;
|
||||||
|
|
||||||
if (isRef(pathData)) continue;
|
if (isRef(pathData)) continue;
|
||||||
@@ -314,7 +321,7 @@ function iteratePathItems(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeYaml(pathData, pathFile);
|
writeToFileByExtension(pathData, pathFile);
|
||||||
pathItems[pathName] = {
|
pathItems[pathName] = {
|
||||||
$ref: slash(path.relative(openapiDir, pathFile)),
|
$ref: slash(path.relative(openapiDir, pathFile)),
|
||||||
};
|
};
|
||||||
@@ -326,7 +333,8 @@ function iteratePathItems(
|
|||||||
function iterateComponents(
|
function iterateComponents(
|
||||||
openapi: Oas3Definition | Oas3_1Definition,
|
openapi: Oas3Definition | Oas3_1Definition,
|
||||||
openapiDir: string,
|
openapiDir: string,
|
||||||
componentsFiles: ComponentsFiles
|
componentsFiles: ComponentsFiles,
|
||||||
|
ext: string
|
||||||
) {
|
) {
|
||||||
const { components } = openapi;
|
const { components } = openapi;
|
||||||
if (components) {
|
if (components) {
|
||||||
@@ -340,7 +348,7 @@ function iterateComponents(
|
|||||||
function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
|
function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
|
||||||
const componentDirPath = path.join(componentsDir, componentType);
|
const componentDirPath = path.join(componentsDir, componentType);
|
||||||
for (const componentName of Object.keys(components?.[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);
|
gatherComponentsFiles(components!, componentsFiles, componentType, componentName, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,7 +358,7 @@ function iterateComponents(
|
|||||||
const componentDirPath = path.join(componentsDir, componentType);
|
const componentDirPath = path.join(componentsDir, componentType);
|
||||||
createComponentDir(componentDirPath, componentType);
|
createComponentDir(componentDirPath, componentType);
|
||||||
for (const componentName of Object.keys(components?.[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];
|
const componentData = components?.[componentType]?.[componentName];
|
||||||
replace$Refs(componentData, path.dirname(filename), componentsFiles);
|
replace$Refs(componentData, path.dirname(filename), componentsFiles);
|
||||||
implicitlyReferenceDiscriminator(
|
implicitlyReferenceDiscriminator(
|
||||||
@@ -369,7 +377,7 @@ function iterateComponents(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
writeYaml(componentData, filename);
|
writeToFileByExtension(componentData, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotSecurityComponentType(componentType)) {
|
if (isNotSecurityComponentType(componentType)) {
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ yargs
|
|||||||
describe: 'Output file',
|
describe: 'Output file',
|
||||||
alias: 'o',
|
alias: 'o',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'openapi.yaml',
|
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
description: 'Path to the config file.',
|
description: 'Path to the config file.',
|
||||||
|
|||||||
@@ -25,7 +25,14 @@ import {
|
|||||||
RedoclyClient,
|
RedoclyClient,
|
||||||
} from '@redocly/openapi-core';
|
} from '@redocly/openapi-core';
|
||||||
import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
|
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 { isEmptyObject } from '@redocly/openapi-core/lib/utils';
|
||||||
import { Arguments } from 'yargs';
|
import { Arguments } from 'yargs';
|
||||||
import { version } from './update-version-notifier';
|
import { version } from './update-version-notifier';
|
||||||
@@ -209,6 +216,17 @@ export function readYaml(filename: string) {
|
|||||||
return parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
|
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) {
|
export function writeYaml(data: any, filename: string, noRefs = false) {
|
||||||
const content = stringifyYaml(data, { noRefs });
|
const content = stringifyYaml(data, { noRefs });
|
||||||
|
|
||||||
@@ -220,6 +238,27 @@ export function writeYaml(data: any, filename: string, noRefs = false) {
|
|||||||
fs.writeFileSync(filename, content);
|
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) {
|
export function pluralize(label: string, num: number) {
|
||||||
if (label.endsWith('is')) {
|
if (label.endsWith('is')) {
|
||||||
[label] = label.split(' ');
|
[label] = label.split(' ');
|
||||||
|
|||||||
Reference in New Issue
Block a user