Add @internal/cli package for opaque CLI types

Move OpaqueCliPlugin to a new @internal/cli inline package, following the
same pattern as @internal/frontend. Both cli-plugin-api and cli import it
directly, and it gets bundled into their dists at build time.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-11 08:18:55 +01:00
parent d6caf7f13b
commit 2e5f189cfa
12 changed files with 107 additions and 146 deletions
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+9
View File
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: internal-cli
title: '@internal/cli'
spec:
lifecycle: experimental
type: backstage-node-library
owner: tooling-maintainers
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@internal/cli",
"version": "0.0.1",
"backstage": {
"role": "node-library",
"inline": true
},
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/cli-internal"
},
"license": "Apache-2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"lint": "backstage-cli package lint",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/cli-plugin-api": "workspace:^"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BackstageCommand, CliPlugin } from '@backstage/cli-plugin-api';
import { OpaqueType } from '@internal/opaque';
export const OpaqueCliPlugin = OpaqueType.create<{
public: CliPlugin;
versions: {
readonly version: 'v1';
readonly description: string;
init: (registry: {
addCommand: (command: BackstageCommand) => void;
}) => Promise<void>;
};
}>({
type: '@backstage/CliPlugin',
versions: ['v1'],
});
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { OpaqueCliPlugin } from './InternalCliPlugin';
+3 -16
View File
@@ -6,7 +6,9 @@
"role": "cli-plugin"
},
"publishConfig": {
"access": "public"
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
@@ -15,23 +17,8 @@
"directory": "packages/cli-plugin-api"
},
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./internals": "./src/internals.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"internals": [
"src/internals.ts"
],
"package.json": [
"package.json"
]
}
},
"files": [
"dist"
],
@@ -1,60 +0,0 @@
## API Report File for "@backstage/cli-plugin-api"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
// @public
export interface BackstageCommand {
// (undocumented)
deprecated?: boolean;
// (undocumented)
description: string;
// (undocumented)
execute:
| CommandExecuteFn
| {
loader: () => Promise<{
default: CommandExecuteFn;
}>;
};
// (undocumented)
experimental?: boolean;
// (undocumented)
path: string[];
}
// @public
export interface CliPlugin {
// (undocumented)
readonly $$type: '@backstage/CliPlugin';
// (undocumented)
readonly pluginId: string;
}
// @public
export interface CommandContext {
// (undocumented)
args: string[];
// (undocumented)
info: {
usage: string;
description: string;
};
}
// @public
export type CommandExecuteFn = (context: CommandContext) => Promise<void>;
// @public
export function initializeCliPlugin(
plugin: CliPlugin,
registry: {
addCommand: (command: BackstageCommand) => void;
},
): Promise<void>;
// @public
export function isCliPlugin(value: unknown): value is CliPlugin;
// (No @packageDocumentation comment for this package)
```
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { OpaqueCliPlugin } from '@internal/cli';
import { describeParentCallSite } from './describeParentCallSite';
import { BackstageCommand, CliPlugin, OpaqueCliPlugin } from './types';
import { BackstageCommand, CliPlugin } from './types';
/**
* Creates a new CLI plugin that provides commands to the Backstage CLI.
-47
View File
@@ -1,47 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BackstageCommand, CliPlugin, OpaqueCliPlugin } from './types';
export type {
BackstageCommand,
CliPlugin,
CommandContext,
CommandExecuteFn,
} from './types';
/**
* Checks whether a value is a {@link CliPlugin}.
*
* @public
*/
export function isCliPlugin(value: unknown): value is CliPlugin {
return OpaqueCliPlugin.isType(value);
}
/**
* Initializes a CLI plugin by calling its init function with the provided
* command registry.
*
* @public
*/
export async function initializeCliPlugin(
plugin: CliPlugin,
registry: { addCommand: (command: BackstageCommand) => void },
): Promise<void> {
const internal = OpaqueCliPlugin.toInternal(plugin);
await internal.init(registry);
}
-16
View File
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OpaqueType } from '@internal/opaque';
/**
* The context provided to a CLI command when it is executed.
@@ -74,18 +73,3 @@ export interface CliPlugin {
readonly pluginId: string;
readonly $$type: '@backstage/CliPlugin';
}
/** @internal */
export const OpaqueCliPlugin = OpaqueType.create<{
public: CliPlugin;
versions: {
readonly version: 'v1';
readonly description: string;
init: (registry: {
addCommand: (command: BackstageCommand) => void;
}) => Promise<void>;
};
}>({
type: '@backstage/CliPlugin',
versions: ['v1'],
});
+4 -6
View File
@@ -15,10 +15,7 @@
*/
import { CommandGraph } from './CommandGraph';
import {
isCliPlugin,
initializeCliPlugin,
} from '@backstage/cli-plugin-api/internals';
import { OpaqueCliPlugin } from '@internal/cli';
import type { BackstageCommand, CliFeature } from '@backstage/cli-plugin-api';
import { CommandRegistry } from './CommandRegistry';
import { Command } from 'commander';
@@ -57,8 +54,9 @@ export class CliInitializer {
}
async #register(feature: CliFeature) {
if (isCliPlugin(feature)) {
await initializeCliPlugin(feature, this.commandRegistry);
if (OpaqueCliPlugin.isType(feature)) {
const internal = OpaqueCliPlugin.toInternal(feature);
await internal.init(this.commandRegistry);
} else {
throw new Error(`Unsupported feature type: ${(feature as any).$$type}`);
}
+9
View File
@@ -9680,6 +9680,15 @@ __metadata:
languageName: node
linkType: hard
"@internal/cli@workspace:packages/cli-internal":
version: 0.0.0-use.local
resolution: "@internal/cli@workspace:packages/cli-internal"
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/cli-plugin-api": "workspace:^"
languageName: unknown
linkType: soft
"@internal/frontend@workspace:packages/frontend-internal":
version: 0.0.0-use.local
resolution: "@internal/frontend@workspace:packages/frontend-internal"