diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 3ca91966bf..b42278e4a6 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -59,7 +59,7 @@ import { ApiResolver, ApiFactoryRegistry } from '../apis/system'; type FullAppOptions = { apis: Iterable; icons: SystemIcons; - plugins: BackstagePlugin[]; + plugins: BackstagePlugin[]; components: AppComponents; themes: AppTheme[]; configLoader?: AppConfigLoader; @@ -107,7 +107,7 @@ export class PrivateAppImpl implements BackstageApp { private readonly apis: Iterable; private readonly icons: SystemIcons; - private readonly plugins: BackstagePlugin[]; + private readonly plugins: BackstagePlugin[]; private readonly components: AppComponents; private readonly themes: AppTheme[]; private readonly configLoader?: AppConfigLoader; @@ -125,7 +125,7 @@ export class PrivateAppImpl implements BackstageApp { this.defaultApis = options.defaultApis; } - getPlugins(): BackstagePlugin[] { + getPlugins(): BackstagePlugin[] { return this.plugins; } diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 8a1eb92e8e..aa432bd005 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -93,7 +93,7 @@ export type AppOptions = { /** * A list of all plugins to include in the app. */ - plugins?: BackstagePlugin[]; + plugins?: BackstagePlugin[]; /** * Supply components to the app to override the default ones. @@ -140,7 +140,7 @@ export type BackstageApp = { /** * Returns all plugins registered for the app. */ - getPlugins(): BackstagePlugin[]; + getPlugins(): BackstagePlugin[]; /** * Get a common icon for this app. diff --git a/packages/core-api/src/extensions/extensions.tsx b/packages/core-api/src/extensions/extensions.tsx index 4087d5f963..079fec4597 100644 --- a/packages/core-api/src/extensions/extensions.tsx +++ b/packages/core-api/src/extensions/extensions.tsx @@ -54,7 +54,7 @@ export function createReactExtension(options: { }): Extension> { const { component: Component, data = {} } = options; return { - expose(plugin: BackstagePlugin): NamedExoticComponent { + expose(plugin: BackstagePlugin): NamedExoticComponent { const Result = (props: Props) => ; attachComponentData(Result, 'core.plugin', plugin); diff --git a/packages/core-api/src/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx index f477674f4d..797dcd852e 100644 --- a/packages/core-api/src/plugin/Plugin.tsx +++ b/packages/core-api/src/plugin/Plugin.tsx @@ -19,13 +19,18 @@ import { PluginOutput, BackstagePlugin, Extension, + AnyRoutes, + AnyExternalRoutes, } from './types'; import { AnyApiFactory } from '../apis'; -export class PluginImpl implements BackstagePlugin { +export class PluginImpl< + Routes extends AnyRoutes, + ExternalRoutes extends AnyExternalRoutes +> implements BackstagePlugin { private storedOutput?: PluginOutput[]; - constructor(private readonly config: PluginConfig) {} + constructor(private readonly config: PluginConfig) {} getId(): string { return this.config.id; @@ -35,6 +40,14 @@ export class PluginImpl implements BackstagePlugin { return this.config.apis ?? []; } + get routes(): Routes { + return this.config.routes ?? ({} as Routes); + } + + get externalRoutes(): ExternalRoutes { + return this.config.externalRoutes ?? ({} as ExternalRoutes); + } + output(): PluginOutput[] { if (this.storedOutput) { return this.storedOutput; @@ -79,6 +92,11 @@ export class PluginImpl implements BackstagePlugin { } } -export function createPlugin(config: PluginConfig): BackstagePlugin { +export function createPlugin< + Routes extends AnyRoutes, + ExternalRoutes extends AnyExternalRoutes +>( + config: PluginConfig, +): BackstagePlugin { return new PluginImpl(config); } diff --git a/packages/core-api/src/plugin/collectors.ts b/packages/core-api/src/plugin/collectors.ts index 3eadfc0185..b04c7b41c4 100644 --- a/packages/core-api/src/plugin/collectors.ts +++ b/packages/core-api/src/plugin/collectors.ts @@ -34,9 +34,12 @@ import { getComponentData } from '../extensions'; import { createCollector } from '../extensions/traversal'; export const pluginCollector = createCollector( - () => new Set(), + () => new Set>(), (acc, node) => { - const plugin = getComponentData(node, 'core.plugin'); + const plugin = getComponentData>( + node, + 'core.plugin', + ); if (plugin) { acc.add(plugin); } diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts index dd19948a0a..e3f2e38972 100644 --- a/packages/core-api/src/plugin/types.ts +++ b/packages/core-api/src/plugin/types.ts @@ -17,6 +17,7 @@ import { ComponentType } from 'react'; import { RouteRef } from '../routing'; import { AnyApiFactory } from '../apis/system'; +import { ExternalRouteRef } from '../routing/RouteRef'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. @@ -67,20 +68,34 @@ export type PluginOutput = | FeatureFlagOutput; export type Extension = { - expose(plugin: BackstagePlugin): T; + expose(plugin: BackstagePlugin): T; }; -export type BackstagePlugin = { +export type AnyRoutes = { [name: string]: RouteRef }; + +export type AnyExternalRoutes = { [name: string]: ExternalRouteRef }; + +export type BackstagePlugin< + Routes extends AnyRoutes, + ExternalRoutes extends AnyExternalRoutes +> = { getId(): string; output(): PluginOutput[]; getApis(): Iterable; provide(extension: Extension): T; + routes: Routes; + externalRoutes: ExternalRoutes; }; -export type PluginConfig = { +export type PluginConfig< + Routes extends AnyRoutes, + ExternalRoutes extends AnyExternalRoutes +> = { id: string; apis?: Iterable; register?(hooks: PluginHooks): void; + routes?: Routes; + externalRoutes?: ExternalRoutes; }; export type PluginHooks = {