frontend-plugin-api: add support for accessing extensions from plugin instance

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-08 16:19:54 +02:00
parent 57cb03d6b6
commit a65cfc8149
25 changed files with 564 additions and 152 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint.
@@ -7,7 +7,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api';
import { default as React_2 } from 'react';
// @public (undocumented)
const examplePlugin: BackstagePlugin<{}, {}>;
const examplePlugin: BackstagePlugin<{}, {}, {}>;
export default examplePlugin;
// @public (undocumented)
+138 -34
View File
@@ -275,17 +275,22 @@ export { BackstageIdentityResponse };
// @public (undocumented)
export interface BackstagePlugin<
Routes extends AnyRoutes = AnyRoutes,
ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TExtensionMap extends {
[id in string]: ExtensionDefinition<any, any>;
} = {},
> {
// (undocumented)
readonly $$type: '@backstage/BackstagePlugin';
// (undocumented)
readonly externalRoutes: ExternalRoutes;
readonly externalRoutes: TExternalRoutes;
// (undocumented)
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
// (undocumented)
readonly id: string;
// (undocumented)
readonly routes: Routes;
readonly routes: TRoutes;
}
export { BackstageUserIdentity };
@@ -396,7 +401,15 @@ export function createApiExtension<
configSchema?: PortableSchema<TConfig>;
inputs?: TInputs;
},
): ExtensionDefinition<TConfig, TConfig, never, never>;
): ExtensionDefinition<
TConfig,
TConfig,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
export namespace createApiExtension {
@@ -492,7 +505,15 @@ export function createComponentExtension<
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => ComponentType<TProps>;
};
}): ExtensionDefinition<TConfig, TConfig, never, never>;
}): ExtensionDefinition<
TConfig,
TConfig,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
export namespace createComponentExtension {
@@ -528,8 +549,14 @@ export function createExtension<
[key: string]: (zImpl: typeof z) => z.ZodType;
},
UFactoryOutput extends ExtensionDataValue<any, any>,
const TKind extends string | undefined = undefined,
const TNamespace extends string | undefined = undefined,
const TName extends string | undefined = undefined,
>(
options: CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
@@ -545,7 +572,10 @@ export function createExtension<
}>
>,
UOutput,
TInputs
TInputs,
string | undefined extends TKind ? undefined : TKind,
string | undefined extends TNamespace ? undefined : TNamespace,
string | undefined extends TName ? undefined : TName
>;
// @public @deprecated (undocumented)
@@ -580,11 +610,17 @@ export function createExtensionBlueprint<
[key in string]: (zImpl: typeof z) => z.ZodType;
},
UFactoryOutput extends ExtensionDataValue<any, any>,
TKind extends string,
TNamespace extends string | undefined = undefined,
TName extends string | undefined = undefined,
TDataRefs extends {
[name in string]: AnyExtensionDataRef;
} = never,
>(
options: CreateExtensionBlueprintOptions<
TKind,
TNamespace,
TName,
TParams,
UOutput,
TInputs,
@@ -593,6 +629,9 @@ export function createExtensionBlueprint<
TDataRefs
>,
): ExtensionBlueprint<
TKind,
TNamespace,
TName,
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
@@ -613,6 +652,9 @@ export function createExtensionBlueprint<
// @public (undocumented)
export type CreateExtensionBlueprintOptions<
TKind extends string,
TNamespace extends string | undefined,
TName extends string | undefined,
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
@@ -632,8 +674,8 @@ export type CreateExtensionBlueprintOptions<
[name in string]: AnyExtensionDataRef;
},
> = {
kind: string;
namespace?: string | ((params: TParams) => string);
kind: TKind;
namespace?: TNamespace | ((params: TParams) => TNamespace);
attachTo: {
id: string;
input: string;
@@ -641,7 +683,7 @@ export type CreateExtensionBlueprintOptions<
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
name?: string | ((params: TParams) => string);
name?: TName | ((params: TParams) => TName);
config?: {
schema: TConfigSchema | ((params: TParams) => TConfigSchema);
};
@@ -714,6 +756,9 @@ export function createExtensionInput<
// @public (undocumented)
export type CreateExtensionOptions<
TKind extends string | undefined,
TNamespace extends string | undefined,
TName extends string | undefined,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
@@ -729,9 +774,9 @@ export type CreateExtensionOptions<
},
UFactoryOutput extends ExtensionDataValue<any, any>,
> = {
kind?: string;
namespace?: string;
name?: string;
kind?: TKind;
namespace?: TNamespace;
name?: TName;
attachTo: {
id: string;
input: string;
@@ -794,7 +839,10 @@ export function createNavItemExtension(options: {
title?: string | undefined;
},
never,
never
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
@@ -817,7 +865,15 @@ export function createNavLogoExtension(options: {
namespace?: string;
logoIcon: JSX.Element;
logoFull: JSX.Element;
}): ExtensionDefinition<unknown, unknown, never, never>;
}): ExtensionDefinition<
unknown,
unknown,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
export namespace createNavLogoExtension {
@@ -865,11 +921,22 @@ export function createPageExtension<
// @public (undocumented)
export function createPlugin<
Routes extends AnyRoutes = {},
ExternalRoutes extends AnyExternalRoutes = {},
TId extends string,
TRoutes extends AnyRoutes = {},
TExternalRoutes extends AnyExternalRoutes = {},
TExtensions extends readonly ExtensionDefinition<any, any>[] = [],
>(
options: PluginOptions<Routes, ExternalRoutes>,
): BackstagePlugin<Routes, ExternalRoutes>;
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
TRoutes,
TExternalRoutes,
{
[KExtension in TExtensions[number] as ResolveExtensionId<
KExtension,
TId
>]: KExtension;
}
>;
// @public
export function createRouteRef<
@@ -972,7 +1039,15 @@ export function createSubRouteRef<
// @public (undocumented)
export function createThemeExtension(
theme: AppTheme,
): ExtensionDefinition<unknown, unknown, never, never>;
): ExtensionDefinition<
unknown,
unknown,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
export namespace createThemeExtension {
@@ -988,7 +1063,15 @@ export namespace createThemeExtension {
export function createTranslationExtension(options: {
name?: string;
resource: TranslationResource | TranslationMessages;
}): ExtensionDefinition<unknown, unknown, never, never>;
}): ExtensionDefinition<
unknown,
unknown,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @public (undocumented)
export namespace createTranslationExtension {
@@ -1044,6 +1127,9 @@ export interface Extension<TConfig, TConfigInput = TConfig> {
// @public (undocumented)
export interface ExtensionBlueprint<
TKind extends string,
TNamespace extends string | undefined,
TName extends string | undefined,
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
@@ -1068,6 +1154,8 @@ export interface ExtensionBlueprint<
// (undocumented)
dataRefs: TDataRefs;
make<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
@@ -1084,8 +1172,8 @@ export interface ExtensionBlueprint<
},
>(
args: {
namespace?: string;
name?: string;
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: {
id: string;
input: string;
@@ -1143,7 +1231,12 @@ export interface ExtensionBlueprint<
>;
}>
> &
TConfigInput
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
TKind,
string | undefined extends TNewNamespace ? TNamespace : TNewNamespace,
string | undefined extends TNewName ? TName : TNewName
>;
}
@@ -1239,6 +1332,9 @@ export interface ExtensionDefinition<
}
>;
} = {},
TKind extends string | undefined = string | undefined,
TNamespace extends string | undefined = string | undefined,
TName extends string | undefined = string | undefined,
> {
// (undocumented)
$$type: '@backstage/ExtensionDefinition';
@@ -1252,11 +1348,11 @@ export interface ExtensionDefinition<
// (undocumented)
readonly disabled: boolean;
// (undocumented)
readonly kind?: string;
readonly kind?: TKind;
// (undocumented)
readonly name?: string;
readonly name?: TName;
// (undocumented)
readonly namespace?: string;
readonly namespace?: TNamespace;
// (undocumented)
override<
TExtensionConfigSchema extends {
@@ -1325,7 +1421,10 @@ export interface ExtensionDefinition<
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs
TInputs & TExtraInputs,
TKind,
TNamespace,
TName
>;
}
@@ -1405,6 +1504,9 @@ export { googleAuthApiRef };
// @public (undocumented)
export const IconBundleBlueprint: ExtensionBlueprint<
'icon-bundle',
'app',
undefined,
{
icons: {
[x: string]: IconComponent;
@@ -1536,19 +1638,21 @@ export { PendingOAuthRequest };
// @public (undocumented)
export interface PluginOptions<
Routes extends AnyRoutes,
ExternalRoutes extends AnyExternalRoutes,
TId extends string,
TRoutes extends AnyRoutes,
TExternalRoutes extends AnyExternalRoutes,
TExtensions extends readonly ExtensionDefinition<any, any>[],
> {
// (undocumented)
extensions?: ExtensionDefinition<any, any>[];
extensions?: TExtensions;
// (undocumented)
externalRoutes?: ExternalRoutes;
externalRoutes?: TExternalRoutes;
// (undocumented)
featureFlags?: FeatureFlagConfig[];
// (undocumented)
id: string;
id: TId;
// (undocumented)
routes?: Routes;
routes?: TRoutes;
}
// @public (undocumented)
@@ -174,6 +174,9 @@ export type VerifyExtensionFactoryOutput<
/** @public */
export type CreateExtensionOptions<
TKind extends string | undefined,
TNamespace extends string | undefined,
TName extends string | undefined,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
@@ -184,9 +187,9 @@ export type CreateExtensionOptions<
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
> = {
kind?: string;
namespace?: string;
name?: string;
kind?: TKind;
namespace?: TNamespace;
name?: TName;
attachTo: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
@@ -214,11 +217,14 @@ export interface ExtensionDefinition<
{ optional: boolean; singleton: boolean }
>;
} = {},
TKind extends string | undefined = string | undefined,
TNamespace extends string | undefined = string | undefined,
TName extends string | undefined = string | undefined,
> {
$$type: '@backstage/ExtensionDefinition';
readonly kind?: string;
readonly namespace?: string;
readonly name?: string;
readonly kind?: TKind;
readonly namespace?: TNamespace;
readonly name?: TName;
readonly attachTo: { id: string; input: string };
readonly disabled: boolean;
readonly configSchema?: PortableSchema<TConfig, TConfigInput>;
@@ -284,45 +290,68 @@ export interface ExtensionDefinition<
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs
TInputs & TExtraInputs,
TKind,
TNamespace,
TName
>;
}
/** @internal */
export type InternalExtensionDefinition<TConfig, TConfigInput> =
ExtensionDefinition<TConfig, TConfigInput> &
(
| {
readonly version: 'v1';
readonly inputs: AnyExtensionInputMap;
readonly output: AnyExtensionDataMap;
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<AnyExtensionInputMap>;
}): ExtensionDataValues<any>;
}
| {
readonly version: 'v2';
readonly inputs: {
export type InternalExtensionDefinition<
TConfig,
TConfigInput = TConfig,
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
} = {},
TKind extends string | undefined = string | undefined,
TNamespace extends string | undefined = string | undefined,
TName extends string | undefined = string | undefined,
> = ExtensionDefinition<
TConfig,
TConfigInput,
UOutput,
TInputs,
TKind,
TNamespace,
TName
> &
(
| {
readonly version: 'v1';
readonly inputs: AnyExtensionInputMap;
readonly output: AnyExtensionDataMap;
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<AnyExtensionInputMap>;
}): ExtensionDataValues<any>;
}
| {
readonly version: 'v2';
readonly inputs: {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
};
readonly output: Array<AnyExtensionDataRef>;
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<{
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
};
readonly output: Array<AnyExtensionDataRef>;
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<{
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
}>;
}): Iterable<ExtensionDataValue<any, any>>;
}
);
}>;
}): Iterable<ExtensionDataValue<any, any>>;
}
);
/** @internal */
export function toInternalExtensionDefinition<TConfig, TConfigInput>(
@@ -357,8 +386,14 @@ export function createExtension<
},
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
const TKind extends string | undefined = undefined,
const TNamespace extends string | undefined = undefined,
const TName extends string | undefined = undefined,
>(
options: CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
@@ -374,7 +409,10 @@ export function createExtension<
}>
>,
UOutput,
TInputs
TInputs,
string | undefined extends TKind ? undefined : TKind,
string | undefined extends TNamespace ? undefined : TNamespace,
string | undefined extends TName ? undefined : TName
>;
/**
* @public
@@ -394,6 +432,9 @@ export function createExtension<
>,
): ExtensionDefinition<TConfig, TConfigInput, never, never>;
export function createExtension<
const TKind extends string | undefined,
const TNamespace extends string | undefined,
const TName extends string | undefined,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
@@ -408,7 +449,15 @@ export function createExtension<
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
options:
| CreateExtensionOptions<UOutput, TInputs, TConfigSchema, UFactoryOutput>
| CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
UFactoryOutput
>
| LegacyCreateExtensionOptions<
AnyExtensionDataMap,
TLegacyInputs,
@@ -431,7 +480,10 @@ export function createExtension<
}>
>),
UOutput,
TInputs
TInputs,
TKind,
TNamespace,
TName
> {
if ('configSchema' in options && 'config' in options) {
throw new Error(`Cannot provide both configSchema and config.schema`);
@@ -542,7 +594,10 @@ export function createExtension<
>
>,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs
TInputs & TExtraInputs,
TKind,
TNamespace,
TName
> => {
if (!Array.isArray(options.output)) {
throw new Error(
@@ -550,6 +605,9 @@ export function createExtension<
);
}
const newOptions = options as CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
@@ -612,7 +670,7 @@ export function createExtension<
return deduplicatedResult.values() as Iterable<UOverrideFactoryOutput>;
},
} as CreateExtensionOptions<AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UOverrideFactoryOutput>);
} as CreateExtensionOptions<TKind, TNamespace, TName, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UOverrideFactoryOutput>);
},
} as InternalExtensionDefinition<
TConfig &
@@ -630,6 +688,11 @@ export function createExtension<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>)
>),
UOutput,
TInputs,
TKind,
TNamespace,
TName
>;
}
@@ -36,6 +36,9 @@ import {
* @public
*/
export type CreateExtensionBlueprintOptions<
TKind extends string,
TNamespace extends string | undefined,
TName extends string | undefined,
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
@@ -48,13 +51,13 @@ export type CreateExtensionBlueprintOptions<
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends { [name in string]: AnyExtensionDataRef },
> = {
kind: string;
namespace?: string | ((params: TParams) => string);
kind: TKind;
namespace?: TNamespace | ((params: TParams) => TNamespace);
attachTo: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
name?: string | ((params: TParams) => string);
name?: TName | ((params: TParams) => TName);
config?: {
schema: TConfigSchema | ((params: TParams) => TConfigSchema);
};
@@ -76,6 +79,9 @@ export type CreateExtensionBlueprintOptions<
* @public
*/
export interface ExtensionBlueprint<
TKind extends string,
TNamespace extends string | undefined,
TName extends string | undefined,
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
@@ -97,6 +103,8 @@ export interface ExtensionBlueprint<
* optionally call the original factory with the same params.
*/
make<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
@@ -110,8 +118,8 @@ export interface ExtensionBlueprint<
},
>(
args: {
namespace?: string;
name?: string;
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
@@ -166,7 +174,12 @@ export interface ExtensionBlueprint<
>;
}>
> &
TConfigInput
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
TKind,
string | undefined extends TNewNamespace ? TNamespace : TNewNamespace,
string | undefined extends TNewName ? TName : TNewName
>;
}
@@ -198,6 +211,9 @@ export function createDataContainer<UData extends AnyExtensionDataRef>(
* @internal
*/
class ExtensionBlueprintImpl<
TKind extends string,
TNamespace extends string | undefined,
TName extends string | undefined,
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
@@ -211,6 +227,9 @@ class ExtensionBlueprintImpl<
> {
constructor(
private readonly options: CreateExtensionBlueprintOptions<
TKind,
TNamespace,
TName,
TParams,
UOutput,
TInputs,
@@ -236,9 +255,11 @@ class ExtensionBlueprintImpl<
{ optional: boolean; singleton: boolean }
>;
},
TNewNamespace extends string | undefined = undefined,
TNewName extends string | undefined = undefined,
>(args: {
namespace?: string;
name?: string;
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs;
@@ -360,26 +381,7 @@ class ExtensionBlueprintImpl<
}
throw new Error('Either params or factory must be provided');
},
} as CreateExtensionOptions<UOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UFactoryOutput>) as ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
} & {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<
{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
} & {
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}
>
>
>;
} as CreateExtensionOptions<TKind, string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, string | undefined extends TNewName ? TName : TNewName, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UFactoryOutput>);
}
}
@@ -400,9 +402,15 @@ export function createExtensionBlueprint<
},
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
TKind extends string,
TNamespace extends string | undefined = undefined,
TName extends string | undefined = undefined,
TDataRefs extends { [name in string]: AnyExtensionDataRef } = never,
>(
options: CreateExtensionBlueprintOptions<
TKind,
TNamespace,
TName,
TParams,
UOutput,
TInputs,
@@ -411,6 +419,9 @@ export function createExtensionBlueprint<
TDataRefs
>,
): ExtensionBlueprint<
TKind,
TNamespace,
TName,
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
@@ -427,6 +438,9 @@ export function createExtensionBlueprint<
TDataRefs
> {
return new ExtensionBlueprintImpl(options) as ExtensionBlueprint<
TKind,
TNamespace,
TName,
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
@@ -34,11 +34,9 @@ const nameExtensionDataRef = createExtensionDataRef<string>().with({
const Extension1 = createExtension({
name: '1',
attachTo: { id: 'test/output', input: 'names' },
output: {
name: nameExtensionDataRef,
},
output: [nameExtensionDataRef],
factory() {
return { name: 'extension-1' };
return [nameExtensionDataRef('extension-1')];
},
});
@@ -150,6 +148,8 @@ describe('createPlugin', () => {
});
expect(plugin).toBeDefined();
expect(plugin.getExtension('test/1')).toBe(Extension1);
await renderWithEffects(
createTestAppRoot({
features: [plugin],
@@ -17,6 +17,7 @@
import { ExtensionDefinition } from './createExtension';
import {
Extension,
ResolveExtensionId,
resolveExtensionDefinition,
} from './resolveExtensionDefinition';
import {
@@ -28,21 +29,23 @@ import {
/** @public */
export interface PluginOptions<
Routes extends AnyRoutes,
ExternalRoutes extends AnyExternalRoutes,
TId extends string,
TRoutes extends AnyRoutes,
TExternalRoutes extends AnyExternalRoutes,
TExtensions extends readonly ExtensionDefinition<any, any>[],
> {
id: string;
routes?: Routes;
externalRoutes?: ExternalRoutes;
extensions?: ExtensionDefinition<any, any>[];
id: TId;
routes?: TRoutes;
externalRoutes?: TExternalRoutes;
extensions?: TExtensions;
featureFlags?: FeatureFlagConfig[];
}
/** @public */
export interface InternalBackstagePlugin<
Routes extends AnyRoutes = AnyRoutes,
ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
> extends BackstagePlugin<Routes, ExternalRoutes> {
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
> extends BackstagePlugin<TRoutes, TExternalRoutes> {
readonly version: 'v1';
readonly extensions: Extension<unknown>[];
readonly featureFlags: FeatureFlagConfig[];
@@ -50,17 +53,36 @@ export interface InternalBackstagePlugin<
/** @public */
export function createPlugin<
Routes extends AnyRoutes = {},
ExternalRoutes extends AnyExternalRoutes = {},
TId extends string,
TRoutes extends AnyRoutes = {},
TExternalRoutes extends AnyExternalRoutes = {},
TExtensions extends readonly ExtensionDefinition<any, any>[] = [],
>(
options: PluginOptions<Routes, ExternalRoutes>,
): BackstagePlugin<Routes, ExternalRoutes> {
const extensions = (options.extensions ?? []).map(def =>
resolveExtensionDefinition(def, { namespace: options.id }),
);
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
TRoutes,
TExternalRoutes,
{
[KExtension in TExtensions[number] as ResolveExtensionId<
KExtension,
TId
>]: KExtension;
}
> {
const extensions = new Array<Extension<unknown>>();
const extensionDefinitionsById = new Map<
string,
ExtensionDefinition<unknown>
>();
const extensionIds = extensions.map(e => e.id);
if (extensionIds.length !== new Set(extensionIds).size) {
for (const def of options.extensions ?? []) {
const ext = resolveExtensionDefinition(def, { namespace: options.id });
extensions.push(ext);
extensionDefinitionsById.set(ext.id, def);
}
if (extensions.length !== extensionDefinitionsById.size) {
const extensionIds = extensions.map(e => e.id);
const duplicates = Array.from(
new Set(
extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index),
@@ -78,14 +100,17 @@ export function createPlugin<
$$type: '@backstage/BackstagePlugin',
version: 'v1',
id: options.id,
routes: options.routes ?? ({} as Routes),
externalRoutes: options.externalRoutes ?? ({} as ExternalRoutes),
routes: options.routes ?? ({} as TRoutes),
externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),
featureFlags: options.featureFlags ?? [],
extensions,
getExtension(id) {
return extensionDefinitionsById.get(id);
},
toString() {
return `Plugin{id=${options.id}}`;
},
} as InternalBackstagePlugin<Routes, ExternalRoutes>;
} as InternalBackstagePlugin<TRoutes, TExternalRoutes>;
}
/** @internal */
@@ -15,7 +15,10 @@
*/
import { ExtensionDefinition } from './createExtension';
import { resolveExtensionDefinition } from './resolveExtensionDefinition';
import {
ResolveExtensionId,
resolveExtensionDefinition,
} from './resolveExtensionDefinition';
describe('resolveExtensionDefinition', () => {
const baseDef = {
@@ -98,3 +101,103 @@ describe('old resolveExtensionDefinition', () => {
);
});
});
describe('ResolveExtensionId', () => {
it('should resolve extension IDs correctly', () => {
type NamedExtension<
TKind extends string | undefined,
TNamespace extends string | undefined,
TName extends string | undefined,
> = ExtensionDefinition<any, any, any, any, TKind, TNamespace, TName>;
const id1: 'k:ns' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', undefined>,
undefined
>;
const id2: 'k:n' = {} as ResolveExtensionId<
NamedExtension<'k', undefined, 'n'>,
undefined
>;
const id3: 'ns/n' = {} as ResolveExtensionId<
NamedExtension<undefined, 'ns', 'n'>,
undefined
>;
const id4: never = {} as ResolveExtensionId<
NamedExtension<'k', undefined, undefined>,
undefined
>;
const id5: 'ns' = {} as ResolveExtensionId<
NamedExtension<undefined, 'ns', undefined>,
undefined
>;
const id6: 'n' = {} as ResolveExtensionId<
NamedExtension<undefined, undefined, 'n'>,
undefined
>;
const id7: 'k:ns/n' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', 'n'>,
undefined
>;
const id8: 'k:ns2' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', undefined>,
'ns2'
>;
const id9: 'k:ns2/n' = {} as ResolveExtensionId<
NamedExtension<'k', undefined, 'n'>,
'ns2'
>;
const ida: 'ns2/n' = {} as ResolveExtensionId<
NamedExtension<undefined, 'ns', 'n'>,
'ns2'
>;
const idb: 'k:ns2' = {} as ResolveExtensionId<
NamedExtension<'k', undefined, undefined>,
'ns2'
>;
const idc: 'ns2' = {} as ResolveExtensionId<
NamedExtension<undefined, 'ns', undefined>,
'ns2'
>;
const idd: 'ns2/n' = {} as ResolveExtensionId<
NamedExtension<undefined, undefined, 'n'>,
'ns2'
>;
const ide: 'k:ns2/n' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', 'n'>,
'ns2'
>;
const invalid1: never = {} as ResolveExtensionId<
NamedExtension<string | undefined, 'ns', 'n'>,
undefined
>;
const invalid2: never = {} as ResolveExtensionId<
NamedExtension<'k', string | undefined, 'n'>,
undefined
>;
const invalid3: never = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', string | undefined>,
undefined
>;
expect([
id1,
id2,
id3,
id4,
id5,
id6,
id7,
id8,
id9,
ida,
idb,
idc,
idd,
ide,
invalid1,
invalid2,
invalid3,
]).toBeDefined();
});
});
@@ -94,6 +94,36 @@ export function toInternalExtension<TConfig, TConfigInput>(
return internal;
}
/** @ignore */
export type ResolveExtensionId<
TExtension extends ExtensionDefinition<any>,
TDefaultNamespace extends string | undefined,
> = TExtension extends ExtensionDefinition<
any,
any,
any,
any,
infer IKind,
infer INamespace,
infer IName
>
? [string | undefined] extends [IKind | INamespace | IName]
? never
: (
(
undefined extends TDefaultNamespace ? INamespace : TDefaultNamespace
) extends infer ISelectedNamespace extends string
? undefined extends IName
? ISelectedNamespace
: `${ISelectedNamespace}/${IName}`
: IName
) extends infer INamePart extends string
? IKind extends string
? `${IKind}:${INamePart}`
: INamePart
: never
: never;
/** @internal */
export function resolveExtensionDefinition<TConfig, TConfigInput>(
definition: ExtensionDefinition<TConfig, TConfigInput>,
@@ -15,6 +15,7 @@
*/
import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing';
import { ExtensionDefinition } from './createExtension';
/**
* Feature flag configuration.
@@ -32,15 +33,24 @@ export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef };
/** @public */
export type AnyExternalRoutes = { [name in string]: ExternalRouteRef };
/** @public */
export type ExtensionMap<
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> },
> = {
get<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
};
/** @public */
export interface BackstagePlugin<
Routes extends AnyRoutes = AnyRoutes,
ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> } = {},
> {
readonly $$type: '@backstage/BackstagePlugin';
readonly id: string;
readonly routes: Routes;
readonly externalRoutes: ExternalRoutes;
readonly routes: TRoutes;
readonly externalRoutes: TExternalRoutes;
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
}
/** @public */
+2 -1
View File
@@ -14,7 +14,8 @@ const _default: BackstagePlugin<
},
{
registerApi: ExternalRouteRef<undefined>;
}
},
{}
>;
export default _default;
+1 -1
View File
@@ -6,7 +6,7 @@
import { BackstagePlugin } from '@backstage/frontend-plugin-api';
// @public (undocumented)
const visualizerPlugin: BackstagePlugin<{}, {}>;
const visualizerPlugin: BackstagePlugin<{}, {}, {}>;
export default visualizerPlugin;
// (No @packageDocumentation comment for this package)
+2 -1
View File
@@ -18,7 +18,8 @@ const _default: BackstagePlugin<
kind: string;
namespace: string;
}>;
}
},
{}
>;
export default _default;
@@ -11,6 +11,7 @@ const _default: BackstagePlugin<
{
importPage: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
+13 -2
View File
@@ -115,7 +115,15 @@ export function createEntityCardExtension<
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => Promise<JSX.Element>;
}): ExtensionDefinition<TConfig, TConfig, never, never>;
}): ExtensionDefinition<
TConfig,
TConfig,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)
export function createEntityContentExtension<
@@ -150,7 +158,10 @@ export function createEntityContentExtension<
path?: string | undefined;
},
never,
never
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha
+11 -2
View File
@@ -108,7 +108,15 @@ export function createCatalogFilterExtension<
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
loader: (options: { config: TConfig }) => Promise<JSX.Element>;
}): ExtensionDefinition<TConfig, TConfig, never, never>;
}): ExtensionDefinition<
TConfig,
TConfig,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)
const _default: BackstagePlugin<
@@ -132,7 +140,8 @@ const _default: BackstagePlugin<
templateName: string;
}>;
unregisterRedirect: ExternalRouteRef<undefined>;
}
},
{}
>;
export default _default;
+1
View File
@@ -11,6 +11,7 @@ const _default: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
+1 -1
View File
@@ -7,7 +7,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
// @alpha (undocumented)
const _default: BackstagePlugin<{}, {}>;
const _default: BackstagePlugin<{}, {}, {}>;
export default _default;
// @alpha (undocumented)
+1
View File
@@ -11,6 +11,7 @@ const _default: BackstagePlugin<
{
kubernetes: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
+2 -1
View File
@@ -11,7 +11,8 @@ const _default: BackstagePlugin<
{},
{
catalogIndex: ExternalRouteRef<undefined>;
}
},
{}
>;
export default _default;
+2 -1
View File
@@ -36,7 +36,8 @@ const _default: BackstagePlugin<
kind: string;
namespace: string;
}>;
}
},
{}
>;
export default _default;
+9 -1
View File
@@ -25,7 +25,15 @@ export function createSearchResultListItemExtension<
},
>(
options: SearchResultItemExtensionOptions<TConfig>,
): ExtensionDefinition<TConfig, TConfig, never, never>;
): ExtensionDefinition<
TConfig,
TConfig,
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)
export namespace createSearchResultListItemExtension {
+18 -3
View File
@@ -13,12 +13,21 @@ const _default: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
// @alpha (undocumented)
export const searchApi: ExtensionDefinition<{}, {}, never, never>;
export const searchApi: ExtensionDefinition<
{},
{},
never,
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)
export const searchNavItem: ExtensionDefinition<
@@ -29,7 +38,10 @@ export const searchNavItem: ExtensionDefinition<
title?: string | undefined;
},
never,
never
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)
@@ -43,7 +55,10 @@ export const searchPage: ExtensionDefinition<
noTrack: boolean;
},
AnyExtensionDataRef,
{}
{},
string | undefined,
string | undefined,
string | undefined
>;
// (No @packageDocumentation comment for this package)
+5 -1
View File
@@ -18,6 +18,7 @@ const _default: BackstagePlugin<
}>;
entityContent: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
@@ -39,7 +40,10 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition<
title?: string | undefined;
},
never,
never
never,
string | undefined,
string | undefined,
string | undefined
>;
// (No @packageDocumentation comment for this package)
+5 -1
View File
@@ -13,6 +13,7 @@ const _default: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{},
{}
>;
export default _default;
@@ -26,7 +27,10 @@ export const settingsNavItem: ExtensionDefinition<
title?: string | undefined;
},
never,
never
never,
string | undefined,
string | undefined,
string | undefined
>;
// @alpha (undocumented)