@redocly/cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/commands/build-docs/index.js +2 -4
  3. package/lib/commands/build-docs/utils.d.ts +1 -1
  4. package/lib/commands/build-docs/utils.js +3 -3
  5. package/package.json +2 -2
  6. package/src/__mocks__/@redocly/openapi-core.ts +80 -0
  7. package/src/__mocks__/documents.ts +63 -0
  8. package/src/__mocks__/fs.ts +6 -0
  9. package/src/__mocks__/perf_hooks.ts +3 -0
  10. package/src/__mocks__/redoc.ts +2 -0
  11. package/src/__mocks__/utils.ts +19 -0
  12. package/src/__tests__/commands/build-docs.test.ts +62 -0
  13. package/src/__tests__/commands/bundle.test.ts +150 -0
  14. package/src/__tests__/commands/join.test.ts +122 -0
  15. package/src/__tests__/commands/lint.test.ts +190 -0
  16. package/src/__tests__/commands/push-region.test.ts +58 -0
  17. package/src/__tests__/commands/push.test.ts +492 -0
  18. package/src/__tests__/fetch-with-timeout.test.ts +35 -0
  19. package/src/__tests__/fixtures/config.ts +21 -0
  20. package/src/__tests__/fixtures/openapi.json +0 -0
  21. package/src/__tests__/fixtures/openapi.yaml +0 -0
  22. package/src/__tests__/fixtures/redocly.yaml +0 -0
  23. package/src/__tests__/utils.test.ts +564 -0
  24. package/src/__tests__/wrapper.test.ts +57 -0
  25. package/src/assert-node-version.ts +8 -0
  26. package/src/commands/build-docs/index.ts +50 -0
  27. package/src/commands/build-docs/template.hbs +23 -0
  28. package/src/commands/build-docs/types.ts +24 -0
  29. package/src/commands/build-docs/utils.ts +110 -0
  30. package/src/commands/bundle.ts +177 -0
  31. package/src/commands/join.ts +811 -0
  32. package/src/commands/lint.ts +151 -0
  33. package/src/commands/login.ts +27 -0
  34. package/src/commands/preview-docs/index.ts +190 -0
  35. package/src/commands/preview-docs/preview-server/default.hbs +24 -0
  36. package/src/commands/preview-docs/preview-server/hot.js +42 -0
  37. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  38. package/src/commands/preview-docs/preview-server/preview-server.ts +156 -0
  39. package/src/commands/preview-docs/preview-server/server.ts +91 -0
  40. package/src/commands/push.ts +441 -0
  41. package/src/commands/split/__tests__/fixtures/samples.json +61 -0
  42. package/src/commands/split/__tests__/fixtures/spec.json +70 -0
  43. package/src/commands/split/__tests__/fixtures/webhooks.json +85 -0
  44. package/src/commands/split/__tests__/index.test.ts +137 -0
  45. package/src/commands/split/index.ts +385 -0
  46. package/src/commands/split/types.ts +85 -0
  47. package/src/commands/stats.ts +119 -0
  48. package/src/custom.d.ts +1 -0
  49. package/src/fetch-with-timeout.ts +21 -0
  50. package/src/index.ts +484 -0
  51. package/src/js-utils.ts +17 -0
  52. package/src/types.ts +40 -0
  53. package/src/update-version-notifier.ts +106 -0
  54. package/src/utils.ts +590 -0
  55. package/src/wrapper.ts +42 -0
  56. package/tsconfig.json +9 -0
  57. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,24 @@
1
+ export type BuildDocsOptions = {
2
+ watch?: boolean;
3
+ output?: string;
4
+ title?: string;
5
+ disableGoogleFont?: boolean;
6
+ port?: number;
7
+ templateFileName?: string;
8
+ templateOptions?: any;
9
+ redocOptions?: any;
10
+ redocCurrentVersion: string;
11
+ };
12
+
13
+ export type BuildDocsArgv = {
14
+ api: string;
15
+ o: string;
16
+ title?: string;
17
+ disableGoogleFont?: boolean;
18
+ template?: string;
19
+ templateOptions: Record<string, any>;
20
+ theme: {
21
+ openapi: string | Record<string, unknown>;
22
+ };
23
+ config?: string;
24
+ };
@@ -0,0 +1,110 @@
1
+ import { createElement } from 'react';
2
+ import { createStore, Redoc } from 'redoc';
3
+ import { Config, isAbsoluteUrl } from '@redocly/openapi-core';
4
+
5
+ import { renderToString } from 'react-dom/server';
6
+ import { ServerStyleSheet } from 'styled-components';
7
+ import { compile } from 'handlebars';
8
+ import { dirname, join, resolve } from 'path';
9
+ import { existsSync, lstatSync, readFileSync } from 'fs';
10
+
11
+ import type { BuildDocsOptions } from './types';
12
+ import { red } from 'colorette';
13
+ import { exitWithError } from '../../utils';
14
+
15
+ export function getObjectOrJSON(
16
+ openapiOptions: string | Record<string, unknown>,
17
+ config: Config
18
+ ): JSON | Record<string, unknown> | Config {
19
+ switch (typeof openapiOptions) {
20
+ case 'object':
21
+ return openapiOptions;
22
+ case 'string':
23
+ try {
24
+ if (existsSync(openapiOptions) && lstatSync(openapiOptions).isFile()) {
25
+ return JSON.parse(readFileSync(openapiOptions, 'utf-8'));
26
+ } else {
27
+ return JSON.parse(openapiOptions);
28
+ }
29
+ } catch (e) {
30
+ process.stderr.write(
31
+ red(
32
+ `Encountered error:\n\n${openapiOptions}\n\nis neither a file with a valid JSON object neither a stringified JSON object.`
33
+ )
34
+ );
35
+ exitWithError(e);
36
+ }
37
+ break;
38
+ default: {
39
+ if (config) {
40
+ process.stderr.write(`Found ${config.configFile} and using theme.openapi options\n`);
41
+
42
+ return config.theme.openapi ? config.theme.openapi : {};
43
+ }
44
+ return {};
45
+ }
46
+ }
47
+ return {};
48
+ }
49
+
50
+ export async function getPageHTML(
51
+ api: any,
52
+ pathToApi: string,
53
+ {
54
+ title,
55
+ disableGoogleFont,
56
+ templateFileName,
57
+ templateOptions,
58
+ redocOptions = {},
59
+ redocCurrentVersion,
60
+ }: BuildDocsOptions,
61
+ configPath?: string
62
+ ) {
63
+ process.stderr.write('Prerendering docs\n');
64
+
65
+ const apiUrl = redocOptions.specUrl || (isAbsoluteUrl(pathToApi) ? pathToApi : undefined);
66
+ const store = await createStore(api, apiUrl, redocOptions);
67
+ const sheet = new ServerStyleSheet();
68
+
69
+ const html = renderToString(sheet.collectStyles(createElement(Redoc, { store })));
70
+ const state = await store.toJS();
71
+ const css = sheet.getStyleTags();
72
+
73
+ templateFileName = templateFileName
74
+ ? templateFileName
75
+ : redocOptions?.htmlTemplate
76
+ ? resolve(configPath ? dirname(configPath) : '', redocOptions.htmlTemplate)
77
+ : join(__dirname, './template.hbs');
78
+ const template = compile(readFileSync(templateFileName).toString());
79
+ return template({
80
+ redocHTML: `
81
+ <div id="redoc">${html || ''}</div>
82
+ <script>
83
+ ${`const __redoc_state = ${sanitizeJSONString(JSON.stringify(state))};` || ''}
84
+
85
+ var container = document.getElementById('redoc');
86
+ Redoc.${'hydrate(__redoc_state, container)'};
87
+
88
+ </script>`,
89
+ redocHead:
90
+ `<script src="https://cdn.redoc.ly/redoc/v${redocCurrentVersion}/bundles/redoc.standalone.js"></script>` +
91
+ css,
92
+ title: title || api.info.title || 'ReDoc documentation',
93
+ disableGoogleFont,
94
+ templateOptions,
95
+ });
96
+ }
97
+
98
+ export function sanitizeJSONString(str: string): string {
99
+ return escapeClosingScriptTag(escapeUnicode(str));
100
+ }
101
+
102
+ // see http://www.thespanner.co.uk/2011/07/25/the-json-specification-is-now-wrong/
103
+ export function escapeClosingScriptTag(str: string): string {
104
+ return str.replace(/<\/script>/g, '<\\/script>');
105
+ }
106
+
107
+ // see http://www.thespanner.co.uk/2011/07/25/the-json-specification-is-now-wrong/
108
+ export function escapeUnicode(str: string): string {
109
+ return str.replace(/\u2028|\u2029/g, (m) => '\\u202' + (m === '\u2028' ? '8' : '9'));
110
+ }
@@ -0,0 +1,177 @@
1
+ import {
2
+ formatProblems,
3
+ getTotals,
4
+ getMergedConfig,
5
+ lint,
6
+ bundle,
7
+ Config,
8
+ OutputFormat,
9
+ } from '@redocly/openapi-core';
10
+ import {
11
+ dumpBundle,
12
+ getExecutionTime,
13
+ getFallbackApisOrExit,
14
+ getOutputFileName,
15
+ handleError,
16
+ printUnusedWarnings,
17
+ saveBundle,
18
+ printLintTotals,
19
+ checkIfRulesetExist,
20
+ sortTopLevelKeysForOas,
21
+ } from '../utils';
22
+ import type { OutputExtensions, Skips, Totals } from '../types';
23
+ import { performance } from 'perf_hooks';
24
+ import { blue, gray, green, yellow } from 'colorette';
25
+ import { writeFileSync } from 'fs';
26
+
27
+ export type BundleOptions = {
28
+ apis?: string[];
29
+ 'max-problems': number;
30
+ extends?: string[];
31
+ config?: string;
32
+ format: OutputFormat;
33
+ output?: string;
34
+ ext: OutputExtensions;
35
+ dereferenced?: boolean;
36
+ force?: boolean;
37
+ lint?: boolean;
38
+ metafile?: string;
39
+ 'remove-unused-components'?: boolean;
40
+ 'keep-url-references'?: boolean;
41
+ } & Skips;
42
+
43
+ export async function handleBundle(argv: BundleOptions, config: Config, version: string) {
44
+ const removeUnusedComponents =
45
+ argv['remove-unused-components'] ||
46
+ config.rawConfig?.styleguide?.decorators?.hasOwnProperty('remove-unused-components');
47
+ const apis = await getFallbackApisOrExit(argv.apis, config);
48
+ const totals: Totals = { errors: 0, warnings: 0, ignored: 0 };
49
+ const maxProblems = argv['max-problems'];
50
+
51
+ for (const { path, alias } of apis) {
52
+ try {
53
+ const startedAt = performance.now();
54
+ const resolvedConfig = getMergedConfig(config, alias);
55
+ const { styleguide } = resolvedConfig;
56
+
57
+ styleguide.skipRules(argv['skip-rule']);
58
+ styleguide.skipPreprocessors(argv['skip-preprocessor']);
59
+ styleguide.skipDecorators(argv['skip-decorator']);
60
+
61
+ if (argv.lint) {
62
+ checkIfRulesetExist(styleguide.rules);
63
+ if (config.styleguide.recommendedFallback) {
64
+ process.stderr.write(
65
+ `No configurations were provided -- using built in ${blue(
66
+ 'recommended'
67
+ )} configuration by default.\n\n`
68
+ );
69
+ }
70
+ const results = await lint({
71
+ ref: path,
72
+ config: resolvedConfig,
73
+ });
74
+ const fileLintTotals = getTotals(results);
75
+
76
+ totals.errors += fileLintTotals.errors;
77
+ totals.warnings += fileLintTotals.warnings;
78
+ totals.ignored += fileLintTotals.ignored;
79
+
80
+ formatProblems(results, {
81
+ format: argv.format || 'codeframe',
82
+ totals: fileLintTotals,
83
+ version,
84
+ maxProblems,
85
+ });
86
+ printLintTotals(fileLintTotals, 2);
87
+ }
88
+
89
+ process.stderr.write(gray(`bundling ${path}...\n`));
90
+
91
+ const {
92
+ bundle: result,
93
+ problems,
94
+ ...meta
95
+ } = await bundle({
96
+ config: resolvedConfig,
97
+ ref: path,
98
+ dereference: argv.dereferenced,
99
+ removeUnusedComponents,
100
+ keepUrlRefs: argv['keep-url-references'],
101
+ });
102
+
103
+ const fileTotals = getTotals(problems);
104
+ const { outputFile, ext } = getOutputFileName(path, apis.length, argv.output, argv.ext);
105
+
106
+ if (fileTotals.errors === 0 || argv.force) {
107
+ if (!argv.output) {
108
+ const output = dumpBundle(
109
+ sortTopLevelKeysForOas(result.parsed),
110
+ argv.ext || 'yaml',
111
+ argv.dereferenced
112
+ );
113
+ process.stdout.write(output);
114
+ } else {
115
+ const output = dumpBundle(sortTopLevelKeysForOas(result.parsed), ext, argv.dereferenced);
116
+ saveBundle(outputFile, output);
117
+ }
118
+ }
119
+
120
+ totals.errors += fileTotals.errors;
121
+ totals.warnings += fileTotals.warnings;
122
+ totals.ignored += fileTotals.ignored;
123
+
124
+ formatProblems(problems, {
125
+ format: argv.format,
126
+ maxProblems,
127
+ totals: fileTotals,
128
+ version,
129
+ });
130
+
131
+ if (argv.metafile) {
132
+ if (apis.length > 1) {
133
+ process.stderr.write(
134
+ yellow(`[WARNING] "--metafile" cannot be used with multiple apis. Skipping...`)
135
+ );
136
+ }
137
+ {
138
+ writeFileSync(argv.metafile, JSON.stringify(meta), 'utf-8');
139
+ }
140
+ }
141
+
142
+ const elapsed = getExecutionTime(startedAt);
143
+ if (fileTotals.errors > 0) {
144
+ if (argv.force) {
145
+ process.stderr.write(
146
+ `❓ Created a bundle for ${blue(path)} at ${blue(outputFile)} with errors ${green(
147
+ elapsed
148
+ )}.\n${yellow('Errors ignored because of --force')}.\n`
149
+ );
150
+ } else {
151
+ process.stderr.write(
152
+ `❌ Errors encountered while bundling ${blue(
153
+ path
154
+ )}: bundle not created (use --force to ignore errors).\n`
155
+ );
156
+ }
157
+ } else {
158
+ process.stderr.write(
159
+ `📦 Created a bundle for ${blue(path)} at ${blue(outputFile)} ${green(elapsed)}.\n`
160
+ );
161
+ }
162
+
163
+ const removedCount = meta.visitorsData?.['remove-unused-components']?.removedCount;
164
+ if (removedCount) {
165
+ process.stderr.write(gray(`🧹 Removed ${removedCount} unused components.\n`));
166
+ }
167
+ } catch (e) {
168
+ handleError(e, path);
169
+ }
170
+ }
171
+
172
+ printUnusedWarnings(config.styleguide);
173
+
174
+ if (!(totals.errors === 0 || argv.force)) {
175
+ throw new Error('Bundle failed.');
176
+ }
177
+ }