Merge branch 'main' into feat/add-deprecation-label-to-redoc-cli

This commit is contained in:
Alex Varchuk 2023-03-06 11:35:57 +02:00
commit fa7454f73b
No known key found for this signature in database
GPG Key ID: F1FD9569DC4AED04
21 changed files with 582 additions and 327 deletions

View File

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

View File

@ -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:
![](docs/images/nested-demo.gif)
## 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

View File

@ -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",

View File

@ -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",

View File

@ -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 (

View File

@ -88,6 +88,8 @@ x-tagGroups:
tags:
- pet_model
- store_model
security:
- {}
paths:
/pet:
parameters:

View File

@ -83,6 +83,8 @@ x-tagGroups:
tags:
- pet_model
- store_model
security:
- {}
paths:
/pet:
parameters:

695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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'} />

View File

@ -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} />
)}{' '}

View File

@ -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!} />

View File

@ -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>
);

View File

@ -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} />
) : (

View File

@ -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,
}),

View File

@ -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;
}

View 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);
});
});
});
});

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
import { Omit } from './index';
import type { Omit } from './index';
export interface OpenAPISpec {
openapi: string;

View File

@ -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",

View File

@ -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') {