frontend-plugin-api: refactor ExtensionDefinition and ExtensionBlueprint type params

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-22 14:21:05 +02:00
parent 4387b80784
commit ee454fdf53
42 changed files with 2426 additions and 2426 deletions
@@ -18,6 +18,7 @@ import { createExtensionTester } from '@backstage/frontend-test-utils';
import { createExtension } from './createExtension';
import { createExtensionDataRef } from './createExtensionDataRef';
import { createExtensionInput } from './createExtensionInput';
import { PortableSchema } from '../schema';
const stringDataRef = createExtensionDataRef<string>().with({ id: 'string' });
const numberDataRef = createExtensionDataRef<number>().with({ id: 'number' });
@@ -289,7 +290,12 @@ describe('createExtension', () => {
);
expect(
extension.configSchema?.parse({
(
(extension as any).configSchema as PortableSchema<
(typeof extension.T)['config'],
(typeof extension.T)['configInput']
>
)?.parse({
foo: 'x',
bar: 'y',
baz: 'z',
@@ -302,7 +308,12 @@ describe('createExtension', () => {
baz: 'z',
});
expect(
extension.configSchema?.parse({
(
(extension as any).configSchema as PortableSchema<
(typeof extension.T)['config'],
(typeof extension.T)['configInput']
>
)?.parse({
foo: 'x',
}),
).toEqual({
@@ -142,33 +142,27 @@ export type CreateExtensionOptions<
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/** @public */
export interface ExtensionDefinition<
TConfig,
TConfigInput = TConfig,
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
export type ExtensionDefinitionParameters = {
kind?: string;
namespace?: string;
name?: string;
configInput?: { [K in string]: any };
config?: { [K in string]: any };
output: AnyExtensionDataRef;
inputs?: {
[KName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
} = {},
TIdParts extends {
kind?: string;
namespace?: string;
name?: string;
} = {
kind?: string;
namespace?: string;
name?: string;
},
> {
};
};
/** @public */
export type ExtensionDefinition<
T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters,
> = {
$$type: '@backstage/ExtensionDefinition';
readonly kind?: TIdParts['kind'];
readonly namespace?: TIdParts['namespace'];
readonly name?: TIdParts['name'];
readonly attachTo: { id: string; input: string };
readonly disabled: boolean;
readonly configSchema?: PortableSchema<TConfig, TConfigInput>;
readonly T: T;
override<
TExtensionConfigSchema extends {
@@ -187,78 +181,71 @@ export interface ExtensionDefinition<
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
[KName in keyof T['inputs']]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
[KName in keyof T['config']]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
};
factory(
originalFactory: (context?: {
config?: TConfig;
inputs?: ResolveInputValueOverrides<TInputs>;
}) => ExtensionDataContainer<UOutput>,
config?: T['config'];
inputs?: ResolveInputValueOverrides<NonNullable<T['inputs']>>;
}) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: TConfig & {
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
AnyExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: UNewOutput,
UFactoryOutput
>,
): ExtensionDefinition<
{
): ExtensionDefinition<{
kind: T['kind'];
namespace: T['namespace'];
name: T['name'];
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
inputs: T['inputs'] & TExtraInputs;
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
} & TConfig,
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
TIdParts
>;
}
};
configInput: T['configInput'] &
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
>;
}>;
};
/** @internal */
export type InternalExtensionDefinition<
TConfig,
TConfigInput = TConfig,
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
} = {},
TIdParts extends {
kind?: string;
namespace?: string;
name?: string;
} = {
kind?: string;
namespace?: string;
name?: string;
},
> = ExtensionDefinition<TConfig, TConfigInput, UOutput, TInputs, TIdParts> &
(
T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters,
> = ExtensionDefinition<T> & {
readonly kind?: string;
readonly namespace?: string;
readonly name?: string;
readonly attachTo: { id: string; input: string };
readonly disabled: boolean;
readonly configSchema?: PortableSchema<T['config'], T['configInput']>;
} & (
| {
readonly version: 'v1';
readonly inputs: {
@@ -276,7 +263,7 @@ export type InternalExtensionDefinition<
factory(context: {
node: AppNode;
apis: ApiHolder;
config: TConfig;
config: object;
inputs: {
[inputName in string]: unknown;
};
@@ -296,7 +283,7 @@ export type InternalExtensionDefinition<
factory(context: {
node: AppNode;
apis: ApiHolder;
config: TConfig;
config: object;
inputs: ResolvedExtensionInputs<{
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
@@ -308,13 +295,10 @@ export type InternalExtensionDefinition<
);
/** @internal */
export function toInternalExtensionDefinition<TConfig, TConfigInput>(
overrides: ExtensionDefinition<TConfig, TConfigInput>,
): InternalExtensionDefinition<TConfig, TConfigInput> {
const internal = overrides as InternalExtensionDefinition<
TConfig,
TConfigInput
>;
export function toInternalExtensionDefinition<
T extends ExtensionDefinitionParameters,
>(overrides: ExtensionDefinition<T>): InternalExtensionDefinition<T> {
const internal = overrides as InternalExtensionDefinition<T>;
if (internal.$$type !== '@backstage/ExtensionDefinition') {
throw new Error(
`Invalid extension definition instance, bad type '${internal.$$type}'`,
@@ -353,23 +337,45 @@ export function createExtension<
TConfigSchema,
UFactoryOutput
>,
): ExtensionDefinition<
{
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
): ExtensionDefinition<{
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>;
output: UOutput;
inputs: TInputs;
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}> {
type T = {
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>;
output: UOutput;
inputs: TInputs;
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}
> {
};
const schemaDeclaration = options.config?.schema;
const configSchema =
schemaDeclaration &&
@@ -384,6 +390,7 @@ export function createExtension<
return {
$$type: '@backstage/ExtensionDefinition',
version: 'v2',
T: null as unknown as T,
kind: options.kind,
namespace: options.namespace,
name: options.name,
@@ -486,21 +493,5 @@ export function createExtension<
},
}) as ExtensionDefinition<any>;
},
} as InternalExtensionDefinition<
{
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}
>;
} as InternalExtensionDefinition<T>;
}
@@ -35,10 +35,7 @@ import { createExtensionDataContainer } from './createExtensionDataContainer';
function unused(..._any: any[]) {}
function factoryOutput(
ext: ExtensionDefinition<any, any>,
inputs: unknown = undefined,
) {
function factoryOutput(ext: ExtensionDefinition, inputs: unknown = undefined) {
const int = toInternalExtensionDefinition(ext);
if (int.version !== 'v2') {
throw new Error('Expected v2 extension');
@@ -81,28 +81,31 @@ export type CreateExtensionBlueprintOptions<
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/** @public */
export type ExtensionBlueprintParameters = {
kind: string;
namespace?: string;
name?: string;
params: object;
configInput?: { [K in string]: any };
config?: { [K in string]: any };
output: AnyExtensionDataRef;
inputs?: {
[KName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
};
dataRefs?: { [name in string]: AnyExtensionDataRef };
};
/**
* @public
*/
export interface ExtensionBlueprint<
TIdParts extends {
kind: string;
namespace?: string;
name?: string;
},
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
TConfig extends { [key in string]: unknown },
TConfigInput extends { [key in string]: unknown },
TDataRefs extends { [name in string]: AnyExtensionDataRef },
T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters,
> {
dataRefs: TDataRefs;
dataRefs: T['dataRefs'];
make<
TNewNamespace extends string | undefined,
@@ -112,20 +115,18 @@ export interface ExtensionBlueprint<
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
params: TParams;
}): ExtensionDefinition<
TConfig,
TConfigInput,
UOutput,
TInputs,
{
kind: TIdParts['kind'];
namespace: string | undefined extends TNewNamespace
? TIdParts['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? TIdParts['name'] : TNewName;
}
>;
params: T['params'];
}): ExtensionDefinition<{
kind: T['kind'];
namespace: string | undefined extends TNewNamespace
? T['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? T['name'] : TNewName;
config: T['config'];
configInput: T['configInput'];
output: T['output'];
inputs: T['inputs'];
}>;
/**
* Creates a new extension from the blueprint.
@@ -153,63 +154,66 @@ export interface ExtensionBlueprint<
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
[KName in keyof T['inputs']]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
[KName in keyof T['config']]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
};
factory(
originalFactory: (
params: TParams,
params: T['params'],
context?: {
config?: TConfig;
inputs?: ResolveInputValueOverrides<TInputs>;
config?: T['config'];
inputs?: ResolveInputValueOverrides<NonNullable<T['inputs']>>;
},
) => ExtensionDataContainer<UOutput>,
) => ExtensionDataContainer<T['output']>,
context: {
node: AppNode;
apis: ApiHolder;
config: TConfig & {
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput,
UFactoryOutput
>;
}): ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
} & TConfig,
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
{
kind: TIdParts['kind'];
namespace: string | undefined extends TNewNamespace
? TIdParts['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? TIdParts['name'] : TNewName;
}
>;
}): ExtensionDefinition<{
config: (string extends keyof TExtensionConfigSchema
? {}
: {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
}) &
T['config'];
configInput: (string extends keyof TExtensionConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
>) &
T['configInput'];
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
inputs: T['inputs'] & TExtraInputs;
kind: T['kind'];
namespace: string | undefined extends TNewNamespace
? T['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? T['name'] : TNewName;
}>;
}
/**
@@ -219,7 +223,7 @@ export interface ExtensionBlueprint<
* @public
*/
export function createExtensionBlueprint<
TParams,
TParams extends object,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
@@ -245,27 +249,25 @@ export function createExtensionBlueprint<
UFactoryOutput,
TDataRefs
>,
): ExtensionBlueprint<
{
kind: TKind;
namespace: TNamespace;
name: TName;
},
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
string extends keyof TConfigSchema
): ExtensionBlueprint<{
kind: TKind;
namespace: TNamespace;
name: TName;
params: TParams;
output: UOutput;
inputs: string extends keyof TInputs ? {} : TInputs;
config: string extends keyof TConfigSchema
? {}
: { [key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>> },
string extends keyof TConfigSchema
: { [key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>> };
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
TDataRefs
> {
>;
dataRefs: TDataRefs;
}> {
return {
dataRefs: options.dataRefs,
make(args) {
@@ -282,7 +284,7 @@ export function createExtensionBlueprint<
options.factory(args.params, ctx) as Iterable<
ExtensionDataValue<any, any>
>,
});
}) as ExtensionDefinition;
},
makeWithOverrides(args) {
return createExtension({
@@ -327,29 +329,27 @@ export function createExtensionBlueprint<
},
) as Iterable<ExtensionDataValue<any, any>>;
},
}) as ExtensionDefinition<any>;
}) as ExtensionDefinition;
},
} as ExtensionBlueprint<
{
kind: TKind;
namespace: TNamespace;
name: TName;
},
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
string extends keyof TConfigSchema
} as ExtensionBlueprint<{
kind: TKind;
namespace: TNamespace;
name: TName;
params: TParams;
output: UOutput;
inputs: string extends keyof TInputs ? {} : TInputs;
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
string extends keyof TConfigSchema
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
TDataRefs
>;
>;
dataRefs: TDataRefs;
}>;
}
@@ -23,7 +23,7 @@ import { ExtensionOverrides, FeatureFlagConfig } from './types';
/** @public */
export interface ExtensionOverridesOptions {
extensions: ExtensionDefinition<any, any>[];
extensions: ExtensionDefinition[];
featureFlags?: FeatureFlagConfig[];
}
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { ExtensionDefinition } from './createExtension';
import {
ExtensionDefinition,
InternalExtensionDefinition,
toInternalExtensionDefinition,
} from './createExtension';
import {
Extension,
ResolveExtensionId,
@@ -32,7 +36,7 @@ export interface PluginOptions<
TId extends string,
TRoutes extends AnyRoutes,
TExternalRoutes extends AnyExternalRoutes,
TExtensions extends readonly ExtensionDefinition<any, any>[],
TExtensions extends readonly ExtensionDefinition[],
> {
id: TId;
routes?: TRoutes;
@@ -56,7 +60,7 @@ export function createFrontendPlugin<
TId extends string,
TRoutes extends AnyRoutes = {},
TExternalRoutes extends AnyExternalRoutes = {},
TExtensions extends readonly ExtensionDefinition<any, any>[] = [],
TExtensions extends readonly ExtensionDefinition[] = [],
>(
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
@@ -69,16 +73,20 @@ export function createFrontendPlugin<
>]: KExtension;
}
> {
const extensions = new Array<Extension<unknown>>();
const extensions = new Array<Extension<any>>();
const extensionDefinitionsById = new Map<
string,
ExtensionDefinition<unknown>
InternalExtensionDefinition
>();
for (const def of options.extensions ?? []) {
const internal = toInternalExtensionDefinition(def);
const ext = resolveExtensionDefinition(def, { namespace: options.id });
extensions.push(ext);
extensionDefinitionsById.set(ext.id, { ...def, namespace: options.id });
extensionDefinitionsById.set(ext.id, {
...internal,
namespace: options.id,
});
}
if (extensions.length !== extensionDefinitionsById.size) {
@@ -18,6 +18,7 @@ export { coreExtensionData } from './coreExtensionData';
export {
createExtension,
type ExtensionDefinition,
type ExtensionDefinitionParameters,
type CreateExtensionOptions,
type ResolvedExtensionInput,
type ResolvedExtensionInputs,
@@ -56,6 +57,7 @@ export {
export {
type CreateExtensionBlueprintOptions,
type ExtensionBlueprint,
type ExtensionBlueprintParameters,
createExtensionBlueprint,
} from './createExtensionBlueprint';
export { type ResolveInputValueOverrides } from './resolveInputOverrides';
@@ -23,10 +23,11 @@ import {
describe('resolveExtensionDefinition', () => {
const baseDef = {
$$type: '@backstage/ExtensionDefinition',
T: null as any,
version: 'v2',
attachTo: { id: '', input: '' },
disabled: false,
override: () => ({} as ExtensionDefinition<unknown>),
override: () => ({} as ExtensionDefinition),
};
it.each([
@@ -39,7 +40,7 @@ describe('resolveExtensionDefinition', () => {
const resolved = resolveExtensionDefinition({
...baseDef,
...definition,
} as ExtensionDefinition<unknown>);
} as ExtensionDefinition);
expect(resolved.id).toBe(expected);
expect(String(resolved)).toBe(`Extension{id=${expected}}`);
});
@@ -49,12 +50,12 @@ describe('resolveExtensionDefinition', () => {
resolveExtensionDefinition({
...baseDef,
kind: 'k',
} as ExtensionDefinition<unknown>),
} as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined',
);
expect(() =>
resolveExtensionDefinition(baseDef as ExtensionDefinition<unknown>),
resolveExtensionDefinition(baseDef as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined',
);
@@ -64,10 +65,11 @@ describe('resolveExtensionDefinition', () => {
describe('old resolveExtensionDefinition', () => {
const baseDef = {
$$type: '@backstage/ExtensionDefinition',
T: null as any,
version: 'v1',
attachTo: { id: '', input: '' },
disabled: false,
override: () => ({} as ExtensionDefinition<unknown>),
override: () => ({} as ExtensionDefinition),
};
it.each([
@@ -80,7 +82,7 @@ describe('old resolveExtensionDefinition', () => {
const resolved = resolveExtensionDefinition({
...baseDef,
...definition,
} as ExtensionDefinition<unknown>);
} as ExtensionDefinition);
expect(resolved.id).toBe(expected);
expect(String(resolved)).toBe(`Extension{id=${expected}}`);
});
@@ -90,12 +92,12 @@ describe('old resolveExtensionDefinition', () => {
resolveExtensionDefinition({
...baseDef,
kind: 'k',
} as ExtensionDefinition<unknown>),
} as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined',
);
expect(() =>
resolveExtensionDefinition(baseDef as ExtensionDefinition<unknown>),
resolveExtensionDefinition(baseDef as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined',
);
@@ -108,13 +110,12 @@ describe('ResolveExtensionId', () => {
TKind extends string | undefined,
TNamespace extends string | undefined,
TName extends string | undefined,
> = ExtensionDefinition<
any,
any,
any,
any,
{ kind: TKind; namespace: TNamespace; name: TName }
>;
> = ExtensionDefinition<{
kind: TKind;
namespace: TNamespace;
name: TName;
output: any;
}>;
const id1: 'k:ns' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', undefined>,
@@ -17,6 +17,7 @@
import { ApiHolder, AppNode } from '../apis';
import {
ExtensionDefinition,
ExtensionDefinitionParameters,
ResolvedExtensionInputs,
toInternalExtensionDefinition,
} from './createExtension';
@@ -109,19 +110,14 @@ export function toInternalExtension<TConfig, TConfigInput>(
/** @ignore */
export type ResolveExtensionId<
TExtension extends ExtensionDefinition<any>,
TExtension extends ExtensionDefinition,
TDefaultNamespace extends string | undefined,
> = TExtension extends ExtensionDefinition<
any,
any,
any,
any,
{
kind: infer IKind extends string | undefined;
namespace: infer INamespace extends string | undefined;
name: infer IName extends string | undefined;
}
>
> = TExtension extends ExtensionDefinition<{
kind: infer IKind extends string | undefined;
namespace: infer INamespace extends string | undefined;
name: infer IName extends string | undefined;
output: any;
}>
? [string | undefined] extends [IKind | INamespace | IName]
? never
: (
@@ -140,10 +136,12 @@ export type ResolveExtensionId<
: never;
/** @internal */
export function resolveExtensionDefinition<TConfig, TConfigInput>(
definition: ExtensionDefinition<TConfig, TConfigInput>,
export function resolveExtensionDefinition<
T extends ExtensionDefinitionParameters,
>(
definition: ExtensionDefinition<T>,
context?: { namespace?: string },
): Extension<TConfig, TConfigInput> {
): Extension<T['config'], T['configInput']> {
const internalDefinition = toInternalExtensionDefinition(definition);
const {
name,
@@ -172,5 +170,5 @@ export function resolveExtensionDefinition<TConfig, TConfigInput>(
toString() {
return `Extension{id=${id}}`;
},
} as InternalExtension<TConfig, TConfigInput>;
} as InternalExtension<T['config'], T['configInput']> & Object;
}
@@ -35,7 +35,7 @@ export type AnyExternalRoutes = { [name in string]: ExternalRouteRef };
/** @public */
export type ExtensionMap<
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> },
TExtensionMap extends { [id in string]: ExtensionDefinition },
> = {
get<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
};
@@ -44,7 +44,7 @@ export type ExtensionMap<
export interface BackstagePlugin<
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> } = {},
TExtensionMap extends { [id in string]: ExtensionDefinition } = {},
> {
readonly $$type: '@backstage/BackstagePlugin';
readonly id: string;
@@ -52,7 +52,7 @@ export interface BackstagePlugin<
readonly externalRoutes: TExternalRoutes;
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
withOverrides(options: {
extensions: Array<ExtensionDefinition<any, any>>;
extensions: Array<ExtensionDefinition>;
}): BackstagePlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}