mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-09 20:57:44 +00:00
feat: support for webhooks to the split command (#550)
Co-authored-by: Anton_Kozachuk <anton.kozachuk@cloudmobilizd.com> Co-authored-by: romanhotsiy <gotsijroman@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ function getEntrypoints(folderPath: string) {
|
||||
return Object.keys(redoclyYaml.apiDefinitions);
|
||||
}
|
||||
|
||||
function getBundleResult(params: string[], folderPath: string) {
|
||||
function getCommandOutput(params: string[], folderPath: string) {
|
||||
const result = spawnSync('ts-node', params, {
|
||||
cwd: folderPath,
|
||||
env: {
|
||||
@@ -72,6 +72,47 @@ describe('E2E', () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('split', () => {
|
||||
test('without option: outDir', () => {
|
||||
const folderPath = join(__dirname, `split/missing-outDir`);
|
||||
const args = [
|
||||
'--transpile-only',
|
||||
'../../../packages/cli/src/index.ts',
|
||||
'split',
|
||||
'../../../__tests__/split/test-split/spec.json',
|
||||
];
|
||||
const result = getCommandOutput(args, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||
});
|
||||
|
||||
test('swagger', () => {
|
||||
const folderPath = join(__dirname, `split/oas2`);
|
||||
const args = [
|
||||
'--transpile-only',
|
||||
'../../../packages/cli/src/index.ts',
|
||||
'split',
|
||||
'../../../__tests__/split/oas2/openapi.yaml',
|
||||
'--outDir=output'
|
||||
];
|
||||
const result = getCommandOutput(args, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||
});
|
||||
|
||||
test('openapi with no errors', () => {
|
||||
const folderPath = join(__dirname, `split/oas3-no-errors`);
|
||||
const file = '../../../__tests__/split/oas3-no-errors/openapi.yaml';
|
||||
const args = [
|
||||
'--transpile-only',
|
||||
'../../../packages/cli/src/index.ts',
|
||||
'split',
|
||||
file,
|
||||
'--outDir=output'
|
||||
];
|
||||
const result = getCommandOutput(args, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('join', () => {
|
||||
const folderPath = join(__dirname, 'join');
|
||||
const contents = readdirSync(folderPath);
|
||||
@@ -171,14 +212,14 @@ describe('E2E', () => {
|
||||
|
||||
test.each(['codeframe','stylish','json','checkstyle'])('bundle lint: should be formatted by format: %s', (format) => {
|
||||
const params = [...args, `--format=${format}`];
|
||||
const result = getBundleResult(params, folderPath);
|
||||
const result = getCommandOutput(params, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-format-snapshot.js`));
|
||||
});
|
||||
|
||||
test.each(['noFormatParameter','emptyFormatValue'])('bundle lint: no format parameter or empty value should be formatted as codeframe', (format) => {
|
||||
const formatArgument = format === 'emptyFormatValue' ? ['--format'] : [];
|
||||
const params = [...args, ... formatArgument];
|
||||
const result = getBundleResult(params, folderPath);
|
||||
const result = getCommandOutput(params, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-snapshot.js`));
|
||||
});
|
||||
});
|
||||
@@ -193,7 +234,7 @@ describe('E2E', () => {
|
||||
"--remove-unused-components",
|
||||
...entryPoints,
|
||||
];
|
||||
const result = getBundleResult(args, folderPath);
|
||||
const result = getCommandOutput(args, folderPath);
|
||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'remove-unused-components-snapshot.js'));
|
||||
});
|
||||
});
|
||||
|
||||
19
__tests__/split/missing-outDir/snapshot.js
Normal file
19
__tests__/split/missing-outDir/snapshot.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`E2E split without option: outDir 1`] = `
|
||||
|
||||
index.ts split [entrypoint]
|
||||
|
||||
Split definition into a multi-file structure.
|
||||
|
||||
Positionals:
|
||||
entrypoint API definition file that you want to split [string] [required]
|
||||
|
||||
Options:
|
||||
--version Show version number. [boolean]
|
||||
--help Show help. [boolean]
|
||||
--outDir Output directory where files will be saved. [string] [required]
|
||||
|
||||
Missing required argument: outDir
|
||||
|
||||
`;
|
||||
3
__tests__/split/missing-outDir/spec.json
Normal file
3
__tests__/split/missing-outDir/spec.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"openapi": "3.0.1"
|
||||
}
|
||||
45
__tests__/split/oas2/openapi.yaml
Normal file
45
__tests__/split/oas2/openapi.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
swagger: "2.0"
|
||||
info:
|
||||
version: "1.0.0"
|
||||
title: "Swagger Petstore"
|
||||
description: "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"
|
||||
termsOfService: "http://swagger.io/terms/"
|
||||
contact:
|
||||
name: "Swagger API Team"
|
||||
license:
|
||||
name: "MIT"
|
||||
host: "petstore.swagger.io"
|
||||
basePath: "/api"
|
||||
schemes:
|
||||
- "http"
|
||||
consumes:
|
||||
- "application/json"
|
||||
produces:
|
||||
- "application/json"
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
description: "Returns all pets from the system that the user has access to"
|
||||
produces:
|
||||
- "application/json"
|
||||
responses:
|
||||
"200":
|
||||
description: "A list of pets."
|
||||
schema:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/Pet"
|
||||
definitions:
|
||||
Pet:
|
||||
type: "object"
|
||||
required:
|
||||
- "id"
|
||||
- "name"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
name:
|
||||
type: "string"
|
||||
tag:
|
||||
type: "string"
|
||||
8
__tests__/split/oas2/snapshot.js
Normal file
8
__tests__/split/oas2/snapshot.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`E2E split swagger 1`] = `
|
||||
|
||||
OpenAPI 2 is not supported by this command
|
||||
|
||||
|
||||
`;
|
||||
111
__tests__/split/oas3-no-errors/openapi.yaml
Normal file
111
__tests__/split/oas3-no-errors/openapi.yaml
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
119
__tests__/split/oas3-no-errors/snapshot.js
Normal file
119
__tests__/split/oas3-no-errors/snapshot.js
Normal file
@@ -0,0 +1,119 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`E2E split openapi with no errors 1`] = `
|
||||
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
type: array
|
||||
items:
|
||||
$ref: ./Pet.yaml
|
||||
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: 1.0.0
|
||||
title: Swagger Petstore
|
||||
license:
|
||||
name: MIT
|
||||
servers:
|
||||
- url: http://petstore.swagger.io/v1
|
||||
paths:
|
||||
/pets:
|
||||
$ref: paths/pets.yaml
|
||||
/pets/{petId}:
|
||||
$ref: paths/pets@{petId}.yaml
|
||||
🪓 Document: ../../../__tests__/split/oas3-no-errors/openapi.yaml is successfully split
|
||||
and all related files are saved to the directory: output
|
||||
|
||||
../../../__tests__/split/oas3-no-errors/openapi.yaml: split processed in <test>ms
|
||||
|
||||
|
||||
`;
|
||||
@@ -44,5 +44,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-webhooks": {
|
||||
"test": {
|
||||
"post": {
|
||||
"summary": "New pet",
|
||||
"description": "Information about a new pet in the systems",
|
||||
"operationId": "newPet",
|
||||
"tags": ["pet"],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/Test" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Return a 200 status to indicate that the data was received successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Webhook Example",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"summary": "List all pets",
|
||||
"operationId": "listPets",
|
||||
"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",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pets"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"test": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"description": "Information about a new pet in the system",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Return a 200 status to indicate that the data was received successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Pet": {
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,76 @@
|
||||
import { iteratePaths } from '../index';
|
||||
import { iteratePathItems, handleSplit } from '../index';
|
||||
import * as path from 'path';
|
||||
import * as openapiCore from '@redocly/openapi-core';
|
||||
import {
|
||||
ComponentsFiles,
|
||||
} from '../types';
|
||||
import { blue, green } from 'colorette';
|
||||
|
||||
jest.mock('../../../utils', () => ({
|
||||
...jest.requireActual('../../../utils'),
|
||||
writeYaml: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@redocly/openapi-core', () => ({
|
||||
...jest.requireActual('@redocly/openapi-core'),
|
||||
isRef: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('#split', () => {
|
||||
it('should have correct paths for mac', () => {
|
||||
const pathsDir = 'test/paths';
|
||||
const openapiDir = 'test';
|
||||
const componentsFiles: ComponentsFiles = {};
|
||||
|
||||
it('should split the file and show the success message', async () => {
|
||||
const filePath = "./packages/cli/src/commands/split/__tests__/fixtures/spec.json";
|
||||
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||
|
||||
await handleSplit (
|
||||
{
|
||||
entrypoint: filePath,
|
||||
outDir: openapiDir,
|
||||
}
|
||||
);
|
||||
|
||||
expect(process.stderr.write).toBeCalledTimes(2);
|
||||
expect((process.stderr.write as jest.Mock).mock.calls[0][0]).toBe(
|
||||
`🪓 Document: ${blue(filePath!)} ${green('is successfully split')}
|
||||
and all related files are saved to the directory: ${blue(openapiDir)} \n`
|
||||
);
|
||||
expect((process.stderr.write as jest.Mock).mock.calls[1][0]).toContain(
|
||||
`${filePath}: split processed in <test>ms`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have correct path with paths', () => {
|
||||
const openapi = require("./fixtures/spec.json");
|
||||
|
||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml');
|
||||
jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml');
|
||||
iteratePaths(require("./fixtures/spec.json"), pathsDir, openapiDir);
|
||||
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles);
|
||||
|
||||
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
|
||||
expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml');
|
||||
});
|
||||
|
||||
it('should have correct paths for windows', () => {
|
||||
const pathsDir = 'test\\paths';
|
||||
const openapiDir = 'test';
|
||||
it('should have correct path with webhooks', () => {
|
||||
const openapi = require("./fixtures/webhooks.json");
|
||||
|
||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths\\test.yaml');
|
||||
jest.spyOn(path, 'relative').mockImplementation(() => 'paths\\test.yaml');
|
||||
iteratePaths(require("./fixtures/spec.json"), pathsDir, openapiDir);
|
||||
expect(openapiCore.slash).toHaveBeenCalledWith('paths\\test.yaml');
|
||||
expect(path.relative).toHaveBeenCalledWith('test', 'test\\paths/test.yaml');
|
||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
|
||||
jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
|
||||
iteratePathItems(openapi.webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||
|
||||
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||
expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
|
||||
});
|
||||
|
||||
it('should have correct path with x-webhooks', () => {
|
||||
const openapi = require("./fixtures/spec.json");
|
||||
|
||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
|
||||
jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
|
||||
iteratePathItems(openapi['x-webhooks'], openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||
|
||||
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||
expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { red, blue, yellow, green } from 'colorette';
|
||||
import * as fs from 'fs';
|
||||
import { parseYaml, slash } from '@redocly/openapi-core';
|
||||
import { parseYaml, slash, isRef } from '@redocly/openapi-core';
|
||||
import * as path from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
const isEqual = require('lodash.isequal');
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Oas2Definition,
|
||||
Oas3Schema,
|
||||
Oas3Definition,
|
||||
Oas3_1Definition,
|
||||
Oas3Components,
|
||||
Oas3ComponentName,
|
||||
ComponentsFiles,
|
||||
@@ -20,19 +21,19 @@ import {
|
||||
OPENAPI3_COMPONENT,
|
||||
COMPONENTS,
|
||||
componentsPath,
|
||||
PATHS,
|
||||
OPENAPI3_METHOD_NAMES,
|
||||
OPENAPI3_COMPONENT_NAMES
|
||||
} from './types'
|
||||
OPENAPI3_COMPONENT_NAMES,
|
||||
Referenced
|
||||
} from './types';
|
||||
|
||||
export async function handleSplit (argv: {
|
||||
entrypoint?: string;
|
||||
entrypoint: string;
|
||||
outDir: string
|
||||
}) {
|
||||
const startedAt = performance.now();
|
||||
const { entrypoint, outDir } = argv;
|
||||
validateDefinitionFileName(entrypoint!);
|
||||
const openapi = readYaml(entrypoint!) as Oas3Definition;
|
||||
const openapi = readYaml(entrypoint!) as Oas3Definition | Oas3_1Definition;
|
||||
splitDefinition(openapi, outDir);
|
||||
process.stderr.write(
|
||||
`🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')}
|
||||
@@ -41,23 +42,16 @@ export async function handleSplit (argv: {
|
||||
printExecutionTime('split', startedAt, entrypoint!);
|
||||
}
|
||||
|
||||
function splitDefinition(openapi: Oas3Definition, openapiDir: string) {
|
||||
function splitDefinition(openapi: Oas3Definition | Oas3_1Definition, openapiDir: string) {
|
||||
fs.mkdirSync(openapiDir, { recursive: true });
|
||||
const pathsDir = path.join(openapiDir, PATHS);
|
||||
fs.mkdirSync(pathsDir, { recursive: true });
|
||||
|
||||
const componentsFiles: ComponentsFiles = {};
|
||||
iteratePaths(openapi, pathsDir, openapiDir);
|
||||
iterateComponents(openapi, openapiDir, componentsFiles);
|
||||
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles);
|
||||
const webhooks = (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
|
||||
// use webhook_ prefix for code samples to prevent potential name-clashes with paths samples
|
||||
iteratePathItems(webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||
|
||||
function traverseDirectoryDeepCallback(filename: string, directory: string) {
|
||||
if (isNotYaml(filename)) return;
|
||||
const pathData = readYaml(filename);
|
||||
replace$Refs(pathData, directory, componentsFiles);
|
||||
writeYaml(pathData, filename);
|
||||
}
|
||||
|
||||
traverseDirectoryDeep(pathsDir, traverseDirectoryDeepCallback);
|
||||
replace$Refs(openapi, openapiDir, componentsFiles);
|
||||
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
|
||||
}
|
||||
@@ -82,7 +76,7 @@ function validateDefinitionFileName(fileName: string) {
|
||||
if (!fs.existsSync(fileName)) exitWithError(`File ${blue(fileName)} does not exist \n`);
|
||||
const file = loadFile(fileName);
|
||||
if ((file as Oas2Definition).swagger) exitWithError('OpenAPI 2 is not supported by this command');
|
||||
if (!(file as Oas3Definition).openapi) exitWithError('File does not conform to the OpenAPI Specification. OpenAPI version is not specified');
|
||||
if (!(file as Oas3Definition | Oas3_1Definition).openapi) exitWithError('File does not conform to the OpenAPI Specification. OpenAPI version is not specified');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,19 +94,30 @@ function langToExt(lang: string) {
|
||||
return langObj[lang];
|
||||
}
|
||||
|
||||
function traverseDirectoryDeep(directory: string, callback: any) {
|
||||
function traverseDirectoryDeep(directory: string, callback: any, componentsFiles: object) {
|
||||
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) return;
|
||||
const files = fs.readdirSync(directory);
|
||||
for (const f of files) {
|
||||
const filename = path.join(directory, f);
|
||||
if (fs.statSync(filename).isDirectory()) {
|
||||
traverseDirectoryDeep(filename, callback);
|
||||
traverseDirectoryDeep(filename, callback, componentsFiles);
|
||||
} else {
|
||||
callback(filename, directory);
|
||||
callback(filename, directory, componentsFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function traverseDirectoryDeepCallback(
|
||||
filename: string,
|
||||
directory: string,
|
||||
componentsFiles: object,
|
||||
) {
|
||||
if (isNotYaml(filename)) return;
|
||||
const pathData = readYaml(filename);
|
||||
replace$Refs(pathData, directory, componentsFiles);
|
||||
writeYaml(pathData, filename);
|
||||
}
|
||||
|
||||
function crawl(object: any, visitor: any) {
|
||||
if (!isObject(object)) return;
|
||||
for (const key of Object.keys(object)) {
|
||||
@@ -199,7 +204,7 @@ function doesFileDiffer(filename: string, componentData: any) {
|
||||
return fs.existsSync(filename) && !isEqual(readYaml(filename), componentData);
|
||||
}
|
||||
|
||||
function removeEmptyComponents(openapi: Oas3Definition, componentType: Oas3ComponentName) {
|
||||
function removeEmptyComponents(openapi: Oas3Definition | Oas3_1Definition, componentType: Oas3ComponentName) {
|
||||
if (openapi.components && isEmptyObject(openapi.components[componentType])) {
|
||||
delete openapi.components[componentType];
|
||||
}
|
||||
@@ -237,49 +242,57 @@ function gatherComponentsFiles(
|
||||
componentsFiles[componentType][componentName] = { inherits, filename };
|
||||
}
|
||||
|
||||
function iteratePaths(
|
||||
openapi: Oas3Definition,
|
||||
pathsDir: string,
|
||||
openapiDir: string
|
||||
function iteratePathItems(
|
||||
pathItems: Record<string, Referenced<Oas3PathItem>> | undefined,
|
||||
openapiDir: string,
|
||||
outDir: string,
|
||||
componentsFiles: object,
|
||||
codeSamplesPathPrefix: string = '',
|
||||
) {
|
||||
const { paths } = openapi;
|
||||
if (paths) {
|
||||
for (const oasPath of Object.keys(paths)) {
|
||||
const pathFile = path.join(pathsDir, pathToFilename(oasPath)) + '.yaml';
|
||||
const pathData: Oas3PathItem = paths[oasPath] as Oas3PathItem;
|
||||
if (!pathItems) return;
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
for (const pathName of Object.keys(pathItems)) {
|
||||
const pathFile = `${path.join(outDir, pathToFilename(pathName))}.yaml`;
|
||||
const pathData = pathItems[pathName] as Oas3PathItem;
|
||||
|
||||
if (isRef(pathData)) continue;
|
||||
|
||||
for (const method of OPENAPI3_METHOD_NAMES) {
|
||||
const methodData = pathData[method];
|
||||
const methodDataXCode = methodData?.['x-code-samples'] || methodData?.['x-codeSamples'];
|
||||
if (!methodDataXCode || !Array.isArray(methodDataXCode)) { continue; }
|
||||
if (!methodDataXCode || !Array.isArray(methodDataXCode)) {
|
||||
continue;
|
||||
}
|
||||
for (const sample of methodDataXCode) {
|
||||
if (sample.source && (sample.source as any).$ref) continue;
|
||||
const sampleFileName = path.join(
|
||||
openapiDir,
|
||||
'code_samples',
|
||||
sample.lang,
|
||||
pathToFilename(oasPath),
|
||||
method + langToExt(sample.lang)
|
||||
codeSamplesPathPrefix + pathToFilename(pathName),
|
||||
method + langToExt(sample.lang),
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.dirname(sampleFileName), { recursive: true });
|
||||
fs.writeFileSync(sampleFileName, sample.source);
|
||||
// @ts-ignore
|
||||
sample.source = {
|
||||
$ref: slash(path.relative(pathsDir, sampleFileName))
|
||||
$ref: slash(path.relative(outDir, sampleFileName))
|
||||
};
|
||||
}
|
||||
}
|
||||
writeYaml(pathData, pathFile);
|
||||
paths[oasPath] = {
|
||||
pathItems[pathName] = {
|
||||
$ref: slash(path.relative(openapiDir, pathFile))
|
||||
};
|
||||
}
|
||||
|
||||
traverseDirectoryDeep(outDir, traverseDirectoryDeepCallback, componentsFiles);
|
||||
}
|
||||
}
|
||||
|
||||
function iterateComponents(
|
||||
openapi: Oas3Definition,
|
||||
openapi: Oas3Definition | Oas3_1Definition,
|
||||
openapiDir: string,
|
||||
componentsFiles: ComponentsFiles
|
||||
) {
|
||||
@@ -331,6 +344,4 @@ function iterateComponents(
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
iteratePaths,
|
||||
}
|
||||
export { iteratePathItems };
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import {
|
||||
Oas3Schema,
|
||||
Oas3_1Schema,
|
||||
Oas3Definition,
|
||||
Oas3_1Definition,
|
||||
Oas3Components,
|
||||
Oas3PathItem,
|
||||
Oas3Paths,
|
||||
Oas3ComponentName,
|
||||
Oas2Definition
|
||||
Oas3_1Webhooks,
|
||||
Oas2Definition,
|
||||
Referenced
|
||||
} from "@redocly/openapi-core";
|
||||
export { Oas3Definition, Oas2Definition, Oas3Components, Oas3Paths, Oas3PathItem, Oas3ComponentName, Oas3Schema }
|
||||
export type Definition = Oas3Definition | Oas2Definition;
|
||||
export { Oas3_1Definition, Oas3Definition, Oas2Definition, Oas3Components, Oas3Paths, Oas3PathItem, Oas3ComponentName, Oas3_1Schema, Oas3Schema, Oas3_1Webhooks, Referenced }
|
||||
export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition;
|
||||
export interface ComponentsFiles {
|
||||
[schemas: string]: any;
|
||||
}
|
||||
@@ -18,6 +22,8 @@ export interface refObj {
|
||||
|
||||
export const COMPONENTS = 'components';
|
||||
export const PATHS = 'paths';
|
||||
export const WEBHOOKS = 'webhooks';
|
||||
export const xWEBHOOKS = 'x-webhooks';
|
||||
export const componentsPath = `#/${COMPONENTS}/`;
|
||||
|
||||
enum OPENAPI3_METHOD {
|
||||
|
||||
@@ -35,13 +35,21 @@ yargs
|
||||
'split [entrypoint]',
|
||||
'Split definition into a multi-file structure.',
|
||||
(yargs) =>
|
||||
yargs.positional('entrypoint', { type: 'string' }).option({
|
||||
yargs
|
||||
.positional('entrypoint', {
|
||||
description: 'API definition file that you want to split',
|
||||
type: 'string'
|
||||
})
|
||||
.option({
|
||||
outDir: {
|
||||
description: 'Output directory where files will be saved.',
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
}),
|
||||
})
|
||||
.demandOption(
|
||||
'entrypoint'
|
||||
),
|
||||
handleSplit,
|
||||
)
|
||||
.command(
|
||||
@@ -207,7 +215,7 @@ yargs
|
||||
description: 'Remove unused components.',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
}),
|
||||
(argv) => {
|
||||
handleBundle(argv, version);
|
||||
|
||||
@@ -65,7 +65,7 @@ export function pathToFilename(path: string) {
|
||||
return path
|
||||
.replace(/~1/g, '/')
|
||||
.replace(/~0/g, '~')
|
||||
.substring(1)
|
||||
.replace(/^\//, '')
|
||||
.replace(/\//g, '@');
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,16 @@ export { Oas2Types } from './types/oas2';
|
||||
export { ConfigTypes } from './types/redocly-yaml';
|
||||
export {
|
||||
Oas3Definition,
|
||||
Oas3_1Definition,
|
||||
Oas3Components,
|
||||
Oas3PathItem,
|
||||
Oas3Paths,
|
||||
Oas3ComponentName,
|
||||
Oas3Schema,
|
||||
Oas3_1Schema,
|
||||
Oas3Tag,
|
||||
Oas3_1Webhooks,
|
||||
Referenced
|
||||
} from './typings/openapi';
|
||||
export { Oas2Definition } from './typings/swagger';
|
||||
export { StatsAccumulator, StatsName } from './typings/common';
|
||||
@@ -30,7 +34,7 @@ export {
|
||||
makeDocumentFromString,
|
||||
} from './resolve';
|
||||
export { parseYaml, stringifyYaml } from './js-yaml';
|
||||
export { unescapePointer } from './ref-utils';
|
||||
export { unescapePointer, isRef } from './ref-utils';
|
||||
export { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
|
||||
export { normalizeVisitors } from './visitors';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Oas3Definition {
|
||||
security?: Oas3SecurityRequirement[];
|
||||
tags?: Oas3Tag[];
|
||||
externalDocs?: Oas3ExternalDocs;
|
||||
'x-webhooks'?: Oas3_1Webhooks;
|
||||
}
|
||||
|
||||
export interface Oas3Info {
|
||||
@@ -154,6 +155,14 @@ export interface Oas3_1Schema extends Oas3Schema {
|
||||
examples?: any[];
|
||||
}
|
||||
|
||||
export interface Oas3_1Definition extends Oas3Definition {
|
||||
webhooks?: Oas3_1Webhooks;
|
||||
}
|
||||
|
||||
export interface Oas3_1Webhooks {
|
||||
[webhook: string]: Referenced<Oas3PathItem>;
|
||||
}
|
||||
|
||||
export interface Oas3Discriminator {
|
||||
propertyName: string;
|
||||
mapping?: { [name: string]: string };
|
||||
|
||||
Reference in New Issue
Block a user