mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-09 20:57:44 +00:00
chore: refactored cli commands handlers
This commit is contained in:
59
package-lock.json
generated
59
package-lock.json
generated
@@ -2014,6 +2014,19 @@
|
||||
"fsevents": "~2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
@@ -3526,9 +3539,10 @@
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz",
|
||||
"integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8315,6 +8329,18 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.11.6",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.6.tgz",
|
||||
"integrity": "sha512-oASI1FOJ7BBFkSCNDZ446EgkSuHkOZBuqRFrwXIKWCoXw8ZXQETooTQjkAcBS03Acab7ubCKsXnwuV2svy061g==",
|
||||
"extraneous": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
@@ -9746,11 +9772,10 @@
|
||||
}
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@redocly/openapi-cli",
|
||||
"version": "1.0.0-beta.22",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/openapi-core": "^1.0.0-beta.19",
|
||||
"@redocly/openapi-core": "^1.0.0-beta.22",
|
||||
"@types/node": "^14.11.8",
|
||||
"chokidar": "^3.4.0",
|
||||
"colorette": "^1.2.0",
|
||||
@@ -9774,7 +9799,6 @@
|
||||
}
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@redocly/openapi-core",
|
||||
"version": "1.0.0-beta.22",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10442,7 +10466,7 @@
|
||||
"@redocly/openapi-cli": {
|
||||
"version": "file:packages/cli",
|
||||
"requires": {
|
||||
"@redocly/openapi-core": "^1.0.0-beta.19",
|
||||
"@redocly/openapi-core": "^1.0.0-beta.22",
|
||||
"@types/node": "^14.11.8",
|
||||
"@types/yargs": "^15.0.5",
|
||||
"chokidar": "^3.4.0",
|
||||
@@ -11543,6 +11567,14 @@
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
@@ -12798,9 +12830,10 @@
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz",
|
||||
"integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
@@ -16567,6 +16600,12 @@
|
||||
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.11.6",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.6.tgz",
|
||||
"integrity": "sha512-oASI1FOJ7BBFkSCNDZ446EgkSuHkOZBuqRFrwXIKWCoXw8ZXQETooTQjkAcBS03Acab7ubCKsXnwuV2svy061g==",
|
||||
"extraneous": true
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"Andriy Leliv <andriy@redoc.ly> (https://redoc.ly/)"
|
||||
],
|
||||
"dependencies": {
|
||||
"@redocly/openapi-core": "^1.0.0-beta.19",
|
||||
"@redocly/openapi-core": "^1.0.0-beta.22",
|
||||
"@types/node": "^14.11.8",
|
||||
"yargs": "^15.3.1",
|
||||
"colorette": "^1.2.0",
|
||||
|
||||
108
packages/cli/src/commands/bundle.ts
Normal file
108
packages/cli/src/commands/bundle.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { bundle, formatProblems, loadConfig, OutputFormat } from '@redocly/openapi-core';
|
||||
import {
|
||||
dumpBundle,
|
||||
getExecutionTime,
|
||||
getFallbackEntryPointsOrExit,
|
||||
getOutputFileName,
|
||||
getTotals, handleError, printUnusedWarnings,
|
||||
saveBundle,
|
||||
} from '../utils';
|
||||
import { OutputExtensions, Totals } from '../types';
|
||||
import { performance } from "perf_hooks";
|
||||
import { blue, gray, green, yellow } from 'colorette';
|
||||
|
||||
export async function handleBundle (argv: {
|
||||
entrypoints: string[];
|
||||
output?: string;
|
||||
ext: OutputExtensions;
|
||||
'max-problems'?: number;
|
||||
'skip-rule'?: string[];
|
||||
'skip-preprocessor'?: string[];
|
||||
'skip-decorator'?: string[];
|
||||
dereferenced?: boolean;
|
||||
force?: boolean;
|
||||
config?: string;
|
||||
format: OutputFormat;
|
||||
},
|
||||
version: string
|
||||
) {
|
||||
const config = await loadConfig(argv.config);
|
||||
config.lint.skipRules(argv['skip-rule']);
|
||||
config.lint.skipPreprocessors(argv['skip-preprocessor']);
|
||||
config.lint.skipDecorators(argv['skip-decorator']);
|
||||
|
||||
const entrypoints = await getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
||||
|
||||
const totals: Totals = { errors: 0, warnings: 0, ignored: 0 };
|
||||
for (const entrypoint of entrypoints) {
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
process.stderr.write(gray(`bundling ${entrypoint}...\n`));
|
||||
const { bundle: result, problems } = await bundle({
|
||||
config,
|
||||
ref: entrypoint,
|
||||
dereference: argv.dereferenced,
|
||||
});
|
||||
|
||||
const fileTotals = getTotals(problems);
|
||||
|
||||
const { outputFile, ext } = getOutputFileName(
|
||||
entrypoint,
|
||||
entrypoints.length,
|
||||
argv.output,
|
||||
argv.ext,
|
||||
);
|
||||
|
||||
if (fileTotals.errors === 0 || argv.force) {
|
||||
if (!argv.output) {
|
||||
const output = dumpBundle(result, argv.ext || 'yaml', argv.dereferenced);
|
||||
process.stdout.write(output);
|
||||
} else {
|
||||
const output = dumpBundle(result, ext, argv.dereferenced);
|
||||
saveBundle(outputFile, output);
|
||||
}
|
||||
}
|
||||
|
||||
totals.errors += fileTotals.errors;
|
||||
totals.warnings += fileTotals.warnings;
|
||||
totals.ignored += fileTotals.ignored;
|
||||
|
||||
formatProblems(problems, {
|
||||
format: argv.format,
|
||||
maxProblems: argv['max-problems'],
|
||||
totals: fileTotals,
|
||||
version,
|
||||
});
|
||||
|
||||
const elapsed = getExecutionTime(startedAt);
|
||||
if (fileTotals.errors > 0) {
|
||||
if (argv.force) {
|
||||
process.stderr.write(
|
||||
`❓ Created a bundle for ${blue(entrypoint)} at ${blue(
|
||||
outputFile,
|
||||
)} with errors ${green(elapsed)}.\n${yellow(
|
||||
'Errors ignored because of --force',
|
||||
)}.\n`,
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`❌ Errors encountered while bundling ${blue(
|
||||
entrypoint,
|
||||
)}: bundle not created (use --force to ignore errors).\n`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`📦 Created a bundle for ${blue(entrypoint)} at ${blue(outputFile)} ${green(
|
||||
elapsed,
|
||||
)}.\n`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(e, entrypoint);
|
||||
}
|
||||
}
|
||||
|
||||
printUnusedWarnings(config.lint);
|
||||
process.exit(totals.errors === 0 || argv.force ? 0 : 1);
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
BaseResolver,
|
||||
Document,
|
||||
LintConfig,
|
||||
Oas3Tag,
|
||||
loadConfig,
|
||||
formatProblems,
|
||||
validateDocument,
|
||||
detectOpenAPI,
|
||||
Oas3Tag,
|
||||
detectOpenAPI
|
||||
} from '@redocly/openapi-core';
|
||||
|
||||
import {
|
||||
@@ -38,7 +38,6 @@ type JoinDocumentContext = {
|
||||
componentsPrefix: string | undefined
|
||||
}
|
||||
|
||||
|
||||
export async function handleJoin (argv: {
|
||||
entrypoints: string[],
|
||||
lint?: boolean,
|
||||
|
||||
91
packages/cli/src/commands/lint.ts
Normal file
91
packages/cli/src/commands/lint.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Config, formatProblems, loadConfig, OutputFormat, validate } from '@redocly/openapi-core';
|
||||
import {
|
||||
getExecutionTime,
|
||||
getFallbackEntryPointsOrExit,
|
||||
getTotals,
|
||||
handleError,
|
||||
pluralize,
|
||||
printLintTotals,
|
||||
printUnusedWarnings
|
||||
} from '../utils';
|
||||
import { Totals } from '../types';
|
||||
import { blue, gray } from 'colorette';
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
export async function handleLint (argv: {
|
||||
entrypoints: string[];
|
||||
'max-problems'?: number;
|
||||
'generate-ignore-file'?: boolean;
|
||||
'skip-rule'?: string[];
|
||||
'skip-preprocessor'?: string[];
|
||||
extends?: string[];
|
||||
config?: string;
|
||||
format: OutputFormat;
|
||||
},
|
||||
version: string
|
||||
) {
|
||||
const config: Config = await loadConfig(argv.config, argv.extends);
|
||||
config.lint.skipRules(argv['skip-rule']);
|
||||
config.lint.skipPreprocessors(argv['skip-preprocessor']);
|
||||
const entrypoints = await getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
||||
if (argv['generate-ignore-file']) {
|
||||
config.lint.ignore = {}; // clear ignore
|
||||
}
|
||||
const totals: Totals = { errors: 0, warnings: 0, ignored: 0 };
|
||||
let totalIgnored = 0;
|
||||
if (config.lint.recommendedFallback) {
|
||||
process.stderr.write(
|
||||
`No configurations were defined in extends -- using built in ${blue('recommended')} configuration by default.\n\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: use shared externalRef resolver, blocked by preprocessors now as they can mutate documents
|
||||
for (const entryPoint of entrypoints) {
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
process.stderr.write(gray(`validating ${entryPoint}...\n`));
|
||||
const results = await validate({
|
||||
ref: entryPoint,
|
||||
config,
|
||||
});
|
||||
|
||||
const fileTotals = getTotals(results);
|
||||
totals.errors += fileTotals.errors;
|
||||
totals.warnings += fileTotals.warnings;
|
||||
totals.ignored += fileTotals.ignored;
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
for (let m of results) {
|
||||
config.lint.addIgnore(m);
|
||||
totalIgnored++;
|
||||
}
|
||||
} else {
|
||||
formatProblems(results, {
|
||||
format: argv.format,
|
||||
maxProblems: argv['max-problems'],
|
||||
totals: fileTotals,
|
||||
version
|
||||
});
|
||||
}
|
||||
|
||||
const elapsed = getExecutionTime(startedAt);
|
||||
process.stderr.write(gray(`${entryPoint}: validated in ${elapsed}\n\n`));
|
||||
} catch (e) {
|
||||
totals.errors++;
|
||||
handleError(e, entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
config.lint.saveIgnore();
|
||||
process.stderr.write(
|
||||
`Generated ignore file with ${totalIgnored} ${pluralize('problem', totalIgnored)}.\n\n`,
|
||||
);
|
||||
} else {
|
||||
printLintTotals(totals, entrypoints.length);
|
||||
}
|
||||
|
||||
printUnusedWarnings(config.lint);
|
||||
process.exit(totals.errors === 0 || argv['generate-ignore-file'] ? 0 : 1);
|
||||
|
||||
}
|
||||
@@ -1,39 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
import { extname, basename, dirname, join } from 'path';
|
||||
import { green, yellow, blue, gray } from 'colorette';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { Totals } from './types';
|
||||
import {
|
||||
BundleOutputFormat,
|
||||
validate,
|
||||
bundle,
|
||||
loadConfig,
|
||||
LintConfig,
|
||||
RedoclyClient,
|
||||
formatProblems,
|
||||
OutputFormat,
|
||||
} from "@redocly/openapi-core";
|
||||
|
||||
import {
|
||||
getFallbackEntryPointsOrExit,
|
||||
getExecutionTime,
|
||||
getTotals,
|
||||
dumpBundle,
|
||||
saveBundle,
|
||||
promptUser,
|
||||
pluralize,
|
||||
printLintTotals,
|
||||
handleError
|
||||
} from './utils';
|
||||
import { green, blue } from 'colorette';
|
||||
import { outputExtensions } from './types';
|
||||
import { RedoclyClient, OutputFormat } from "@redocly/openapi-core";
|
||||
import { promptUser } from './utils';
|
||||
import { previewDocs } from './commands/preview-docs';
|
||||
import { handleStats } from './commands/stats';
|
||||
import { handleSplit } from './commands/split';
|
||||
import { handleJoin } from './commands/join';
|
||||
import { handleLint } from './commands/lint';
|
||||
import { handleBundle } from './commands/bundle';
|
||||
const version = require('../package.json').version;
|
||||
const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
||||
|
||||
yargs
|
||||
.version('version', 'Show version number.', version)
|
||||
.help('help', 'Show help.')
|
||||
@@ -47,18 +25,19 @@ yargs
|
||||
choices: ['stylish', 'json'] as ReadonlyArray<OutputFormat>,
|
||||
default: 'stylish' as OutputFormat,
|
||||
}
|
||||
}
|
||||
),
|
||||
async (argv) => { handleStats(argv) }
|
||||
}),
|
||||
(argv) => { handleStats(argv) }
|
||||
)
|
||||
.command('split [entrypoint]', 'Split definition into a multi-file structure',
|
||||
(yargs) => yargs
|
||||
.positional('entrypoint', { type: 'string' })
|
||||
.option({ outDir: {
|
||||
.option({
|
||||
outDir: {
|
||||
description: 'Output directory where files will be saved',
|
||||
required: true,
|
||||
type: 'string'
|
||||
}}),
|
||||
}
|
||||
}),
|
||||
(argv) => { handleSplit(argv) }
|
||||
)
|
||||
.command('join [entrypoints...]', 'Join definitions [experimental]',
|
||||
@@ -88,261 +67,101 @@ yargs
|
||||
}),
|
||||
(argv) => { handleJoin(argv, version) }
|
||||
)
|
||||
.command(
|
||||
'lint [entrypoints...]',
|
||||
'Lint definition.',
|
||||
(yargs) =>
|
||||
yargs
|
||||
.positional('entrypoints', {
|
||||
array: true,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('format', {
|
||||
.command('lint [entrypoints...]', 'Lint definition.',
|
||||
(yargs) => yargs
|
||||
.positional('entrypoints', { array: true, type: 'string', demandOption: true })
|
||||
.option({
|
||||
format: {
|
||||
description: 'Use a specific output format.',
|
||||
choices: ['stylish', 'codeframe', 'json'] as ReadonlyArray<OutputFormat>,
|
||||
default: 'codeframe' as OutputFormat,
|
||||
})
|
||||
.option('max-problems', {
|
||||
},
|
||||
'max-problems': {
|
||||
requiresArg: true,
|
||||
description: 'Reduce output to max N problems.',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
})
|
||||
.option('generate-ignore-file', {
|
||||
},
|
||||
'generate-ignore-file': {
|
||||
description: 'Generate ignore file.',
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('skip-rule', {
|
||||
},
|
||||
'skip-rule': {
|
||||
description: 'Ignore certain rules.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('skip-preprocessor', {
|
||||
},
|
||||
'skip-preprocessor': {
|
||||
description: 'Ignore certain preprocessors.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('config', {
|
||||
},
|
||||
config: {
|
||||
description: 'Specify path to the config file.',
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('extends', {
|
||||
},
|
||||
extends: {
|
||||
description: 'Override extends configurations (defaults or config file settings).',
|
||||
requiresArg: true,
|
||||
array: true,
|
||||
type: 'string',
|
||||
}
|
||||
}),
|
||||
async (argv) => {
|
||||
const config = await loadConfig(argv.config, argv.extends);
|
||||
config.lint.skipRules(argv['skip-rule']);
|
||||
config.lint.skipPreprocessors(argv['skip-preprocessor']);
|
||||
|
||||
const entrypoints = await getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
config.lint.ignore = {}; // clear ignore
|
||||
}
|
||||
|
||||
const totals: Totals = { errors: 0, warnings: 0, ignored: 0 };
|
||||
let totalIgnored = 0;
|
||||
|
||||
if (config.lint.recommendedFallback) {
|
||||
process.stderr.write(
|
||||
`No configurations were defined in extends -- using built in ${blue('recommended')} configuration by default.\n\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: use shared externalRef resolver, blocked by preprocessors now as they can mutate documents
|
||||
for (const entryPoint of entrypoints) {
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
process.stderr.write(gray(`validating ${entryPoint}...\n`));
|
||||
const results = await validate({
|
||||
ref: entryPoint,
|
||||
config,
|
||||
});
|
||||
|
||||
const fileTotals = getTotals(results);
|
||||
totals.errors += fileTotals.errors;
|
||||
totals.warnings += fileTotals.warnings;
|
||||
totals.ignored += fileTotals.ignored;
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
for (let m of results) {
|
||||
config.lint.addIgnore(m);
|
||||
totalIgnored++;
|
||||
}
|
||||
} else {
|
||||
formatProblems(results, {
|
||||
format: argv.format,
|
||||
maxProblems: argv['max-problems'],
|
||||
totals: fileTotals,
|
||||
version
|
||||
});
|
||||
}
|
||||
|
||||
const elapsed = getExecutionTime(startedAt);
|
||||
process.stderr.write(gray(`${entryPoint}: validated in ${elapsed}\n\n`));
|
||||
} catch (e) {
|
||||
totals.errors++;
|
||||
handleError(e, entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
config.lint.saveIgnore();
|
||||
process.stderr.write(
|
||||
`Generated ignore file with ${totalIgnored} ${pluralize('problem', totalIgnored)}.\n\n`,
|
||||
);
|
||||
} else {
|
||||
printLintTotals(totals, entrypoints.length);
|
||||
}
|
||||
|
||||
printUnusedWarnings(config.lint);
|
||||
process.exit(totals.errors === 0 || argv['generate-ignore-file'] ? 0 : 1);
|
||||
},
|
||||
(argv) => { handleLint(argv, version) }
|
||||
)
|
||||
.command(
|
||||
'bundle [entrypoints...]',
|
||||
'Bundle definition.',
|
||||
(yargs) =>
|
||||
yargs
|
||||
.positional('entrypoints', {
|
||||
array: true,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.command('bundle [entrypoints...]', 'Bundle definition.',
|
||||
(yargs) => yargs
|
||||
.positional('entrypoints', { array: true, type: 'string', demandOption: true })
|
||||
.options({
|
||||
output: { type: 'string', alias: 'o' },
|
||||
})
|
||||
.option('format', {
|
||||
format: {
|
||||
description: 'Use a specific output format.',
|
||||
choices: ['stylish', 'codeframe', 'json'] as ReadonlyArray<OutputFormat>,
|
||||
default: 'codeframe' as OutputFormat,
|
||||
})
|
||||
.option('max-problems', {
|
||||
},
|
||||
'max-problems': {
|
||||
requiresArg: true,
|
||||
description: 'Reduce output to max N problems.',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
})
|
||||
.option('ext', {
|
||||
},
|
||||
ext: {
|
||||
description: 'Bundle file extension.',
|
||||
requiresArg: true,
|
||||
choices: outputExtensions,
|
||||
})
|
||||
.option('skip-rule', {
|
||||
},
|
||||
'skip-rule': {
|
||||
description: 'Ignore certain rules.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('skip-preprocessor', {
|
||||
},
|
||||
'skip-preprocessor': {
|
||||
description: 'Ignore certain preprocessors.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('skip-decorator', {
|
||||
},
|
||||
'skip-decorator': {
|
||||
description: 'Ignore certain decorators.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('dereferenced', {
|
||||
},
|
||||
dereferenced: {
|
||||
alias: 'd',
|
||||
type: 'boolean',
|
||||
description: 'Produce fully dereferenced bundle.',
|
||||
})
|
||||
.option('force', {
|
||||
},
|
||||
force: {
|
||||
alias: 'f',
|
||||
type: 'boolean',
|
||||
description: 'Produce bundle output even when errors occur.',
|
||||
})
|
||||
.option('config', {
|
||||
},
|
||||
config: {
|
||||
description: 'Specify path to the config file.',
|
||||
type: 'string',
|
||||
}
|
||||
}),
|
||||
async (argv) => {
|
||||
const config = await loadConfig(argv.config);
|
||||
config.lint.skipRules(argv['skip-rule']);
|
||||
config.lint.skipPreprocessors(argv['skip-preprocessor']);
|
||||
config.lint.skipDecorators(argv['skip-decorator']);
|
||||
|
||||
const entrypoints = await getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
||||
|
||||
const totals: Totals = { errors: 0, warnings: 0, ignored: 0 };
|
||||
for (const entrypoint of entrypoints) {
|
||||
try {
|
||||
const startedAt = performance.now();
|
||||
process.stderr.write(gray(`bundling ${entrypoint}...\n`));
|
||||
const { bundle: result, problems } = await bundle({
|
||||
config,
|
||||
ref: entrypoint,
|
||||
dereference: argv.dereferenced,
|
||||
});
|
||||
|
||||
const fileTotals = getTotals(problems);
|
||||
|
||||
const { outputFile, ext } = getOutputFileName(
|
||||
entrypoint,
|
||||
entrypoints.length,
|
||||
argv.output,
|
||||
argv.ext,
|
||||
);
|
||||
|
||||
if (fileTotals.errors === 0 || argv.force) {
|
||||
if (!argv.output) {
|
||||
const output = dumpBundle(result, argv.ext || 'yaml', argv.dereferenced);
|
||||
process.stdout.write(output);
|
||||
} else {
|
||||
const output = dumpBundle(result, ext, argv.dereferenced);
|
||||
saveBundle(outputFile, output);
|
||||
}
|
||||
}
|
||||
|
||||
totals.errors += fileTotals.errors;
|
||||
totals.warnings += fileTotals.warnings;
|
||||
totals.ignored += fileTotals.ignored;
|
||||
|
||||
formatProblems(problems, {
|
||||
format: argv.format,
|
||||
maxProblems: argv['max-problems'],
|
||||
totals: fileTotals,
|
||||
version,
|
||||
});
|
||||
|
||||
const elapsed = getExecutionTime(startedAt);
|
||||
if (fileTotals.errors > 0) {
|
||||
if (argv.force) {
|
||||
process.stderr.write(
|
||||
`❓ Created a bundle for ${blue(entrypoint)} at ${blue(
|
||||
outputFile,
|
||||
)} with errors ${green(elapsed)}.\n${yellow(
|
||||
'Errors ignored because of --force',
|
||||
)}.\n`,
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`❌ Errors encountered while bundling ${blue(
|
||||
entrypoint,
|
||||
)}: bundle not created (use --force to ignore errors).\n`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`📦 Created a bundle for ${blue(entrypoint)} at ${blue(outputFile)} ${green(
|
||||
elapsed,
|
||||
)}.\n`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(e, entrypoint);
|
||||
}
|
||||
}
|
||||
|
||||
printUnusedWarnings(config.lint);
|
||||
process.exit(totals.errors === 0 || argv.force ? 0 : 1);
|
||||
},
|
||||
(argv) => { handleBundle(argv, version) }
|
||||
)
|
||||
.command('login', 'Login to the Redoc.ly API registry with an access token.', async () => {
|
||||
const clientToken = await promptUser(
|
||||
@@ -359,109 +178,41 @@ yargs
|
||||
const client = new RedoclyClient();
|
||||
client.logout();
|
||||
})
|
||||
.command(
|
||||
'preview-docs [entrypoint]',
|
||||
'Preview API reference docs for the specified definition.',
|
||||
(yargs) =>
|
||||
yargs
|
||||
.positional('entrypoint', {
|
||||
type: 'string',
|
||||
})
|
||||
.option('port', {
|
||||
.command('preview-docs [entrypoint]', 'Preview API reference docs for the specified definition.',
|
||||
(yargs) => yargs
|
||||
.positional('entrypoint', { type: 'string' })
|
||||
.options({
|
||||
port: {
|
||||
alias: 'p',
|
||||
type: 'number',
|
||||
default: 8080,
|
||||
description: 'Preview port.',
|
||||
})
|
||||
.option('skip-preprocessor', {
|
||||
description: 'Preview port.'
|
||||
},
|
||||
'skip-preprocessor': {
|
||||
description: 'Ignore certain preprocessors.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('skip-decorator', {
|
||||
},
|
||||
'skip-decorator': {
|
||||
description: 'Ignore certain decorators.',
|
||||
array: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('use-community-edition', {
|
||||
},
|
||||
'use-community-edition': {
|
||||
description: 'Force using Redoc CE for docs preview.',
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('force', {
|
||||
},
|
||||
'force': {
|
||||
alias: 'f',
|
||||
type: 'boolean',
|
||||
description: 'Produce bundle output even when errors occur.',
|
||||
})
|
||||
.option('config', {
|
||||
},
|
||||
'config': {
|
||||
description: 'Specify path to the config file.',
|
||||
type: 'string',
|
||||
}
|
||||
}),
|
||||
async (argv) => {
|
||||
previewDocs(argv);
|
||||
},
|
||||
async (argv) => { previewDocs(argv) },
|
||||
)
|
||||
.demandCommand(1)
|
||||
.strict().argv;
|
||||
|
||||
function getOutputFileName(
|
||||
entrypoint: string,
|
||||
entries: number,
|
||||
output?: string,
|
||||
ext?: BundleOutputFormat,
|
||||
) {
|
||||
if (!output) {
|
||||
return { outputFile: 'stdout', ext: ext || 'yaml' };
|
||||
}
|
||||
|
||||
let outputFile = output;
|
||||
if (entries > 1) {
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(output, basename(entrypoint, extname(entrypoint))) + '.' + ext;
|
||||
} else {
|
||||
if (output) {
|
||||
ext = ext || (extname(output).substring(1) as BundleOutputFormat);
|
||||
}
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
|
||||
}
|
||||
return { outputFile, ext };
|
||||
}
|
||||
|
||||
function printUnusedWarnings(config: LintConfig) {
|
||||
const { preprocessors, rules, decorators } = config.getUnusedRules();
|
||||
if (rules.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused rules found in ${blue(config.configFile || '')}: ${rules.join(', ')}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (preprocessors.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused preprocessors found in ${blue(
|
||||
config.configFile || '',
|
||||
)}: ${preprocessors.join(', ')}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (decorators.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused decorators found in ${blue(config.configFile || '')}: ${decorators.join(
|
||||
', ',
|
||||
)}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (rules.length || preprocessors.length) {
|
||||
process.stderr.write(`Check the spelling and verify you added plugin prefix.\n`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { BundleOutputFormat } from '@redocly/openapi-core';
|
||||
|
||||
export type Totals = {
|
||||
errors: number;
|
||||
warnings: number;
|
||||
ignored: number;
|
||||
}
|
||||
|
||||
export const outputExtensions = ['json', 'yaml', 'yml'] as ReadonlyArray<BundleOutputFormat>;
|
||||
export type OutputExtensions = 'json' | 'yaml' | 'yml' | undefined;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { basename, dirname, extname, join, resolve } from 'path';
|
||||
import { blue, gray, green, red, yellow } from 'colorette';
|
||||
import { performance } from "perf_hooks";
|
||||
import * as colors from 'colorette';
|
||||
import * as glob from 'glob-promise';
|
||||
@@ -5,10 +7,15 @@ import * as yaml from 'js-yaml';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as readline from 'readline';
|
||||
import { BundleOutputFormat, Config, NormalizedProblem, ResolveError, YamlParseError } from '@redocly/openapi-core';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { Totals } from './types';
|
||||
import { blue, gray, green, red, yellow } from 'colorette';
|
||||
import {
|
||||
BundleOutputFormat,
|
||||
Config,
|
||||
LintConfig,
|
||||
NormalizedProblem,
|
||||
ResolveError,
|
||||
YamlParseError,
|
||||
} from '@redocly/openapi-core';
|
||||
import { Totals, outputExtensions } from './types';
|
||||
|
||||
export async function getFallbackEntryPointsOrExit(argsEntrypoints: string[] | undefined, config: Config) {
|
||||
const { apiDefinitions } = config;
|
||||
@@ -207,3 +214,66 @@ export function printLintTotals(totals: Totals, definitionsCount: number) {
|
||||
|
||||
process.stderr.write('\n');
|
||||
}
|
||||
|
||||
export function getOutputFileName(
|
||||
entrypoint: string,
|
||||
entries: number,
|
||||
output?: string,
|
||||
ext?: BundleOutputFormat,
|
||||
) {
|
||||
if (!output) {
|
||||
return { outputFile: 'stdout', ext: ext || 'yaml' };
|
||||
}
|
||||
|
||||
let outputFile = output;
|
||||
if (entries > 1) {
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(output, basename(entrypoint, extname(entrypoint))) + '.' + ext;
|
||||
} else {
|
||||
if (output) {
|
||||
ext = ext || (extname(output).substring(1) as BundleOutputFormat);
|
||||
}
|
||||
ext = ext || (extname(entrypoint).substring(1) as BundleOutputFormat);
|
||||
if (!outputExtensions.includes(ext as any)) {
|
||||
throw new Error(`Invalid file extension: ${ext}.`);
|
||||
}
|
||||
outputFile = join(dirname(outputFile), basename(outputFile, extname(outputFile))) + '.' + ext;
|
||||
}
|
||||
return { outputFile, ext };
|
||||
}
|
||||
|
||||
export function printUnusedWarnings(config: LintConfig) {
|
||||
const { preprocessors, rules, decorators } = config.getUnusedRules();
|
||||
if (rules.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused rules found in ${blue(config.configFile || '')}: ${rules.join(', ')}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (preprocessors.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused preprocessors found in ${blue(
|
||||
config.configFile || '',
|
||||
)}: ${preprocessors.join(', ')}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (decorators.length) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
`[WARNING] Unused decorators found in ${blue(config.configFile || '')}: ${decorators.join(
|
||||
', ',
|
||||
)}.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (rules.length || preprocessors.length) {
|
||||
process.stderr.write(`Check the spelling and verify you added plugin prefix.\n`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user