diff --git a/.changeset/four-peaches-talk.md b/.changeset/four-peaches-talk.md new file mode 100644 index 0000000000..4b0d2e8a47 --- /dev/null +++ b/.changeset/four-peaches-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor of opaque type handling. diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts index d3cba34f60..b310a07a12 100644 --- a/packages/cli/src/wiring/CliInitializer.ts +++ b/packages/cli/src/wiring/CliInitializer.ts @@ -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 }, diff --git a/packages/cli/src/wiring/describeParentCallSite.ts b/packages/cli/src/wiring/describeParentCallSite.ts new file mode 100644 index 0000000000..35603e33b0 --- /dev/null +++ b/packages/cli/src/wiring/describeParentCallSite.ts @@ -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'; diff --git a/packages/cli/src/wiring/factory.ts b/packages/cli/src/wiring/factory.ts index eec058e180..6374f37c14 100644 --- a/packages/cli/src/wiring/factory.ts +++ b/packages/cli/src/wiring/factory.ts @@ -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; -}): InternalCliPlugin { - return { - id: options.pluginId, + init: (registry: { + addCommand: (command: BackstageCommand) => void; + }) => Promise; +}): 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()}'`, + }); } diff --git a/packages/cli/src/wiring/types.ts b/packages/cli/src/wiring/types.ts index 0decdc6479..86b66f229e 100644 --- a/packages/cli/src/wiring/types.ts +++ b/packages/cli/src/wiring/types.ts @@ -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; } -export interface CliFeature { - $$type: '@backstage/CliFeature'; -} +export type CliFeature = CliPlugin; export interface CliPlugin { - id: string; - init: (registry: CommandRegistry) => Promise; - $$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; -} - -/** @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; + }; +}>({ + type: '@backstage/CliPlugin', + versions: ['v1'], +});