Skip to content

Commit ad66f92

Browse files
authored
Add support for multiple yamls, uses openapi to bundle yamls (#148)
1 parent 937e73a commit ad66f92

File tree

8 files changed

+1325
-29
lines changed

8 files changed

+1325
-29
lines changed

.changeset/new-poems-return.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'docusaurus-plugin-redoc': minor
3+
---
4+
5+
Add support for bundling multiple yamls that reference each other

packages/docusaurus-plugin-redoc/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
"dependencies": {
2929
"@docusaurus/types": "^2.0.0-beta.17",
3030
"@docusaurus/utils": "^2.0.0-beta.17",
31+
"@redocly/openapi-core": "^1.0.0-beta.87",
3132
"joi": "^17.5.0",
32-
"redoc": "^2.0.0-rc.64",
33-
"yaml": "^1.10.2"
33+
"redoc": "^2.0.0-rc.64"
3434
},
3535
"engines": {
3636
"node": ">=14"

packages/docusaurus-plugin-redoc/src/index.ts

+75-26
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ import type {
66
OptionValidationContext,
77
} from '@docusaurus/types';
88
import { normalizeUrl } from '@docusaurus/utils';
9-
import YAML from 'yaml';
109
import { loadAndBundleSpec } from 'redoc';
10+
import {
11+
formatProblems,
12+
getTotals,
13+
Config,
14+
bundle,
15+
loadConfig,
16+
stringifyYaml,
17+
} from '@redocly/openapi-core';
1118

1219
import {
1320
PluginOptionSchema,
1421
PluginOptions,
1522
PluginOptionsWithDefault,
1623
DEFAULT_OPTIONS,
1724
} from './options';
18-
import { ParsedSpec, SpecProps, ApiDocProps } from './types/common';
25+
import { SpecProps, ApiDocProps } from './types/common';
26+
// eslint-disable-next-line @typescript-eslint/no-var-requires
27+
const version = require('../package.json').version;
1928

2029
export { PluginOptions };
2130

@@ -25,50 +34,69 @@ export default function redocPlugin(
2534
): Plugin<Record<string, unknown>> {
2635
const { baseUrl } = context.siteConfig;
2736
const options: PluginOptionsWithDefault = { ...DEFAULT_OPTIONS, ...opts };
28-
const { debug, spec, url: downloadUrl } = options;
37+
const { debug, spec, url: downloadUrl, config } = options;
38+
2939
let url = downloadUrl;
40+
const isSpecFile = fs.existsSync(spec);
41+
const fileName = path.join(
42+
'redocusaurus',
43+
`${options.id || 'api-spec'}.yaml`,
44+
);
45+
3046
if (debug) {
3147
console.error('[REDOCUSAURUS_PLUGIN] Opts Input:', opts);
3248
console.error('[REDOCUSAURUS_PLUGIN] Options:', options);
3349
}
3450
return {
3551
name: 'docusaurus-plugin-redoc',
3652
async loadContent() {
37-
let parsedSpec: ParsedSpec | null = null;
38-
// If local file
39-
if (fs.existsSync(spec)) {
53+
if (!isSpecFile) {
54+
// If spec is a remote url then add it as download url also as a default
55+
url = url || spec;
4056
if (debug) {
41-
console.log('[REDOCUSAURUS_PLUGIN] reading file: ', spec);
57+
console.log('[REDOCUSAURUS_PLUGIN] bundling spec from url', spec);
4258
}
59+
return loadAndBundleSpec(spec!);
60+
}
61+
62+
// If local file
63+
if (debug) {
64+
console.log('[REDOCUSAURUS_PLUGIN] reading file: ', spec);
65+
}
4366

44-
const file = fs.readFileSync(spec).toString();
67+
let redoclyConfig: Config;
4568

46-
if (spec.endsWith('.yaml') || spec.endsWith('.yml')) {
47-
parsedSpec = YAML.parse(file);
48-
} else parsedSpec = JSON.parse(file);
69+
if (config) {
70+
if (typeof config === 'string') {
71+
redoclyConfig = await loadConfig(config);
72+
} else {
73+
redoclyConfig = new Config(config);
74+
}
4975
} else {
50-
// If spec is a remote url then add it as download url
51-
url = spec;
76+
redoclyConfig = await loadConfig();
5277
}
5378

54-
if (debug) {
55-
console.log('[REDOCUSAURUS_PLUGIN] bundling spec');
79+
const { bundle: bundledSpec, problems } = await bundle({
80+
ref: spec,
81+
config: redoclyConfig,
82+
});
83+
84+
if (problems?.length) {
85+
console.error('[REDOCUSAURUS_PLUGIN] errors while bundling spec', spec);
86+
87+
formatProblems(problems, {
88+
totals: getTotals(problems),
89+
version,
90+
});
5691
}
57-
const content = await loadAndBundleSpec(parsedSpec || spec!);
5892

5993
if (debug) {
60-
console.log('[REDOCUSAURUS_PLUGIN] Content loaded');
94+
console.log('[REDOCUSAURUS_PLUGIN] File Bundled');
6195
}
6296

63-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
64-
return content as any;
65-
},
66-
getPathsToWatch() {
67-
if (!spec) {
68-
return [];
69-
}
70-
const contentPath = path.resolve(context.siteDir, spec);
71-
return [contentPath];
97+
// If download url is not provided then use bundled yaml as a static file (see `postBuild`)
98+
url = url || fileName;
99+
return bundledSpec.parsed;
72100
},
73101
async contentLoaded({ content, actions }) {
74102
const { createData, addRoute, setGlobalData } = actions;
@@ -114,6 +142,27 @@ export default function redocPlugin(
114142
addRoute(routeOptions);
115143
}
116144
},
145+
async postBuild(props) {
146+
if (!isSpecFile || downloadUrl) {
147+
return;
148+
}
149+
// Create a static file from bundled spec
150+
const staticFile = path.join(context.outDir, fileName);
151+
fs.mkdirSync(path.dirname(staticFile));
152+
console.error(
153+
'[REDOCUSAURUS_PLUGIN] creating static bundle copy for download',
154+
staticFile,
155+
);
156+
// create bundled url
157+
const bundledYaml = stringifyYaml(props.content);
158+
fs.writeFileSync(staticFile, bundledYaml);
159+
},
160+
getPathsToWatch() {
161+
if (!isSpecFile) {
162+
return [];
163+
}
164+
return [path.resolve(spec)];
165+
},
117166
};
118167
}
119168

packages/docusaurus-plugin-redoc/src/options.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Joi from 'joi';
2+
import { RawConfig } from '@redocly/openapi-core';
23

34
type LayoutProps = {
45
title?: string;
@@ -21,6 +22,11 @@ export interface PluginOptions {
2122
route?: string;
2223
layout?: LayoutProps;
2324
debug?: boolean;
25+
/**
26+
* Redocly config to bundle file
27+
* @see https://redocly.com/docs/cli/configuration/configuration-file/
28+
*/
29+
config?: string | Partial<RawConfig>;
2430
}
2531

2632
export interface PluginOptionsWithDefault extends PluginOptions {
@@ -39,4 +45,5 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
3945
layout: Joi.any().default(DEFAULT_OPTIONS.layout),
4046
debug: Joi.boolean().default(DEFAULT_OPTIONS.debug),
4147
route: Joi.string().uri({ relativeOnly: true }).optional(),
48+
config: Joi.any().optional(),
4249
});

website/docusaurus.config.js

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ const redocusaurus = [
1616
url: `/openapi-page.yaml`,
1717
route: '/examples/using-spec-yaml/',
1818
},
19+
{
20+
id: 'using-multi-file-yaml',
21+
// Local File
22+
spec: 'examples/multi-yaml/index.yaml',
23+
route: '/examples/using-multi-file-yaml/',
24+
},
1925
{
2026
id: 'using-spec-url',
2127
// Remote File
@@ -119,6 +125,10 @@ const config = {
119125
label: 'Custom Layout',
120126
to: '/examples/custom-layout/',
121127
},
128+
{
129+
label: 'Using Multiple YAMLs',
130+
to: '/examples/using-multi-file-yaml/',
131+
},
122132
{
123133
label: 'Using Spec URL',
124134
to: '/examples/using-spec-url/',

0 commit comments

Comments
 (0)