diff --git a/packages/app-defaults/src/createApp.tsx b/packages/app-defaults/src/createApp.tsx index d85dfa04eb..05e048ffca 100644 --- a/packages/app-defaults/src/createApp.tsx +++ b/packages/app-defaults/src/createApp.tsx @@ -50,7 +50,7 @@ export function createApp( ...icons, ...options?.icons, }, - plugins: (options?.plugins as BackstagePlugin[]) ?? [], + plugins: (options?.plugins as BackstagePlugin[]) ?? [], themes: options?.themes ?? themes, }); } diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index f47ddf676a..130d961312 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -82,8 +82,8 @@ import { resolveRouteBindings } from './resolveRouteBindings'; import { BackstageRouteObject } from '../routing/types'; type CompatiblePlugin = - | BackstagePlugin - | (Omit, 'getFeatureFlags'> & { + | BackstagePlugin + | (Omit, 'getFeatureFlags'> & { output(): Array<{ type: 'feature-flag'; name: string }>; }); @@ -145,7 +145,7 @@ function useConfigLoader( class AppContextImpl implements AppContext { constructor(private readonly app: AppManager) {} - getPlugins(): BackstagePlugin[] { + getPlugins(): BackstagePlugin[] { return this.app.getPlugins(); } @@ -186,8 +186,8 @@ export class AppManager implements BackstageApp { this.apiFactoryRegistry = new ApiFactoryRegistry(); } - getPlugins(): BackstagePlugin[] { - return Array.from(this.plugins) as BackstagePlugin[]; + getPlugins(): BackstagePlugin[] { + return Array.from(this.plugins) as BackstagePlugin[]; } getSystemIcon(key: string): IconComponent | undefined { @@ -241,7 +241,7 @@ export class AppManager implements BackstageApp { validateRouteParameters(routing.paths, routing.parents); validateRouteBindings( routeBindings, - this.plugins as Iterable>, + this.plugins as Iterable>, ); } diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index ff7a43d0eb..e7436dbf0a 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -291,7 +291,7 @@ export type BackstageApp = { /** * Returns all plugins registered for the app. */ - getPlugins(): BackstagePlugin[]; + getPlugins(): BackstagePlugin[]; /** * Get a common or custom icon for this app. @@ -321,7 +321,7 @@ export type AppContext = { /** * Get a list of all plugins that are installed in the app. */ - getPlugins(): BackstagePlugin[]; + getPlugins(): BackstagePlugin[]; /** * Get a common or custom icon for this app. diff --git a/packages/core-app-api/src/plugins/collectors.ts b/packages/core-app-api/src/plugins/collectors.ts index 0f8b65ad62..376a8b709c 100644 --- a/packages/core-app-api/src/plugins/collectors.ts +++ b/packages/core-app-api/src/plugins/collectors.ts @@ -18,9 +18,9 @@ import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; import { createCollector } from '../extensions/traversal'; export const pluginCollector = createCollector( - () => new Set>(), + () => new Set>(), (acc, node) => { - const plugin = getComponentData>( + const plugin = getComponentData>( node, 'core.plugin', ); diff --git a/packages/core-app-api/src/routing/validation.ts b/packages/core-app-api/src/routing/validation.ts index c5d3e4aca9..2e0093eaab 100644 --- a/packages/core-app-api/src/routing/validation.ts +++ b/packages/core-app-api/src/routing/validation.ts @@ -65,7 +65,7 @@ export function validateRouteParameters( // Validates that all non-optional external routes have been bound export function validateRouteBindings( routeBindings: Map, - plugins: Iterable>>, + plugins: Iterable>>, ) { for (const plugin of plugins) { if (!plugin.externalRoutes) { diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx index 149ea1f520..1d84d55442 100644 --- a/packages/core-plugin-api/src/extensions/extensions.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.tsx @@ -226,7 +226,7 @@ export function createReactExtension< 'Component'; return { - expose(plugin: BackstagePlugin) { + expose(plugin: BackstagePlugin) { const Result: any = (props: any) => { const app = useApp(); const { Progress } = app.getComponents(); diff --git a/packages/core-plugin-api/src/plugin-options/usePluginOptions.test.tsx b/packages/core-plugin-api/src/plugin-options/usePluginOptions.test.tsx index f9f1b6c67e..11bf85aafa 100644 --- a/packages/core-plugin-api/src/plugin-options/usePluginOptions.test.tsx +++ b/packages/core-plugin-api/src/plugin-options/usePluginOptions.test.tsx @@ -17,13 +17,22 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { usePluginOptions, PluginProvider } from './usePluginOptions'; -import { createPlugin, PluginInputOptions, PluginOptions } from '../plugin'; +import { createPlugin } from '../plugin'; describe('usePluginOptions', () => { it('should provide a versioned value to hook', () => { + type TestInputPluginOptions = { + 'key-1': string; + }; + + type TestPluginOptions = { + 'key-1': string; + 'key-2': string; + }; + const plugin = createPlugin({ id: 'my-plugin', - __experimentalConfigure(_: PluginInputOptions): PluginOptions { + __experimentalConfigure(_: TestInputPluginOptions): TestPluginOptions { return { 'key-1': 'value-1', 'key-2': 'value-2' }; }, }); diff --git a/packages/core-plugin-api/src/plugin-options/usePluginOptions.tsx b/packages/core-plugin-api/src/plugin-options/usePluginOptions.tsx index 499c4f10d3..5feda8234b 100644 --- a/packages/core-plugin-api/src/plugin-options/usePluginOptions.tsx +++ b/packages/core-plugin-api/src/plugin-options/usePluginOptions.tsx @@ -19,7 +19,7 @@ import { createVersionedValueMap, useVersionedContext, } from '@backstage/version-bridge'; -import { BackstagePlugin, PluginOptions } from '../plugin'; +import { BackstagePlugin, AnyPluginOptions } from '../plugin'; import React, { ReactNode } from 'react'; const contextKey: string = 'plugin-context'; @@ -38,12 +38,14 @@ export const PluginProvider = ({ children, plugin, }: PluginOptionsProviderProps): JSX.Element => { - const { Provider } = createVersionedContext<{ 1: PluginOptions }>(contextKey); + const { Provider } = createVersionedContext<{ 1: AnyPluginOptions }>( + contextKey, + ); return ( @@ -59,7 +61,7 @@ export const PluginProvider = ({ * @alpha */ export function usePluginOptions< - TPluginOptions extends PluginOptions = PluginOptions, + TPluginOptions extends AnyPluginOptions = AnyPluginOptions, >(): TPluginOptions { const versionedHolder = useVersionedContext<{ 1: TPluginOptions }>( contextKey, @@ -74,5 +76,9 @@ export function usePluginOptions< throw new Error('Plugin Options v1 is not available'); } - return value.options as TPluginOptions; + return ( + value as unknown as { + getPluginOptions(): AnyPluginOptions; + } + ).getPluginOptions() as TPluginOptions; } diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx index e78b6be01e..8c5153da99 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.tsx @@ -20,8 +20,8 @@ import { Extension, AnyRoutes, AnyExternalRoutes, - PluginOptions, - PluginInputOptions, + AnyPluginOptions, + AnyPluginInputOptions, PluginFeatureFlagConfig, } from './types'; import { AnyApiFactory } from '../apis'; @@ -30,13 +30,22 @@ import { AnyApiFactory } from '../apis'; * @internal */ export class PluginImpl< + PluginInputOptions extends AnyPluginInputOptions, + PluginOptions extends AnyPluginOptions, Routes extends AnyRoutes, ExternalRoutes extends AnyExternalRoutes, -> implements BackstagePlugin +> implements BackstagePlugin { - constructor(private readonly config: PluginConfig) {} + constructor( + private readonly config: PluginConfig< + PluginInputOptions, + PluginOptions, + Routes, + ExternalRoutes + >, + ) {} - options: PluginOptions | undefined = undefined; + options: AnyPluginOptions | undefined = undefined; getId(): string { return this.config.id; @@ -68,11 +77,11 @@ export class PluginImpl< } } - getPluginOptions(): PluginOptions { + getPluginOptions(): AnyPluginOptions { if (this.config.__experimentalConfigure && !this.options) { this.options = this.config.__experimentalConfigure(); } - return this.options ?? ({} as PluginOptions); + return this.options ?? ({} as AnyPluginOptions); } toString() { @@ -87,10 +96,17 @@ export class PluginImpl< * @public */ export function createPlugin< + PluginInputOptions extends AnyPluginInputOptions = {}, + PluginOptions extends AnyPluginInputOptions = {}, Routes extends AnyRoutes = {}, ExternalRoutes extends AnyExternalRoutes = {}, >( - config: PluginConfig, -): BackstagePlugin { + config: PluginConfig< + PluginInputOptions, + PluginOptions, + Routes, + ExternalRoutes + >, +): BackstagePlugin { return new PluginImpl(config); } diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts index e433e5cc86..58fcec03aa 100644 --- a/packages/core-plugin-api/src/plugin/index.ts +++ b/packages/core-plugin-api/src/plugin/index.ts @@ -18,8 +18,8 @@ export { createPlugin } from './Plugin'; export type { AnyExternalRoutes, - PluginOptions, - PluginInputOptions, + AnyPluginOptions, + AnyPluginInputOptions, AnyRoutes, BackstagePlugin, Extension, diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 5daaa3e9f9..56e0845706 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -27,7 +27,7 @@ import { AnyApiFactory } from '../apis'; * @public */ export type Extension = { - expose(plugin: BackstagePlugin): T; + expose(plugin: BackstagePlugin): T; }; /** @@ -49,8 +49,8 @@ export type AnyExternalRoutes = { [name: string]: ExternalRouteRef }; * * @public */ -export type PluginOptions = { [name: string]: unknown }; -export type PluginInputOptions = { [name: string]: unknown }; +export type AnyPluginOptions = { [name: string]: unknown }; +export type AnyPluginInputOptions = { [name: string]: unknown }; /** * Plugin type. @@ -58,6 +58,7 @@ export type PluginInputOptions = { [name: string]: unknown }; * @public */ export type BackstagePlugin< + PluginInputOptions extends AnyPluginInputOptions = {}, Routes extends AnyRoutes = {}, ExternalRoutes extends AnyExternalRoutes = {}, > = { @@ -89,6 +90,8 @@ export type PluginFeatureFlagConfig = { * @public */ export type PluginConfig< + PluginInputOptions extends AnyPluginInputOptions, + PluginOptions extends AnyPluginOptions, Routes extends AnyRoutes, ExternalRoutes extends AnyExternalRoutes, > = { diff --git a/plugins/catalog/src/options.ts b/plugins/catalog/src/options.ts index edf702ee0b..1d8b972ec2 100644 --- a/plugins/catalog/src/options.ts +++ b/plugins/catalog/src/options.ts @@ -20,5 +20,9 @@ export type CatalogPluginOptions = { createButtonTitle: string; }; +export type CatalogInputPluginOptions = { + createButtonTitle: string; +}; + export const useCatalogPluginOptions = () => usePluginOptions(); diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 1fd52a82a8..ec4a1b52e1 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -30,8 +30,6 @@ import { discoveryApiRef, fetchApiRef, storageApiRef, - PluginOptions, - PluginInputOptions, } from '@backstage/core-plugin-api'; import { DefaultStarredEntitiesApi } from './apis'; import { AboutCardProps } from './components/AboutCard'; @@ -45,6 +43,7 @@ import { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; import { HasSystemsCardProps } from './components/HasSystemsCard'; import { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; import { rootRouteRef } from './routes'; +import { CatalogInputPluginOptions, CatalogPluginOptions } from './options'; /** @public */ export const catalogPlugin = createPlugin({ @@ -74,7 +73,9 @@ export const catalogPlugin = createPlugin({ createComponent: createComponentRouteRef, viewTechDoc: viewTechDocRouteRef, }, - __experimentalConfigure(options?: PluginInputOptions): PluginOptions { + __experimentalConfigure( + options?: CatalogInputPluginOptions, + ): CatalogPluginOptions { const defaultOptions = { createButtonTitle: 'Create', };