Fix CLI report generation and --help handling for loader-based commands

Add cleye-based --help handling to all commands using the loader pattern.
Update the CLI report parser to support cleye's USAGE: and FLAGS: sections.
Revert accidental backstage.role addition to eslint-plugin.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-26 20:31:55 +01:00
parent 0d2d0f2e07
commit d5779e525c
12 changed files with 106 additions and 76 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Updated CLI report parser to support cleye-style help output sections (`USAGE:` and `FLAGS:`).
+42 -51
View File
@@ -57,81 +57,75 @@ Commands:
### `backstage-cli config docs`
```
Usage: backstage-cli config docs [options]
Usage: backstage-cli config docs
Options:
--package <name>
--package <string>
-h, --help
```
### `backstage-cli config schema`
```
Usage: <none>
Usage: backstage-cli config schema
Options:
--format
--help
--format <string>
--merge
--no-merge
--package
--version
--package <string>
-h, --help
```
### `backstage-cli config:check`
```
Usage: <none>
Usage: backstage-cli config:check
Options:
--config
--config <string>
--deprecated
--frontend
--help
--lax
--package
--package <string>
--strict
--version
-h, --help
```
### `backstage-cli config:docs`
```
Usage: program [options]
Usage: backstage-cli config:docs
Options:
--package <name>
--package <string>
-h, --help
```
### `backstage-cli config:print`
```
Usage: <none>
Usage: backstage-cli config:print
Options:
--config
--format
--config <string>
--format <string>
--frontend
--help
--lax
--package
--version
--package <string>
--with-secrets
-h, --help
```
### `backstage-cli config:schema`
```
Usage: <none>
Usage: backstage-cli config:schema
Options:
--format
--help
--format <string>
--merge
--no-merge
--package
--version
--package <string>
-h, --help
```
### `backstage-cli create-github-app`
@@ -146,13 +140,12 @@ Options:
### `backstage-cli info`
```
Usage: <none>
Usage: backstage-cli info
Options:
--format
--help
--include
--version
--format <string>
--include <string>
-h, --help
```
### `backstage-cli migrate`
@@ -175,7 +168,7 @@ Commands:
### `backstage-cli migrate package-exports`
```
Usage: program [options]
Usage: backstage-cli migrate package-exports
Options:
-h, --help
@@ -184,7 +177,7 @@ Options:
### `backstage-cli migrate package-lint-configs`
```
Usage: program [options]
Usage: backstage-cli migrate package-lint-configs
Options:
-h, --help
@@ -193,7 +186,7 @@ Options:
### `backstage-cli migrate package-roles`
```
Usage: program [options]
Usage: backstage-cli migrate package-roles
Options:
-h, --help
@@ -202,7 +195,7 @@ Options:
### `backstage-cli migrate package-scripts`
```
Usage: program [options]
Usage: backstage-cli migrate package-scripts
Options:
-h, --help
@@ -211,7 +204,7 @@ Options:
### `backstage-cli migrate react-router-deps`
```
Usage: program [options]
Usage: backstage-cli migrate react-router-deps
Options:
-h, --help
@@ -271,7 +264,7 @@ Options:
### `backstage-cli package clean`
```
Usage: program [options]
Usage: backstage-cli package clean
Options:
-h, --help
@@ -293,7 +286,7 @@ Options:
### `backstage-cli package postpack`
```
Usage: program [options]
Usage: backstage-cli package postpack
Options:
-h, --help
@@ -302,7 +295,7 @@ Options:
### `backstage-cli package prepack`
```
Usage: program [options]
Usage: backstage-cli package prepack
Options:
-h, --help
@@ -472,7 +465,7 @@ Options:
### `backstage-cli repo clean`
```
Usage: program [options]
Usage: backstage-cli repo clean
Options:
-h, --help
@@ -560,25 +553,23 @@ Commands:
### `backstage-cli translations export`
```
Usage: <none>
Usage: backstage-cli translations export
Options:
--help
--output
--pattern
--version
--output <string>
--pattern <string>
-h, --help
```
### `backstage-cli translations import`
```
Usage: <none>
Usage: backstage-cli translations import
Options:
--help
--input
--output
--version
--input <string>
--output <string>
-h, --help
```
### `backstage-cli versions:bump`
@@ -14,11 +14,14 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { targetPaths } from '@backstage/cli-common';
import type { CommandContext } from '../../../../wiring/types';
export default async function clean() {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
await fs.remove(targetPaths.resolve('dist'));
await fs.remove(targetPaths.resolve('dist-types'));
await fs.remove(targetPaths.resolve('coverage'));
}
};
@@ -14,13 +14,15 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { PackageGraph } from '@backstage/cli-node';
import { run, targetPaths } from '@backstage/cli-common';
import type { CommandContext } from '../../../../wiring/types';
export default async function command(): Promise<void> {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
const packages = await PackageGraph.listTargetPackages();
await fs.remove(targetPaths.resolveRoot('dist'));
@@ -48,4 +50,4 @@ export default async function command(): Promise<void> {
}
}),
);
}
};
+5 -2
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import { Command, Option } from 'commander';
import { createCliPlugin } from '../../wiring/factory';
import { lazy } from '../../wiring/lazy';
@@ -214,7 +215,8 @@ export const buildPlugin = createCliPlugin({
reg.addCommand({
path: ['package', 'prepack'],
description: 'Prepares a package for packaging before publishing',
execute: async () => {
execute: async ({ args, info }) => {
cli({ help: info }, undefined, args);
const { pre } = await import('./commands/package/pack');
await pre();
},
@@ -223,7 +225,8 @@ export const buildPlugin = createCliPlugin({
reg.addCommand({
path: ['package', 'postpack'],
description: 'Restores the changes made by the prepack command',
execute: async () => {
execute: async ({ args, info }) => {
cli({ help: info }, undefined, args);
const { post } = await import('./commands/package/pack');
await post();
},
@@ -14,8 +14,12 @@
* limitations under the License.
*/
export default async function command() {
import { cli } from 'cleye';
import type { CommandContext } from '../../../wiring/types';
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
throw new Error(
'The `migrate package-exports` command has been removed, use `repo fix` instead.',
);
}
};
@@ -14,14 +14,17 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { PackageGraph } from '@backstage/cli-node';
import { runOutput } from '@backstage/cli-common';
import type { CommandContext } from '../../../wiring/types';
const PREFIX = `module.exports = require('@backstage/cli/config/eslint-factory')`;
export default async function command() {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
const packages = await PackageGraph.listTargetPackages();
const oldConfigs = [
@@ -86,4 +89,4 @@ export default async function command() {
if (hasPrettier) {
await runOutput(['prettier', '--write', ...configPaths]);
}
}
};
@@ -14,13 +14,16 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { getPackages } from '@manypkg/get-packages';
import { PackageRoles } from '@backstage/cli-node';
import { targetPaths } from '@backstage/cli-common';
import type { CommandContext } from '../../../wiring/types';
export default async () => {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
const { packages } = await getPackages(targetPaths.dir);
await Promise.all(
@@ -14,15 +14,18 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { PackageGraph, PackageRoles, PackageRole } from '@backstage/cli-node';
import type { CommandContext } from '../../../wiring/types';
const configArgPattern = /--config[=\s][^\s$]+/;
const noStartRoles: PackageRole[] = ['cli', 'common-library'];
export default async function command() {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
const packages = await PackageGraph.listTargetPackages();
await Promise.all(
@@ -104,4 +107,4 @@ export default async function command() {
}
}),
);
}
};
@@ -14,14 +14,17 @@
* limitations under the License.
*/
import { cli } from 'cleye';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { PackageGraph, PackageRoles } from '@backstage/cli-node';
import type { CommandContext } from '../../../wiring/types';
const REACT_ROUTER_DEPS = ['react-router', 'react-router-dom'];
const REACT_ROUTER_RANGE = '6.0.0-beta.0 || ^6.3.0';
export default async function command() {
export default async ({ args, info }: CommandContext) => {
cli({ help: info }, undefined, args);
const packages = await PackageGraph.listTargetPackages();
await Promise.all(
@@ -56,4 +59,4 @@ export default async function command() {
}
}),
);
}
};
-3
View File
@@ -5,9 +5,6 @@
"publishConfig": {
"access": "public"
},
"backstage": {
"role": "cli"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
@@ -27,7 +27,14 @@ import { generateCliReport } from './generateCliReport';
import { logApiReportInstructions } from '../common';
function parseHelpPage(helpPageContent: string) {
const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? [];
let usage: string | undefined;
// Commander format: "Usage: backstage-cli ..."
const commanderUsage = helpPageContent.match(/^\s*Usage: (.*)$/im);
if (commanderUsage) {
usage = commanderUsage[1];
}
const lines = helpPageContent.split(/\r?\n/);
let options = new Array<string>();
@@ -39,8 +46,8 @@ function parseHelpPage(helpPageContent: string) {
lines.shift();
}
if (lines.length > 0) {
// Start of a new section, e.g. "Options:"
const sectionName = lines.shift();
// Start of a new section, e.g. "Options:" or "FLAGS:"
const sectionName = lines.shift()?.toLocaleLowerCase('en-US');
// Take lines until we hit the next section or the end
const sectionEndIndex = lines.findIndex(
line => line && !line.match(/^\s/),
@@ -53,12 +60,18 @@ function parseHelpPage(helpPageContent: string) {
.map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
.filter(Boolean) as string[];
if (sectionName?.toLocaleLowerCase('en-US') === 'options:') {
if (sectionName === 'options:' || sectionName === 'flags:') {
options = sectionItems;
} else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') {
} else if (sectionName === 'commands:') {
commands = sectionItems;
} else if (sectionName?.toLocaleLowerCase('en-US') === 'arguments:') {
} else if (sectionName === 'arguments:') {
commandArguments = sectionItems;
} else if (sectionName === 'usage:') {
// cleye format: usage line is inside the USAGE: section
const usageLine = sectionLines.find(l => l.trim().length > 0)?.trim();
if (usageLine) {
usage = usageLine;
}
} else {
throw new Error(`Unknown CLI section: ${sectionName}`);
}