cli: refactor alpha to use common helper for opaque types

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-04-27 11:39:40 +02:00
parent c4fd744f42
commit 9aaec544b8
5 changed files with 57 additions and 55 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Internal refactor of opaque type handling.
+5 -24
View File
@@ -15,7 +15,7 @@
*/
import { CommandGraph } from './CommandGraph';
import { CliFeature, InternalCliFeature, InternalCliPlugin } from './types';
import { CliFeature, OpaqueCliPlugin } from './types';
import { CommandRegistry } from './CommandRegistry';
import { Command } from 'commander';
import { version } from '../lib/version';
@@ -42,10 +42,11 @@ export class CliInitializer {
}
async #register(feature: CliFeature) {
if (isCliPlugin(feature)) {
await feature.init(this.commandRegistry);
if (OpaqueCliPlugin.isType(feature)) {
const internal = OpaqueCliPlugin.toInternal(feature);
await internal.init(this.commandRegistry);
} else {
throw new Error(`Unsupported feature type: ${feature.$$type}`);
throw new Error(`Unsupported feature type: ${(feature as any).$$type}`);
}
}
@@ -146,26 +147,6 @@ export class CliInitializer {
}
}
function toInternalCliFeature(feature: CliFeature): InternalCliFeature {
if (feature.$$type !== '@backstage/CliFeature') {
throw new Error(`Invalid CliFeature, bad type '${feature.$$type}'`);
}
const internal = feature as InternalCliFeature;
if (internal.version !== 'v1') {
throw new Error(`Invalid CliFeature, bad version '${internal.version}'`);
}
return internal;
}
function isCliPlugin(feature: CliFeature): feature is InternalCliPlugin {
const internal = toInternalCliFeature(feature);
if (internal.featureType === 'plugin') {
return true;
}
// Backwards compatibility for v1 registrations that use duck typing
return 'plugin' in internal;
}
/** @internal */
export function unwrapFeature(
feature: CliFeature | { default: CliFeature },
@@ -0,0 +1,20 @@
/*
* Copyright 2023 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.
*/
// Single re-export to avoid doing this import in multiple places, but still
// avoid duplicate declarations because this one is a bit tricky.
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
export { describeParentCallSite } from '../../../frontend-plugin-api/src/routing/describeParentCallSite';
+10 -11
View File
@@ -14,19 +14,18 @@
* limitations under the License.
*/
import { CommandRegistry } from './CommandRegistry';
import { InternalCliPlugin } from './types';
import { describeParentCallSite } from './describeParentCallSite';
import { BackstageCommand, CliPlugin, OpaqueCliPlugin } from './types';
export function createCliPlugin(options: {
pluginId: string;
init: (registry: CommandRegistry) => Promise<void>;
}): InternalCliPlugin {
return {
id: options.pluginId,
init: (registry: {
addCommand: (command: BackstageCommand) => void;
}) => Promise<void>;
}): CliPlugin {
return OpaqueCliPlugin.createInstance('v1', {
pluginId: options.pluginId,
init: options.init,
$$type: '@backstage/CliFeature',
version: 'v1',
featureType: 'plugin',
description: 'A Backstage CLI plugin',
};
description: `created at '${describeParentCallSite()}'`,
});
}
+17 -20
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommandRegistry } from './CommandRegistry';
import { OpaqueType } from '@internal/opaque';
export interface BackstageCommand {
path: string[];
@@ -22,26 +22,23 @@ export interface BackstageCommand {
execute: (options: { args: string[] }) => Promise<void>;
}
export interface CliFeature {
$$type: '@backstage/CliFeature';
}
export type CliFeature = CliPlugin;
export interface CliPlugin {
id: string;
init: (registry: CommandRegistry) => Promise<void>;
$$type: '@backstage/CliFeature';
readonly pluginId: string;
readonly $$type: '@backstage/CliPlugin';
}
/**
* @public
*/
export interface InternalCliPlugin extends CliFeature {
version: 'v1';
featureType: 'plugin';
description: string;
id: string;
init: (registry: CommandRegistry) => Promise<void>;
}
/** @internal */
export type InternalCliFeature = InternalCliPlugin;
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'],
});