mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-02 19:30:19 +03:00
Merge branch 'main' into feat/add-deprecation-label-to-redoc-cli
This commit is contained in:
commit
fa7454f73b
7
.github/CONTRIBUTING.md
vendored
7
.github/CONTRIBUTING.md
vendored
|
@ -30,6 +30,13 @@ After cloning the repo, run:
|
|||
$ npm install # or npm
|
||||
```
|
||||
|
||||
To run the dev server, you will also need to install the cli's dependencies:
|
||||
|
||||
```bash
|
||||
$ cd cli
|
||||
$ npm install # or npm
|
||||
```
|
||||
|
||||
### Commonly used NPM scripts
|
||||
|
||||
``` bash
|
||||
|
|
18
README.md
18
README.md
|
@ -72,16 +72,16 @@ Checkout the following feature comparison of Redocly's premium products versus R
|
|||
|
||||
Refer to the Redocly's documentation for more information on these products:
|
||||
|
||||
- [Portals](https://redoc.ly/docs/developer-portal/introduction/)
|
||||
- [Reference](https://redoc.ly/docs/api-reference-docs/getting-started/)
|
||||
- [Redoc](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||
- [Portals](https://redocly.com/docs/developer-portal/introduction/)
|
||||
- [Reference](https://redocly.com/docs/api-reference-docs/getting-started/)
|
||||
- [Redoc](https://redocly.com/docs/redoc/quickstart/intro/)
|
||||
|
||||
## Features
|
||||
- Responsive three-panel design with menu/scrolling synchronization
|
||||
- [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||
- [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
|
||||
- [Multiple deployment options](https://redocly.com/docs/redoc/quickstart/intro/)
|
||||
- [Server-side rendering (SSR) ready](https://redocly.com/docs/redoc/quickstart/cli/#redoc-cli-commands)
|
||||
- Ability to integrate your API introduction into the side menu
|
||||
- [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
|
||||
- [Simple integration with `create-react-app`](https://redocly.com/docs/redoc/quickstart/react/)
|
||||
|
||||
[Example repo](https://github.com/APIs-guru/create-react-app-redoc)
|
||||
- [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redocly.com/docs/cli/commands/build-docs/)
|
||||
|
@ -89,9 +89,9 @@ Refer to the Redocly's documentation for more information on these products:
|
|||

|
||||
|
||||
## Customization options
|
||||
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||
- High-level grouping in side-menu with the [`x-tagGroups`](https://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
|
||||
- Branding/customizations using the [`theme` option](https://redoc.ly/docs/api-reference-docs/configuration/theming/)
|
||||
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redocly.com/#services)
|
||||
- High-level grouping in side-menu with the [`x-tagGroups`](https://redocly.com/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
|
||||
- Branding/customizations using the [`theme` option](https://redocly.com/docs/api-reference-docs/configuration/theming/)
|
||||
|
||||
## Support
|
||||
- OpenAPI v3.0 support
|
||||
|
|
41
cli/npm-shrinkwrap.json
generated
41
cli/npm-shrinkwrap.json
generated
|
@ -12,7 +12,6 @@
|
|||
"boxen": "5.1.2",
|
||||
"chokidar": "^3.5.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"isarray": "^2.0.5",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mobx": "^6.3.2",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
|
@ -924,8 +923,7 @@
|
|||
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
"isarray": "^1.0.0"
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
|
@ -939,11 +937,6 @@
|
|||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
|
||||
},
|
||||
"node_modules/buffer/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
|
@ -1630,11 +1623,6 @@
|
|||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
|
@ -2328,18 +2316,12 @@
|
|||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
|
@ -3936,15 +3918,7 @@
|
|||
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
"isarray": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
}
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
|
@ -4516,11 +4490,6 @@
|
|||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
|
@ -5061,18 +5030,12 @@
|
|||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
"boxen": "5.1.2",
|
||||
"chokidar": "^3.5.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"isarray": "^2.0.5",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mobx": "^6.3.2",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
|
|
|
@ -86,7 +86,7 @@ class DemoApp extends React.Component<
|
|||
let proxiedUrl = specUrl;
|
||||
if (specUrl !== DEFAULT_SPEC) {
|
||||
proxiedUrl = cors
|
||||
? '\\\\cors.redoc.ly/' + new URL(specUrl, window.location.href).href
|
||||
? 'https://cors.redoc.ly/' + new URL(specUrl, window.location.href).href
|
||||
: specUrl;
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -88,6 +88,8 @@ x-tagGroups:
|
|||
tags:
|
||||
- pet_model
|
||||
- store_model
|
||||
security:
|
||||
- {}
|
||||
paths:
|
||||
/pet:
|
||||
parameters:
|
||||
|
|
|
@ -83,6 +83,8 @@ x-tagGroups:
|
|||
tags:
|
||||
- pet_model
|
||||
- store_model
|
||||
security:
|
||||
- {}
|
||||
paths:
|
||||
/pet:
|
||||
parameters:
|
||||
|
|
695
package-lock.json
generated
695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@ import { StyledComponent } from 'styled-components';
|
|||
import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/Dropdown';
|
||||
|
||||
export interface DropdownOrLabelProps extends DropdownProps {
|
||||
Label?: StyledComponent<any, any, GenericObject, never>;
|
||||
Label?: StyledComponent<any, any, Record<string, any>, never>;
|
||||
Dropdown?: StyledComponent<
|
||||
React.NamedExoticComponent<DropdownProps>,
|
||||
any,
|
||||
|
|
|
@ -73,7 +73,7 @@ export class Field extends React.Component<FieldProps> {
|
|||
<button
|
||||
onClick={this.toggle}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
aria-label="expand properties"
|
||||
aria-label={`expand ${name}`}
|
||||
>
|
||||
<span className="property-name">{name}</span>
|
||||
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
TypePrefix,
|
||||
TypeTitle,
|
||||
} from '../../common-elements/fields';
|
||||
import { getSerializedValue } from '../../utils';
|
||||
import { getSerializedValue, isObject } from '../../utils';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { EnumValues } from './EnumValues';
|
||||
|
@ -52,6 +52,10 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
|
|||
return null;
|
||||
}, [field, showExamples]);
|
||||
|
||||
const defaultValue = isObject(schema.default)
|
||||
? getSerializedValue(field, schema.default).replace(`${field.name}=`, '')
|
||||
: schema.default;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
|
@ -92,7 +96,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
|
|||
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||
</div>
|
||||
)}
|
||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} />
|
||||
{!renderDiscriminatorSwitch && (
|
||||
<EnumValues isArrayType={isArrayType} values={schema.enum} />
|
||||
)}{' '}
|
||||
|
|
|
@ -34,7 +34,12 @@ export const RedocStandalone = function (props: RedocStandaloneProps) {
|
|||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
|
||||
<StoreBuilder
|
||||
spec={spec ? { ...spec } : undefined}
|
||||
specUrl={specUrl}
|
||||
options={options}
|
||||
onLoaded={onLoaded}
|
||||
>
|
||||
{({ loading, store }) =>
|
||||
!loading ? (
|
||||
<Redoc store={store!} />
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface ObjectDescriptionProps {
|
|||
exampleRef?: string;
|
||||
showReadOnly?: boolean;
|
||||
showWriteOnly?: boolean;
|
||||
showExample?: boolean;
|
||||
parser: OpenAPIParser;
|
||||
options: RedocNormalizedOptions;
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
|
|||
}
|
||||
|
||||
render() {
|
||||
const { showReadOnly = true, showWriteOnly = false } = this.props;
|
||||
const { showReadOnly = true, showWriteOnly = false, showExample = true } = this.props;
|
||||
return (
|
||||
<Section>
|
||||
<Row>
|
||||
|
@ -64,11 +65,16 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
|
|||
schema={this.mediaModel.schema}
|
||||
/>
|
||||
</MiddlePanel>
|
||||
<DarkRightPanel>
|
||||
<MediaSamplesWrap>
|
||||
<MediaTypeSamples renderDropdown={this.renderDropdown} mediaType={this.mediaModel} />
|
||||
</MediaSamplesWrap>
|
||||
</DarkRightPanel>
|
||||
{showExample && (
|
||||
<DarkRightPanel>
|
||||
<MediaSamplesWrap>
|
||||
<MediaTypeSamples
|
||||
renderDropdown={this.renderDropdown}
|
||||
mediaType={this.mediaModel}
|
||||
/>
|
||||
</MediaSamplesWrap>
|
||||
</DarkRightPanel>
|
||||
)}
|
||||
</Row>
|
||||
</Section>
|
||||
);
|
||||
|
|
|
@ -44,7 +44,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||
render() {
|
||||
const { item, withoutChildren } = this.props;
|
||||
return (
|
||||
<MenuItemLi onClick={this.activate} depth={item.depth} data-item-id={item.id}>
|
||||
<MenuItemLi onClick={this.activate} depth={item.depth} data-item-id={item.id} role="menuitem">
|
||||
{item.type === 'operation' ? (
|
||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||
) : (
|
||||
|
|
|
@ -125,7 +125,6 @@ export interface MenuItemLabelType {
|
|||
}
|
||||
|
||||
export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
||||
role: 'menuitem',
|
||||
className: classnames('-depth' + props.depth, {
|
||||
active: props.active,
|
||||
}),
|
||||
|
|
|
@ -48,6 +48,9 @@ export function StoreBuilder(props: StoreBuilderProps) {
|
|||
const resolved = await loadAndBundleSpec(spec || specUrl!);
|
||||
setResolvedSpec(resolved);
|
||||
} catch (e) {
|
||||
if (onLoaded) {
|
||||
onLoaded(e);
|
||||
}
|
||||
setError(e);
|
||||
throw e;
|
||||
}
|
||||
|
|
82
src/components/__tests__/SchemaDefinition.test.tsx
Normal file
82
src/components/__tests__/SchemaDefinition.test.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
/* tslint:disable:no-implicit-dependencies */
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { SchemaDefinition } from '..';
|
||||
import { OpenAPIParser } from '../../services';
|
||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||
import { withTheme } from '../testProviders';
|
||||
|
||||
const options = new RedocNormalizedOptions({});
|
||||
describe('Components', () => {
|
||||
describe('SchemaDefinition', () => {
|
||||
const parser = new OpenAPIParser(
|
||||
{
|
||||
openapi: '3.0',
|
||||
info: {
|
||||
title: 'test',
|
||||
version: '0',
|
||||
},
|
||||
paths: {},
|
||||
components: {
|
||||
schemas: {
|
||||
test: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
options,
|
||||
);
|
||||
|
||||
describe('Show example constraints', () => {
|
||||
it('should show the example as default', () => {
|
||||
const component = shallow(
|
||||
withTheme(
|
||||
<SchemaDefinition
|
||||
schemaRef="#/components/schemas/test"
|
||||
parser={parser}
|
||||
options={options}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(component.html().includes('<code>')).toBe(true);
|
||||
});
|
||||
|
||||
it('should show the example if `showExample` is `true`', () => {
|
||||
const component = shallow(
|
||||
withTheme(
|
||||
<SchemaDefinition
|
||||
schemaRef="#/components/schemas/test"
|
||||
parser={parser}
|
||||
options={options}
|
||||
showExample={true}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(component.html().includes('<code>')).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide the example if `showExample` is `false`', () => {
|
||||
const component = shallow(
|
||||
withTheme(
|
||||
<SchemaDefinition
|
||||
schemaRef="#/components/schemas/test"
|
||||
parser={parser}
|
||||
options={options}
|
||||
showExample={false}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(component.html().includes('<code>')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -41,7 +41,7 @@ export class OpenAPIParser {
|
|||
}
|
||||
}
|
||||
|
||||
validate(spec: GenericObject): void {
|
||||
validate(spec: Record<string, any>): void {
|
||||
if (spec.openapi === undefined) {
|
||||
throw new Error('Document must be valid OpenAPI 3.0.0 definition');
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ export class OpenAPIParser {
|
|||
} else {
|
||||
// small optimization
|
||||
return {
|
||||
...(resolved as GenericObject),
|
||||
...(resolved as object),
|
||||
...rest,
|
||||
} as T;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Omit } from './index';
|
||||
import type { Omit } from './index';
|
||||
|
||||
export interface OpenAPISpec {
|
||||
openapi: string;
|
||||
|
|
|
@ -1743,6 +1743,9 @@ culpa qui officia deserunt mollit anim id est laborum.
|
|||
},
|
||||
},
|
||||
},
|
||||
"security": Array [
|
||||
Object {},
|
||||
],
|
||||
"servers": Array [
|
||||
Object {
|
||||
"description": "Default server",
|
||||
|
@ -3729,6 +3732,9 @@ culpa qui officia deserunt mollit anim id est laborum.
|
|||
},
|
||||
},
|
||||
},
|
||||
"security": Array [
|
||||
Object {},
|
||||
],
|
||||
"servers": Array [
|
||||
Object {
|
||||
"description": "Default server",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function objectHas(object: GenericObject, path: string | Array<string>): boolean {
|
||||
export function objectHas(object: object, path: string | Array<string>): boolean {
|
||||
let _path = <Array<string>>path;
|
||||
|
||||
if (typeof path === 'string') {
|
||||
|
@ -12,7 +12,7 @@ export function objectHas(object: GenericObject, path: string | Array<string>):
|
|||
});
|
||||
}
|
||||
|
||||
export function objectSet(object: GenericObject, path: string | Array<string>, value: any): void {
|
||||
export function objectSet(object: object, path: string | Array<string>, value: any): void {
|
||||
let _path = <Array<string>>path;
|
||||
|
||||
if (typeof path === 'string') {
|
||||
|
|
Loading…
Reference in New Issue
Block a user