Add documentation to cli-plugin-api and remove CliFeature type

Add thorough TSDoc comments to createCliPlugin, BackstageCommand,
CommandContext, and CliPlugin. Remove the CliFeature type alias in
favor of using CliPlugin directly.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-11 13:29:31 +01:00
parent 7e6ad01a21
commit 28a438a9f2
6 changed files with 95 additions and 32 deletions
+1 -8
View File
@@ -5,11 +5,8 @@
```ts
// @public
export interface BackstageCommand {
// (undocumented)
deprecated?: boolean;
// (undocumented)
description: string;
// (undocumented)
execute:
| ((context: CommandContext) => Promise<void>)
| {
@@ -17,9 +14,7 @@ export interface BackstageCommand {
default: (context: CommandContext) => Promise<void>;
}>;
};
// (undocumented)
experimental?: boolean;
// (undocumented)
path: string[];
}
@@ -34,12 +29,10 @@ export interface CliPlugin {
// @public
export interface CommandContext {
// (undocumented)
args: string[];
// (undocumented)
info: {
usage: string;
description: string;
name: string;
};
}
@@ -20,11 +20,38 @@ import { BackstageCommand, CliPlugin } from './types';
/**
* Creates a new CLI plugin that provides commands to the Backstage CLI.
*
* The `init` callback is invoked immediately at creation time and is used
* to register commands via the provided registry. The commands are then
* made available to the CLI host once the returned promise resolves.
*
* @example
* ```
* import { createCliPlugin } from '@backstage/cli-plugin-api';
* import packageJson from '../package.json';
*
* export default createCliPlugin({
* packageJson,
* init: async reg => {
* reg.addCommand({
* path: ['repo', 'test'],
* description: 'Run tests across the repository',
* execute: { loader: () => import('./commands/test') },
* });
* },
* });
* ```
*
* @public
*/
export function createCliPlugin(options: {
/** The `package.json` contents of the plugin package, used to identify the plugin. */
packageJson: { name: string };
/**
* An initialization callback that registers commands with the CLI.
* Called immediately when the plugin is created.
*/
init: (registry: {
/** Registers a new command with the CLI. */
addCommand: (command: BackstageCommand) => void;
}) => Promise<void>;
}): CliPlugin {
+1 -6
View File
@@ -15,9 +15,4 @@
*/
export { createCliPlugin } from './createCliPlugin';
export type {
BackstageCommand,
CommandContext,
CliPlugin,
CliFeature,
} from './types';
export type { BackstageCommand, CommandContext, CliPlugin } from './types';
+60 -11
View File
@@ -15,19 +15,33 @@
*/
/**
* The context provided to a CLI command when it is executed.
* The context provided to a CLI command at the time of execution.
*
* Contains the parsed arguments and metadata about the command being run.
*
* @public
*/
export interface CommandContext {
/**
* The remaining arguments passed to the command after the command path
* has been resolved. This includes both positional arguments and flags.
*
* For example, running `backstage-cli repo test --verbose src/` would
* result in `args` being `['--verbose', 'src/']`.
*/
args: string[];
/**
* Metadata about the command being executed.
*/
info: {
/**
* The usage string of the current command, for example: "backstage-cli repo test"
* The full usage string of the command including the program name,
* for example `"backstage-cli repo test"`.
*/
usage: string;
/**
* The name of the command, for example: "repo test"
* The name of the command as defined by its path,
* for example `"repo test"`.
*/
name: string;
};
@@ -36,13 +50,54 @@ export interface CommandContext {
/**
* A command definition for a Backstage CLI plugin.
*
* Each command is identified by a `path` that determines its position in
* the command tree. For example, a path of `['repo', 'test']` registers
* the command as `backstage-cli repo test`.
*
* Commands can either provide an `execute` function directly, or use a
* `loader` for deferred loading of the implementation. The loader pattern
* is recommended for commands with heavy dependencies, as it avoids
* loading the implementation until the command is actually invoked.
*
* @public
*/
export interface BackstageCommand {
/**
* The path segments that define the command's position in the CLI tree.
* For example, `['repo', 'test']` maps to `backstage-cli repo test`.
*/
path: string[];
/**
* A short description of the command, displayed in help output.
*/
description: string;
/**
* If `true`, the command is deprecated and will be hidden from help output
* but can still be invoked.
*/
deprecated?: boolean;
/**
* If `true`, the command is experimental and will be hidden from help
* output but can still be invoked.
*/
experimental?: boolean;
/**
* The command implementation, either as a direct function or as a loader
* that returns the implementation as a default export. The loader form
* is useful for deferring heavy imports until the command is invoked.
*
* @example
* Direct execution:
* ```
* execute: async ({ args }) => { ... }
* ```
*
* @example
* Deferred loading:
* ```
* execute: { loader: () => import('./my-command') }
* ```
*/
execute:
| ((context: CommandContext) => Promise<void>)
| {
@@ -53,14 +108,8 @@ export interface BackstageCommand {
}
/**
* A CLI feature, currently always a CLI plugin.
*
* @public
*/
export type CliFeature = CliPlugin;
/**
* A Backstage CLI plugin.
* An opaque representation of a Backstage CLI plugin, created
* using {@link createCliPlugin}.
*
* @public
*/
+6 -6
View File
@@ -16,7 +16,7 @@
import { CommandGraph } from './CommandGraph';
import { OpaqueCliPlugin } from '@internal/cli';
import type { BackstageCommand, CliFeature } from '@backstage/cli-plugin-api';
import type { BackstageCommand, CliPlugin } from '@backstage/cli-plugin-api';
import { CommandRegistry } from './CommandRegistry';
import { Command } from 'commander';
import { version } from './version';
@@ -36,12 +36,12 @@ function isNodeHidden(
return node.children.every(child => isNodeHidden(child as any));
}
type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>;
type UninitializedFeature = CliPlugin | Promise<{ default: CliPlugin }>;
export class CliInitializer {
private graph = new CommandGraph();
private commandRegistry = new CommandRegistry(this.graph);
#uninitiazedFeatures: Promise<CliFeature>[] = [];
#uninitiazedFeatures: Promise<CliPlugin>[] = [];
add(feature: UninitializedFeature) {
if (isPromise(feature)) {
@@ -53,7 +53,7 @@ export class CliInitializer {
}
}
async #register(feature: CliFeature) {
async #register(feature: CliPlugin) {
if (OpaqueCliPlugin.isType(feature)) {
const internal = OpaqueCliPlugin.toInternal(feature);
for (const command of await internal.commands) {
@@ -180,8 +180,8 @@ export class CliInitializer {
/** @internal */
export function unwrapFeature(
feature: CliFeature | { default: CliFeature },
): CliFeature {
feature: CliPlugin | { default: CliPlugin },
): CliPlugin {
if ('$$type' in feature) {
return feature;
}
-1
View File
@@ -17,6 +17,5 @@
export type {
CommandContext,
BackstageCommand,
CliFeature,
CliPlugin,
} from '@backstage/cli-plugin-api';