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 $ 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 ### Commonly used NPM scripts
``` bash ``` 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: Refer to the Redocly's documentation for more information on these products:
- [Portals](https://redoc.ly/docs/developer-portal/introduction/) - [Portals](https://redocly.com/docs/developer-portal/introduction/)
- [Reference](https://redoc.ly/docs/api-reference-docs/getting-started/) - [Reference](https://redocly.com/docs/api-reference-docs/getting-started/)
- [Redoc](https://redoc.ly/docs/redoc/quickstart/intro/) - [Redoc](https://redocly.com/docs/redoc/quickstart/intro/)
## Features ## Features
- Responsive three-panel design with menu/scrolling synchronization - Responsive three-panel design with menu/scrolling synchronization
- [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/) - [Multiple deployment options](https://redocly.com/docs/redoc/quickstart/intro/)
- [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands) - [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 - 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) [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/) - [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) ![](docs/images/nested-demo.gif)
## Customization options ## Customization options
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services) [<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://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension - 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://redoc.ly/docs/api-reference-docs/configuration/theming/) - Branding/customizations using the [`theme` option](https://redocly.com/docs/api-reference-docs/configuration/theming/)
## Support ## Support
- OpenAPI v3.0 support - OpenAPI v3.0 support

View File

@ -12,7 +12,6 @@
"boxen": "5.1.2", "boxen": "5.1.2",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"isarray": "^2.0.5",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"mobx": "^6.3.2", "mobx": "^6.3.2",
"node-libs-browser": "^2.2.1", "node-libs-browser": "^2.2.1",
@ -924,8 +923,7 @@
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": { "dependencies": {
"base64-js": "^1.0.2", "base64-js": "^1.0.2",
"ieee754": "^1.1.4", "ieee754": "^1.1.4"
"isarray": "^1.0.0"
} }
}, },
"node_modules/buffer-from": { "node_modules/buffer-from": {
@ -939,11 +937,6 @@
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" "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": { "node_modules/builtin-status-codes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
@ -1630,11 +1623,6 @@
"node": ">=0.12.0" "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": { "node_modules/jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@ -2328,18 +2316,12 @@
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0", "process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1", "safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1", "string_decoder": "~1.1.1",
"util-deprecate": "~1.0.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": { "node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "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==", "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"requires": { "requires": {
"base64-js": "^1.0.2", "base64-js": "^1.0.2",
"ieee754": "^1.1.4", "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="
}
} }
}, },
"buffer-from": { "buffer-from": {
@ -4516,11 +4490,6 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" "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": { "jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@ -5061,18 +5030,12 @@
"requires": { "requires": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0", "process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1", "safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1", "string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1" "util-deprecate": "~1.0.1"
}, },
"dependencies": { "dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",

View File

@ -14,7 +14,6 @@
"boxen": "5.1.2", "boxen": "5.1.2",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"isarray": "^2.0.5",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"mobx": "^6.3.2", "mobx": "^6.3.2",
"node-libs-browser": "^2.2.1", "node-libs-browser": "^2.2.1",

View File

@ -86,7 +86,7 @@ class DemoApp extends React.Component<
let proxiedUrl = specUrl; let proxiedUrl = specUrl;
if (specUrl !== DEFAULT_SPEC) { if (specUrl !== DEFAULT_SPEC) {
proxiedUrl = cors proxiedUrl = cors
? '\\\\cors.redoc.ly/' + new URL(specUrl, window.location.href).href ? 'https://cors.redoc.ly/' + new URL(specUrl, window.location.href).href
: specUrl; : specUrl;
} }
return ( return (

View File

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

View File

@ -83,6 +83,8 @@ x-tagGroups:
tags: tags:
- pet_model - pet_model
- store_model - store_model
security:
- {}
paths: paths:
/pet: /pet:
parameters: 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'; import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/Dropdown';
export interface DropdownOrLabelProps extends DropdownProps { export interface DropdownOrLabelProps extends DropdownProps {
Label?: StyledComponent<any, any, GenericObject, never>; Label?: StyledComponent<any, any, Record<string, any>, never>;
Dropdown?: StyledComponent< Dropdown?: StyledComponent<
React.NamedExoticComponent<DropdownProps>, React.NamedExoticComponent<DropdownProps>,
any, any,

View File

@ -73,7 +73,7 @@ export class Field extends React.Component<FieldProps> {
<button <button
onClick={this.toggle} onClick={this.toggle}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
aria-label="expand properties" aria-label={`expand ${name}`}
> >
<span className="property-name">{name}</span> <span className="property-name">{name}</span>
<ShelfIcon direction={expanded ? 'down' : 'right'} /> <ShelfIcon direction={expanded ? 'down' : 'right'} />

View File

@ -8,7 +8,7 @@ import {
TypePrefix, TypePrefix,
TypeTitle, TypeTitle,
} from '../../common-elements/fields'; } from '../../common-elements/fields';
import { getSerializedValue } from '../../utils'; import { getSerializedValue, isObject } from '../../utils';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues'; import { EnumValues } from './EnumValues';
@ -52,6 +52,10 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
return null; return null;
}, [field, showExamples]); }, [field, showExamples]);
const defaultValue = isObject(schema.default)
? getSerializedValue(field, schema.default).replace(`${field.name}=`, '')
: schema.default;
return ( return (
<div> <div>
<div> <div>
@ -92,7 +96,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
<Badge type="warning"> {l('deprecated')} </Badge> <Badge type="warning"> {l('deprecated')} </Badge>
</div> </div>
)} )}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} /> <FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} />
{!renderDiscriminatorSwitch && ( {!renderDiscriminatorSwitch && (
<EnumValues isArrayType={isArrayType} values={schema.enum} /> <EnumValues isArrayType={isArrayType} values={schema.enum} />
)}{' '} )}{' '}

View File

@ -34,7 +34,12 @@ export const RedocStandalone = function (props: RedocStandaloneProps) {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}> <StoreBuilder
spec={spec ? { ...spec } : undefined}
specUrl={specUrl}
options={options}
onLoaded={onLoaded}
>
{({ loading, store }) => {({ loading, store }) =>
!loading ? ( !loading ? (
<Redoc store={store!} /> <Redoc store={store!} />

View File

@ -14,6 +14,7 @@ export interface ObjectDescriptionProps {
exampleRef?: string; exampleRef?: string;
showReadOnly?: boolean; showReadOnly?: boolean;
showWriteOnly?: boolean; showWriteOnly?: boolean;
showExample?: boolean;
parser: OpenAPIParser; parser: OpenAPIParser;
options: RedocNormalizedOptions; options: RedocNormalizedOptions;
} }
@ -53,7 +54,7 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
} }
render() { render() {
const { showReadOnly = true, showWriteOnly = false } = this.props; const { showReadOnly = true, showWriteOnly = false, showExample = true } = this.props;
return ( return (
<Section> <Section>
<Row> <Row>
@ -64,11 +65,16 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
schema={this.mediaModel.schema} schema={this.mediaModel.schema}
/> />
</MiddlePanel> </MiddlePanel>
<DarkRightPanel> {showExample && (
<MediaSamplesWrap> <DarkRightPanel>
<MediaTypeSamples renderDropdown={this.renderDropdown} mediaType={this.mediaModel} /> <MediaSamplesWrap>
</MediaSamplesWrap> <MediaTypeSamples
</DarkRightPanel> renderDropdown={this.renderDropdown}
mediaType={this.mediaModel}
/>
</MediaSamplesWrap>
</DarkRightPanel>
)}
</Row> </Row>
</Section> </Section>
); );

View File

@ -44,7 +44,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
render() { render() {
const { item, withoutChildren } = this.props; const { item, withoutChildren } = this.props;
return ( 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' ? ( {item.type === 'operation' ? (
<OperationMenuItemContent {...this.props} item={item as OperationModel} /> <OperationMenuItemContent {...this.props} item={item as OperationModel} />
) : ( ) : (

View File

@ -125,7 +125,6 @@ export interface MenuItemLabelType {
} }
export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
role: 'menuitem',
className: classnames('-depth' + props.depth, { className: classnames('-depth' + props.depth, {
active: props.active, active: props.active,
}), }),

View File

@ -48,6 +48,9 @@ export function StoreBuilder(props: StoreBuilderProps) {
const resolved = await loadAndBundleSpec(spec || specUrl!); const resolved = await loadAndBundleSpec(spec || specUrl!);
setResolvedSpec(resolved); setResolvedSpec(resolved);
} catch (e) { } catch (e) {
if (onLoaded) {
onLoaded(e);
}
setError(e); setError(e);
throw 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) { if (spec.openapi === undefined) {
throw new Error('Document must be valid OpenAPI 3.0.0 definition'); throw new Error('Document must be valid OpenAPI 3.0.0 definition');
} }
@ -153,7 +153,7 @@ export class OpenAPIParser {
} else { } else {
// small optimization // small optimization
return { return {
...(resolved as GenericObject), ...(resolved as object),
...rest, ...rest,
} as T; } as T;
} }

View File

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

View File

@ -1743,6 +1743,9 @@ culpa qui officia deserunt mollit anim id est laborum.
}, },
}, },
}, },
"security": Array [
Object {},
],
"servers": Array [ "servers": Array [
Object { Object {
"description": "Default server", "description": "Default server",
@ -3729,6 +3732,9 @@ culpa qui officia deserunt mollit anim id est laborum.
}, },
}, },
}, },
"security": Array [
Object {},
],
"servers": Array [ "servers": Array [
Object { Object {
"description": "Default server", "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; let _path = <Array<string>>path;
if (typeof path === 'string') { 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; let _path = <Array<string>>path;
if (typeof path === 'string') { if (typeof path === 'string') {