Merge pull request #12245 from backstage/rugvip/cli-reports

scripts/api-extractor: generate cli-report.md for CLI packages
This commit is contained in:
Patrik Oldsberg
2022-06-30 15:28:46 +02:00
committed by GitHub
10 changed files with 1062 additions and 26 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
The `test` command now ensures that all IO is flushed before exiting when printing `--help`.
+1
View File
@@ -5,6 +5,7 @@ coverage
*.hbs
templates
api-report.md
cli-report.md
plugins/scaffolder-backend/sample-templates
.vscode
dist-types
+563
View File
@@ -0,0 +1,563 @@
## CLI Report file for "@backstage/cli"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `backstage-cli`
```
Usage: backstage-cli [options] [command]
Options:
-V, --version
-h, --help
Commands:
create [options]
create-plugin [options]
plugin:diff [options]
test
config:docs [options]
config:print [options]
config:check [options]
config:schema [options]
repo [command]
package [command]
migrate [command]
versions:bump [options]
versions:check [options]
clean
build-workspace <workspace-dir> [packages...]
create-github-app <github-org>
info
help [command]
```
### `backstage-cli build-workspace`
```
Usage: backstage-cli build-workspace [options] <workspace-dir> [packages...]
Options:
-h, --help
```
### `backstage-cli clean`
```
Usage: backstage-cli clean [options]
Options:
-h, --help
```
### `backstage-cli config:check`
```
Usage: backstage-cli config:check [options]
Options:
--package <name>
--lax
--frontend
--deprecated
--config <path>
-h, --help
```
### `backstage-cli config:docs`
```
Usage: backstage-cli config:docs [options]
Options:
--package <name>
-h, --help
```
### `backstage-cli config:print`
```
Usage: backstage-cli config:print [options]
Options:
--package <name>
--lax
--frontend
--with-secrets
--format <format>
--config <path>
-h, --help
```
### `backstage-cli config:schema`
```
Usage: backstage-cli config:schema [options]
Options:
--package <name>
--format <format>
-h, --help
```
### `backstage-cli create`
```
Usage: backstage-cli create [options]
Options:
--select <name>
--option <name>=<value>
--scope <scope>
--npm-registry <URL>
--no-private
-h, --help
```
### `backstage-cli create-github-app`
```
Usage: backstage-cli create-github-app [options] <github-org>
Options:
-h, --help
```
### `backstage-cli create-plugin`
```
Usage: backstage-cli create-plugin [options]
Options:
--backend
--scope <scope>
--npm-registry <URL>
--no-private
-h, --help
```
### `backstage-cli info`
```
Usage: backstage-cli info [options]
Options:
-h, --help
```
### `backstage-cli migrate`
```
Usage: backstage-cli migrate [options] [command] [command]
Options:
-h, --help
Commands:
package-roles
package-scripts
package-lint-configs
help [command]
```
### `backstage-cli migrate package-lint-configs`
```
Usage: backstage-cli migrate package-lint-configs [options]
Options:
-h, --help
```
### `backstage-cli migrate package-roles`
```
Usage: backstage-cli migrate package-roles [options]
Options:
-h, --help
```
### `backstage-cli migrate package-scripts`
```
Usage: backstage-cli migrate package-scripts [options]
Options:
-h, --help
```
### `backstage-cli package`
```
Usage: backstage-cli package [options] [command] [command]
Options:
-h, --help
Commands:
start [options]
build [options]
lint [options] [directories...]
test
clean
prepack
postpack
help [command]
```
### `backstage-cli package build`
```
Usage: backstage-cli package build [options]
Options:
--role <name>
--minify
--experimental-type-build
--skip-build-dependencies
--stats
--config <path>
-h, --help
```
### `backstage-cli package clean`
```
Usage: backstage-cli package clean [options]
Options:
-h, --help
```
### `backstage-cli package lint`
```
Usage: backstage-cli package lint [options] [directories...]
Options:
--format <format>
--fix
-h, --help
```
### `backstage-cli package postpack`
```
Usage: backstage-cli package postpack [options]
Options:
-h, --help
```
### `backstage-cli package prepack`
```
Usage: backstage-cli package prepack [options]
Options:
-h, --help
```
### `backstage-cli package start`
```
Usage: backstage-cli package start [options]
Options:
--config <path>
--role <name>
--check
--inspect
--inspect-brk
-h, --help
```
### `backstage-cli package test`
```
Usage: backstage-cli [--config=<pathToConfigFile>] [TestPathPattern]
Options:
-h, --help
-v, --version
--all
--automock
-b, --bail
--cache
--cacheDirectory
--changedFilesWithAncestor
--changedSince
--ci
--clearCache
--clearMocks
--collectCoverage
--collectCoverageFrom
--collectCoverageOnlyFrom
--color
--colors
-c, --config
--coverage
--coverageDirectory
--coveragePathIgnorePatterns
--coverageProvider
--coverageReporters
--coverageThreshold
--debug
--detectLeaks
--detectOpenHandles
--env
--errorOnDeprecated
-e, --expand
--filter
--findRelatedTests
--forceExit
--globalSetup
--globalTeardown
--globals
--haste
--init
--injectGlobals
--json
--lastCommit
--listTests
--logHeapUsage
--maxConcurrency
-w, --maxWorkers
--moduleDirectories
--moduleFileExtensions
--moduleNameMapper
--modulePathIgnorePatterns
--modulePaths
--noStackTrace
--notify
--notifyMode
-o, --onlyChanged
-f, --onlyFailures
--outputFile
--passWithNoTests
--preset
--prettierPath
--projects
--reporters
--resetMocks
--resetModules
--resolver
--restoreMocks
--rootDir
--roots
-i, --runInBand
--runTestsByPath
--runner
--selectProjects
--setupFiles
--setupFilesAfterEnv
--showConfig
--silent
--skipFilter
--snapshotSerializers
--testEnvironment
--testEnvironmentOptions
--testFailureExitCode
--testLocationInResults
--testMatch
-t, --testNamePattern
--testPathIgnorePatterns
--testPathPattern
--testRegex
--testResultsProcessor
--testRunner
--testSequencer
--testTimeout
--testURL
--timers
--transform
--transformIgnorePatterns
--unmockedModulePathPatterns
-u, --updateSnapshot
--useStderr
--verbose
--watch
--watchAll
--watchPathIgnorePatterns
--watchman
```
### `backstage-cli plugin:diff`
```
Usage: backstage-cli plugin:diff [options]
Options:
--check
--yes
-h, --help
```
### `backstage-cli repo`
```
Usage: backstage-cli repo [options] [command] [command]
Options:
-h, --help
Commands:
build [options]
lint [options]
help [command]
```
### `backstage-cli repo build`
```
Usage: backstage-cli repo build [options]
Options:
--all
--since <ref>
-h, --help
```
### `backstage-cli repo lint`
```
Usage: backstage-cli repo lint [options]
Options:
--format <format>
--since <ref>
--fix
-h, --help
```
### `backstage-cli test`
```
Usage: backstage-cli [--config=<pathToConfigFile>] [TestPathPattern]
Options:
-h, --help
-v, --version
--all
--automock
-b, --bail
--cache
--cacheDirectory
--changedFilesWithAncestor
--changedSince
--ci
--clearCache
--clearMocks
--collectCoverage
--collectCoverageFrom
--collectCoverageOnlyFrom
--color
--colors
-c, --config
--coverage
--coverageDirectory
--coveragePathIgnorePatterns
--coverageProvider
--coverageReporters
--coverageThreshold
--debug
--detectLeaks
--detectOpenHandles
--env
--errorOnDeprecated
-e, --expand
--filter
--findRelatedTests
--forceExit
--globalSetup
--globalTeardown
--globals
--haste
--init
--injectGlobals
--json
--lastCommit
--listTests
--logHeapUsage
--maxConcurrency
-w, --maxWorkers
--moduleDirectories
--moduleFileExtensions
--moduleNameMapper
--modulePathIgnorePatterns
--modulePaths
--noStackTrace
--notify
--notifyMode
-o, --onlyChanged
-f, --onlyFailures
--outputFile
--passWithNoTests
--preset
--prettierPath
--projects
--reporters
--resetMocks
--resetModules
--resolver
--restoreMocks
--rootDir
--roots
-i, --runInBand
--runTestsByPath
--runner
--selectProjects
--setupFiles
--setupFilesAfterEnv
--showConfig
--silent
--skipFilter
--snapshotSerializers
--testEnvironment
--testEnvironmentOptions
--testFailureExitCode
--testLocationInResults
--testMatch
-t, --testNamePattern
--testPathIgnorePatterns
--testPathPattern
--testRegex
--testResultsProcessor
--testRunner
--testSequencer
--testTimeout
--testURL
--timers
--transform
--transformIgnorePatterns
--unmockedModulePathPatterns
-u, --updateSnapshot
--useStderr
--verbose
--watch
--watchAll
--watchPathIgnorePatterns
--watchman
```
### `backstage-cli versions:bump`
```
Usage: backstage-cli versions:bump [options]
Options:
--pattern <glob>
--release <version|next|main>
-h, --help
```
### `backstage-cli versions:check`
```
Usage: backstage-cli versions:check [options]
Options:
--fix
-h, --help
```
+4
View File
@@ -78,6 +78,10 @@ export default async (_opts: OptionValues, cmd: Command) => {
process.env.TZ = 'UTC';
}
if (args.includes('--help')) {
(process.stdout as any)._handle.setBlocking(true);
}
// eslint-disable-next-line jest/no-jest-import
await require('jest').run(args);
};
+61
View File
@@ -0,0 +1,61 @@
## CLI Report file for "@backstage/codemods"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `backstage-codemods`
```
Usage: backstage-codemods [options] [command]
Options:
-V, --version
-h, --help
Commands:
apply <codemod> [target-dirs...]
list
help [command]
```
### `backstage-codemods apply`
```
Usage: backstage-codemods apply [options] [command] <codemod> [target-dirs...]
Options:
-h, --help
Commands:
core-imports [options] [target-dirs...]
extension-names [options] [target-dirs...]
help [command]
```
### `backstage-codemods apply core-imports`
```
Usage: backstage-codemods apply core-imports [options] [target-dirs...]
Options:
-d, --dry
-h, --help
```
### `backstage-codemods apply extension-names`
```
Usage: backstage-codemods apply extension-names [options] [target-dirs...]
Options:
-d, --dry
-h, --help
```
### `backstage-codemods list`
```
Usage: backstage-codemods list [options]
Options:
-h, --help
```
+15
View File
@@ -0,0 +1,15 @@
## CLI Report file for "@backstage/create-app"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `backstage-create-app`
```
Usage: backstage-create-app [options]
Options:
-V, --version
--path [directory]
--skip-install
-h, --help
```
+26
View File
@@ -0,0 +1,26 @@
## CLI Report file for "e2e-test"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `e2e-test`
```
Usage: e2e-test [options] [command]
Options:
-V, --version
-h, --help
Commands:
run
help [command]
```
### `e2e-test run`
```
Usage: e2e-test run [options]
Options:
-h, --help
```
+117
View File
@@ -0,0 +1,117 @@
## CLI Report file for "@techdocs/cli"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `techdocs-cli`
```
Usage: techdocs-cli [options] [command]
Options:
-V, --version
-h, --help
Commands:
generate|build [options]
migrate [options]
publish [options]
serve:mkdocs [options]
serve [options]
help [command]
```
### `techdocs-cli generate`
```
Usage: techdocs-cli generate|build [options]
Options:
--source-dir <PATH>
--output-dir <PATH>
--docker-image <DOCKER_IMAGE>
--no-pull
--no-docker
--techdocs-ref <HOST_TYPE:URL>
--etag <ETAG>
-v --verbose
--omitTechdocsCoreMkdocsPlugin
--legacyCopyReadmeMdToIndexMd
-h, --help
```
### `techdocs-cli migrate`
```
Usage: techdocs-cli migrate [options]
Options:
--publisher-type <TYPE>
--storage-name <BUCKET/CONTAINER NAME>
--azureAccountName <AZURE ACCOUNT NAME>
--azureAccountKey <AZURE ACCOUNT KEY>
--awsRoleArn <AWS ROLE ARN>
--awsEndpoint <AWS ENDPOINT>
--awsS3ForcePathStyle
--awsBucketRootPath
--osCredentialId <OPENSTACK SWIFT APPLICATION CREDENTIAL ID>
--osSecret <OPENSTACK SWIFT APPLICATION CREDENTIAL SECRET>
--osAuthUrl <OPENSTACK SWIFT AUTHURL>
--osSwiftUrl <OPENSTACK SWIFT SWIFTURL>
--removeOriginal
--concurrency <MAX CONCURRENT REQS>
-v --verbose
-h, --help
```
### `techdocs-cli publish`
```
Usage: techdocs-cli publish [options]
Options:
--publisher-type <TYPE>
--storage-name <BUCKET/CONTAINER NAME>
--entity <NAMESPACE/KIND/NAME>
--legacyUseCaseSensitiveTripletPaths
--azureAccountName <AZURE ACCOUNT NAME>
--azureAccountKey <AZURE ACCOUNT KEY>
--awsRoleArn <AWS ROLE ARN>
--awsEndpoint <AWS ENDPOINT>
--awsS3sse <AWS SSE>
--awsS3ForcePathStyle
--osCredentialId <OPENSTACK SWIFT APPLICATION CREDENTIAL ID>
--osSecret <OPENSTACK SWIFT APPLICATION CREDENTIAL SECRET>
--osAuthUrl <OPENSTACK SWIFT AUTHURL>
--osSwiftUrl <OPENSTACK SWIFT SWIFTURL>
--gcsBucketRootPath
--directory <PATH>
-h, --help
```
### `techdocs-cli serve`
```
Usage: techdocs-cli serve [options]
Options:
-i, --docker-image <DOCKER_IMAGE>
--docker-entrypoint <DOCKER_ENTRYPOINT>
--no-docker
--mkdocs-port <PORT>
-v --verbose
-h, --help
```
### `techdocs-cli serve:mkdocs`
```
Usage: techdocs-cli serve:mkdocs [options]
Options:
-i, --docker-image <DOCKER_IMAGE>
--docker-entrypoint <DOCKER_ENTRYPOINT>
--no-docker
-p, --port <PORT>
-v --verbose
-h, --help
```
+269 -25
View File
@@ -23,7 +23,7 @@ import {
dirname,
join,
} from 'path';
import { spawnSync } from 'child_process';
import { spawnSync, execFile } from 'child_process';
import prettier from 'prettier';
import fs from 'fs-extra';
import {
@@ -197,18 +197,6 @@ ApiReportGenerator.generateReviewFileContent =
const PACKAGE_ROOTS = ['packages', 'plugins'];
const SKIPPED_PACKAGES = [
join('packages', 'app'),
join('packages', 'backend'),
join('packages', 'cli'),
join('packages', 'codemods'),
join('packages', 'create-app'),
join('packages', 'e2e-test'),
join('packages', 'techdocs-cli-embedded-app'),
join('packages', 'storybook'),
join('packages', 'techdocs-cli'),
];
const ALLOW_WARNINGS = [
'packages/core-components',
'plugins/allure',
@@ -303,9 +291,6 @@ async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
if (!packageDir) {
throw new Error(`'${unresolvedPackageDir}' is not a valid package path`);
}
if (SKIPPED_PACKAGES.includes(packageDir)) {
throw new Error(`'${packageDir}' does not have an API report`);
}
packageDirs.push(packageDir);
}
@@ -328,9 +313,7 @@ async function findPackageDirs() {
continue;
}
if (!SKIPPED_PACKAGES.includes(packageDir)) {
packageDirs.push(packageDir);
}
packageDirs.push(packageDir);
}
}
@@ -1065,6 +1048,252 @@ async function buildDocs({
documenter.generateFiles();
}
async function categorizePackageDirs(projectRoot, packageDirs) {
const dirs = packageDirs.slice();
const tsPackageDirs = new Array<string>();
const cliPackageDirs = new Array<string>();
await Promise.all(
Array(10)
.fill(0)
.map(async () => {
for (;;) {
const dir = dirs.pop();
if (!dir) {
return;
}
const pkgJson = await fs
.readJson(resolvePath(projectRoot, dir, 'package.json'))
.catch(error => {
if (error.code === 'ENOENT') {
return undefined;
}
throw error;
});
const role = pkgJson?.backstage?.role;
if (!role) {
throw new Error(`No backstage.role in ${dir}/package.json`);
}
if (role === 'cli') {
cliPackageDirs.push(dir);
} else if (role !== 'frontend' && role !== 'backend') {
tsPackageDirs.push(dir);
}
}
}),
);
return { tsPackageDirs, cliPackageDirs };
}
function createBinRunner(cwd: string, path: string) {
return async (...command: string[]) =>
new Promise<string>((resolve, reject) => {
execFile(
'node',
[path, ...command],
{
cwd,
shell: true,
timeout: 30000,
maxBuffer: 1024 * 1024,
},
(err, stdout, stderr) => {
if (err) {
reject(new Error(`${err.message}\n${stderr}`));
} else if (stderr) {
reject(new Error(`Command printed error output: ${stderr}`));
} else {
resolve(stdout);
}
},
);
});
}
function parseHelpPage(helpPageContent: string) {
const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? [];
const lines = helpPageContent.split(/\r?\n/);
let options = new Array<string>();
let commands = new Array<string>();
while (lines.length > 0) {
while (lines.length > 0 && !lines[0].endsWith(':')) {
lines.shift();
}
if (lines.length > 0) {
// Start of a new section, e.g. "Options:"
const sectionName = lines.shift();
// Take lines until we hit the next section or the end
const sectionEndIndex = lines.findIndex(
line => line && !line.match(/^\s/),
);
const sectionLines = lines.slice(0, sectionEndIndex);
lines.splice(0, sectionLines.length);
// Trim away documentation
const sectionItems = sectionLines
.map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
.filter(Boolean);
if (sectionName.toLocaleLowerCase('en-US') === 'options:') {
options = sectionItems;
} else if (sectionName.toLocaleLowerCase('en-US') === 'commands:') {
commands = sectionItems;
} else {
throw new Error(`Unknown CLI section: ${sectionName}`);
}
}
}
return {
usage,
options,
commands,
};
}
// Represents the help page os a CLI command
interface CliHelpPage {
// Path of commands to reach this page
path: string[];
// Parsed content
usage: string | undefined;
options: string[];
commands: string[];
}
async function exploreCliHelpPages(
run: (...args: string[]) => Promise<string>,
): Promise<CliHelpPage[]> {
const helpPages = new Array<CliHelpPage>();
async function exploreHelpPage(...path: string[]) {
const content = await run(...path, '--help');
const parsed = parseHelpPage(content);
helpPages.push({ path, ...parsed });
await Promise.all(
parsed.commands.map(async fullCommand => {
const command = fullCommand.split(/[|\s]/)[0];
if (command !== 'help') {
await exploreHelpPage(...path, command);
}
}),
);
}
await exploreHelpPage();
helpPages.sort((a, b) => a.path.join(' ').localeCompare(b.path.join(' ')));
return helpPages;
}
// The API model for a CLI entry point
interface CliModel {
name: string;
helpPages: CliHelpPage[];
}
function generateCliReport(name: string, models: CliModel[]): string {
const content = [
`## CLI Report file for "${name}"`,
'',
'> Do not edit this file. It is a report generated by `yarn build:api-reports`',
'',
];
for (const model of models) {
for (const helpPage of model.helpPages) {
content.push(
`### \`${[model.name, ...helpPage.path].join(' ')}\``,
'',
'```',
`Usage: ${helpPage.usage ?? '<none>'}`,
);
if (helpPage.options.length > 0) {
content.push('', 'Options:', ...helpPage.options.map(l => ` ${l}`));
}
if (helpPage.commands.length > 0) {
content.push('', 'Commands:', ...helpPage.commands.map(l => ` ${l}`));
}
content.push('```', '');
}
}
return content.join('\n');
}
interface CliExtractionOptions {
projectRoot: string;
packageDirs: string[];
isLocalBuild: boolean;
}
async function runCliExtraction({
projectRoot,
packageDirs,
isLocalBuild,
}: CliExtractionOptions) {
for (const packageDir of packageDirs) {
console.log(`## Processing ${packageDir}`);
const fullDir = resolvePath(projectRoot, packageDir);
const pkgJson = await fs.readJson(resolvePath(fullDir, 'package.json'));
if (!pkgJson.bin) {
throw new Error(`CLI Package in ${packageDir} has no bin field`);
}
const models = new Array<CliModel>();
for (const [name, path] of Object.entries<string>(pkgJson.bin)) {
const run = createBinRunner(fullDir, path);
const helpPages = await exploreCliHelpPages(run);
models.push({ name, helpPages });
}
const report = generateCliReport(pkgJson.name, models);
const reportPath = resolvePath(fullDir, 'cli-report.md');
const existingReport = await fs
.readFile(reportPath, 'utf8')
.catch(error => {
if (error.code === 'ENOENT') {
return undefined;
}
throw error;
});
if (existingReport !== report) {
if (isLocalBuild) {
console.warn(`CLI report changed for ${packageDir}`);
await fs.writeFile(reportPath, report);
} else {
logApiReportInstructions();
if (existingReport) {
console.log('');
console.log(
`The conflicting file is ${relativePath(
projectRoot,
reportPath,
)}, expecting the following content:`,
);
console.log('');
console.log(report);
logApiReportInstructions();
}
throw new Error(`CLI report changed for ${packageDir}, `);
}
}
}
}
async function main() {
const projectRoot = resolvePath(__dirname, '..');
const isCiBuild = process.argv.includes('--ci');
@@ -1121,13 +1350,28 @@ async function main() {
const packageDirs = selectedPackageDirs ?? (await findPackageDirs());
console.log('# Generating package API reports');
await runApiExtraction({
const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
projectRoot,
packageDirs,
outputDir: tmpDir,
isLocalBuild: !isCiBuild,
tsconfigFilePath,
});
);
if (tsPackageDirs.length > 0) {
console.log('# Generating package API reports');
await runApiExtraction({
packageDirs: tsPackageDirs,
outputDir: tmpDir,
isLocalBuild: !isCiBuild,
tsconfigFilePath,
});
}
if (cliPackageDirs.length > 0) {
console.log('# Generating package CLI reports');
await runCliExtraction({
projectRoot,
packageDirs: cliPackageDirs,
isLocalBuild: !isCiBuild,
});
}
if (isDocsBuild) {
console.log('# Generating package documentation');
+1 -1
View File
@@ -42,7 +42,7 @@ function isPublishedPath(path) {
return false;
}
// API report changes by themselves don't count
if (path === 'api-report.md') {
if (path === 'api-report.md' || path === 'cli-report.md') {
return false;
}
// Lint changes don't count