Skip to content

Commit 95a5abf

Browse files
committed
fix: update build docs command
1 parent b9c397a commit 95a5abf

File tree

4 files changed

+76
-47
lines changed

4 files changed

+76
-47
lines changed

packages/cli/src/commands/build-docs/index.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { createRequire } from 'node:module';
22
import { dirname, resolve } from 'node:path';
33
import { writeFileSync, mkdirSync } from 'node:fs';
4-
import { default as redoc } from 'redoc';
54
import { performance } from 'node:perf_hooks';
6-
import { isAbsoluteUrl, logger } from '@redocly/openapi-core';
7-
import { getObjectOrJSON, getPageHTML } from './utils.js';
5+
import { isAbsoluteUrl, loadConfig, logger, bundle } from '@redocly/openapi-core';
6+
import { getObjectOrJSON, getPageHTML, hasSwaggerProperty } from './utils.js';
87
import { getExecutionTime, getFallbackApisOrExit } from '../../utils/miscellaneous.js';
98
import { exitWithError } from '../../utils/error.js';
9+
import { convertSwagger2OpenAPI } from 'redoc';
1010

1111
import type { BuildDocsArgv } from './types.js';
1212
import type { CommandArgs } from '../../wrapper.js';
@@ -28,22 +28,33 @@ export const handlerBuildCommand = async ({
2828
disableGoogleFont: argv.disableGoogleFont,
2929
templateFileName: argv.template,
3030
templateOptions: argv.templateOptions || {},
31-
redocOptions: getObjectOrJSON(argv.theme?.openapi, config.forAlias(alias)),
31+
redocOptions: getObjectOrJSON(argv.openapi, config.forAlias(alias)),
3232
};
3333

34-
const redocCurrentVersion = packageJson.dependencies.redoc;
34+
if (argv.redocVersion !== packageJson.dependencies.redoc) {
35+
logger.warn(
36+
`\n ⚠️ You are using a custom Redoc version (${argv.redocVersion}) for generating static HTML.`
37+
);
38+
}
39+
const redocVersion = argv.redocVersion || packageJson.dependencies.redoc;
3540

3641
try {
3742
const elapsed = getExecutionTime(startedAt);
43+
const config = await loadConfig({ configPath: argv.config });
44+
const {
45+
bundle: { parsed },
46+
} = await bundle({
47+
config,
48+
ref: isAbsoluteUrl(pathToApi) ? pathToApi : resolve(pathToApi),
49+
});
50+
const openapiDescription = hasSwaggerProperty(parsed) ? convertSwagger2OpenAPI(parsed) : parsed;
3851

39-
const api = await redoc.loadAndBundleSpec(
40-
isAbsoluteUrl(pathToApi) ? pathToApi : resolve(pathToApi)
41-
);
42-
collectSpecData?.(api);
52+
logger.info(`✅ OpenAPI definition loaded and bundled in [⏱ ${elapsed}].\n`);
53+
54+
collectSpecData?.(openapiDescription);
4355
const pageHTML = await getPageHTML(
44-
api,
45-
pathToApi,
46-
{ ...options, redocCurrentVersion },
56+
openapiDescription,
57+
{ ...options, redocVersion },
4758
argv.config
4859
);
4960

packages/cli/src/commands/build-docs/types.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ export type BuildDocsOptions = {
77
disableGoogleFont?: boolean;
88
port?: number;
99
templateFileName?: string;
10-
templateOptions?: any;
10+
templateOptions?: Record<string, unknown>;
1111
redocOptions?: any;
12-
redocCurrentVersion: string;
12+
redocVersion: string;
1313
};
1414

1515
export type BuildDocsArgv = {
@@ -18,8 +18,7 @@ export type BuildDocsArgv = {
1818
title?: string;
1919
disableGoogleFont?: boolean;
2020
template?: string;
21-
templateOptions: Record<string, any>;
22-
theme: {
23-
openapi: string | Record<string, unknown>;
24-
};
21+
templateOptions: Record<string, unknown>;
22+
openapi: string | Record<string, unknown>;
23+
redocVersion?: string;
2524
} & VerifyConfigOptions;

packages/cli/src/commands/build-docs/utils.ts

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { createElement } from 'react';
2-
import { default as redoc } from 'redoc';
2+
import { Redoc, ServerStyleSheet } from 'redoc';
33
import { renderToString } from 'react-dom/server';
4-
import { ServerStyleSheet } from 'styled-components';
54
import { default as handlebars } from 'handlebars';
65
import * as path from 'node:path';
76
import { existsSync, lstatSync, readFileSync } from 'node:fs';
8-
import { isAbsoluteUrl, logger } from '@redocly/openapi-core';
7+
import { logger } from '@redocly/openapi-core';
98
import * as url from 'node:url';
109
import { exitWithError } from '../../utils/error.js';
1110

@@ -49,48 +48,55 @@ export function getObjectOrJSON(
4948
}
5049

5150
export async function getPageHTML(
52-
api: any,
53-
pathToApi: string,
51+
definition: any,
5452
{
5553
title,
5654
disableGoogleFont,
5755
templateFileName,
5856
templateOptions,
5957
redocOptions = {},
60-
redocCurrentVersion,
58+
redocVersion,
6159
}: BuildDocsOptions,
6260
configPath?: string
6361
) {
6462
logger.info('Prerendering docs\n');
6563

66-
const apiUrl = redocOptions.specUrl || (isAbsoluteUrl(pathToApi) ? pathToApi : undefined);
67-
const store = await redoc.createStore(api, apiUrl, redocOptions);
6864
const sheet = new ServerStyleSheet();
69-
70-
const html = renderToString(sheet.collectStyles(createElement(redoc.Redoc, { store })));
71-
const state = await store.toJS();
72-
const css = sheet.getStyleTags();
65+
const html = renderToString(
66+
sheet.collectStyles(
67+
createElement(Redoc, {
68+
store: {
69+
options: redocOptions,
70+
definition,
71+
},
72+
router: 'memory',
73+
withCommonStyles: true,
74+
})
75+
)
76+
);
7377

7478
templateFileName = templateFileName
7579
? templateFileName
7680
: redocOptions?.htmlTemplate
7781
? path.resolve(configPath ? path.dirname(configPath) : '', redocOptions.htmlTemplate)
7882
: path.join(__internalDirname, './template.hbs');
7983
const template = handlebars.compile(readFileSync(templateFileName).toString());
84+
8085
return template({
8186
redocHTML: `
82-
<div id="redoc">${html || ''}</div>
83-
<script>
84-
${`const __redoc_state = ${sanitizeJSONString(JSON.stringify(state))};` || ''}
85-
86-
var container = document.getElementById('redoc');
87-
Redoc.${'hydrate(__redoc_state, container)'};
88-
89-
</script>`,
90-
redocHead:
91-
`<script src="https://cdn.redocly.com/redoc/v${redocCurrentVersion}/bundles/redoc.standalone.js"></script>` +
92-
css,
93-
title: title || api.info.title || 'ReDoc documentation',
87+
<div id="redoc">${html}</div>
88+
<script type="module">
89+
import * as Redoc from "https://cdn.redocly.com/redoc/${redocVersion}/redoc.standalone.js";
90+
const __redoc_store = ${JSON.stringify({
91+
options: redocOptions,
92+
definition,
93+
})};
94+
var container = document.getElementById('redoc');
95+
Redoc.hydrate(__redoc_store, container);
96+
</script>
97+
`,
98+
redocHead: sheet.getStyleTags(),
99+
title: title || definition.info.title || 'ReDoc documentation',
94100
disableGoogleFont,
95101
templateOptions,
96102
});
@@ -109,3 +115,12 @@ export function escapeClosingScriptTag(str: string): string {
109115
export function escapeUnicode(str: string): string {
110116
return str.replace(/\u2028|\u2029/g, (m) => '\\u202' + (m === '\u2028' ? '8' : '9'));
111117
}
118+
119+
export function hasSwaggerProperty(obj: unknown): obj is { swagger: string } & Record<string, any> {
120+
if (typeof obj !== 'object' || obj === null) {
121+
return false;
122+
}
123+
124+
const candidate = obj as Record<string, any>;
125+
return 'swagger' in candidate && typeof candidate.swagger === 'string';
126+
}

packages/cli/src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -578,13 +578,17 @@ yargs(hideBin(process.argv))
578578
'Path to handlebars page template, see https://github.com/Redocly/redocly-cli/blob/main/packages/cli/src/commands/build-docs/template.hbs for the example.',
579579
type: 'string',
580580
},
581+
redocVersion: {
582+
describe: 'Version of Redoc to use.',
583+
type: 'string',
584+
},
581585
templateOptions: {
582586
describe:
583587
'Additional options to pass to the template. Use dot notation, e.g. templateOptions.metaDescription',
584588
},
585-
theme: {
589+
openapi: {
586590
describe:
587-
'Redoc theme.openapi configuration. Use dot notation, e.g. theme.openapi.nativeScrollbars',
591+
'Redoc openapi configuration. Use dot notation, e.g. openapi.hideDownloadButtons',
588592
},
589593
config: {
590594
describe: 'Path to the config file.',
@@ -596,9 +600,9 @@ yargs(hideBin(process.argv))
596600
default: 'warn' as RuleSeverity,
597601
},
598602
})
599-
.check((argv: any) => {
600-
if (argv.theme && !argv.theme?.openapi)
601-
throw Error('Invalid option: theme.openapi not set.');
603+
.check((argv) => {
604+
if (argv?.redocVersion && !argv.redocVersion.startsWith('3.'))
605+
throw Error('Invalid option: redocVersion should be higher or equal to 3.0');
602606
return true;
603607
}),
604608
async (argv) => {

0 commit comments

Comments
 (0)