frontend-plugin-api: type safe attachTo references

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-11-08 19:49:22 +01:00
parent 8b84f39946
commit c6b9f50337
14 changed files with 469 additions and 205 deletions
@@ -15,12 +15,8 @@
*/
import { createApiRef } from '@backstage/core-plugin-api';
import {
FrontendPlugin,
Extension,
ExtensionDataRef,
ExtensionAttachTo,
} from '../../wiring';
import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring';
import { ExtensionAttachTo } from '../../wiring/resolveExtensionDefinition';
/**
* The specification for this {@link AppNode} in the {@link AppTree}.
@@ -203,10 +203,16 @@ describe('ApiBlueprint', () => {
"optional": false,
"singleton": false,
},
"context": {
"input": "test",
"kind": "api",
"name": "test",
},
"extensionData": [
[Function],
],
"replaces": undefined,
"withContext": [Function],
},
},
"kind": "api",
@@ -288,6 +288,95 @@ describe('createExtension', () => {
);
});
it('should provide type safe attachments by reference', () => {
const parent = createExtension({
attachTo: { id: 'root', input: 'children' },
inputs: {
string: createExtensionInput([stringDataRef]),
stringOpt: createExtensionInput([stringDataRef.optional()]),
number: createExtensionInput([numberDataRef]),
numberOpt: createExtensionInput([numberDataRef.optional()]),
both: createExtensionInput([stringDataRef, numberDataRef]),
bothOptString: createExtensionInput([
stringDataRef.optional(),
numberDataRef,
]),
bothOptNumber: createExtensionInput([
stringDataRef,
numberDataRef.optional(),
]),
bothOpt: createExtensionInput([
stringDataRef.optional(),
numberDataRef.optional(),
]),
},
output: [],
factory: () => [],
});
const strOutExt = createExtension({
attachTo: parent.inputs.string,
output: [stringDataRef],
factory: () => [stringDataRef('str')],
});
strOutExt.override({
attachTo: parent.inputs.string,
});
strOutExt.override({
attachTo: parent.inputs.stringOpt,
});
strOutExt.override({
// @ts-expect-error
attachTo: parent.inputs.number,
});
strOutExt.override({
attachTo: parent.inputs.numberOpt,
});
strOutExt.override({
// @ts-expect-error
attachTo: parent.inputs.both,
});
strOutExt.override({
attachTo: parent.inputs.bothOptNumber,
});
strOutExt.override({
// @ts-expect-error
attachTo: parent.inputs.bothOptString,
});
strOutExt.override({
attachTo: parent.inputs.bothOpt,
});
const numberOutExt = createExtension({
// @ts-expect-error
attachTo: parent.inputs.string,
output: [numberDataRef],
factory: () => [numberDataRef(1)],
});
numberOutExt.override({
// @ts-expect-error
attachTo: parent.inputs.string,
});
numberOutExt.override({
attachTo: parent.inputs.number,
});
const bothOutExt = createExtension({
attachTo: parent.inputs.both,
output: [numberDataRef, stringDataRef],
factory: () => [numberDataRef(1), stringDataRef('str')],
});
// TODO(Rugvip): Potentially encapsulate the parent input type in the extension, until then we can't verify this
bothOutExt.override({
output: [numberDataRef.optional(), stringDataRef],
factory: () => [stringDataRef('str')],
});
bothOutExt.override({
// @ts-expect-error
attachTo: parent.inputs.both,
output: [numberDataRef.optional(), stringDataRef],
factory: () => [stringDataRef('str')],
});
expect('types').not.toBe('broken');
});
it('should create an extension with input', () => {
const extension = createExtension({
attachTo: { id: 'root', input: 'default' },
@@ -93,27 +93,39 @@ type JoinStringUnion<
: JoinStringUnion<IRest, TDiv, `${TResult}${TDiv}${INext}`>
: TResult;
/** @ignore */
export type RequiredExtensionIds<UExtensionData extends ExtensionDataRef> =
UExtensionData extends any
? UExtensionData['config']['optional'] extends true
? never
: UExtensionData['id']
: never;
/** @ignore */
export type VerifyExtensionFactoryOutput<
UDeclaredOutput extends ExtensionDataRef,
UFactoryOutput extends ExtensionDataValue<any, any>,
> = (
UDeclaredOutput extends any
? UDeclaredOutput['config']['optional'] extends true
? never
: UDeclaredOutput['id']
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? [UFactoryOutput['id']] extends [UDeclaredOutput['id']]
? {}
: `Error: The extension factory has undeclared output(s): ${JoinStringUnion<
Exclude<UFactoryOutput['id'], UDeclaredOutput['id']>
>}`
: `Error: The extension factory is missing the following output(s): ${JoinStringUnion<
Exclude<IRequiredOutputIds, UFactoryOutput['id']>
> = [RequiredExtensionIds<UDeclaredOutput>] extends [UFactoryOutput['id']]
? [UFactoryOutput['id']] extends [UDeclaredOutput['id']]
? {}
: `Error: The extension factory has undeclared output(s): ${JoinStringUnion<
Exclude<UFactoryOutput['id'], UDeclaredOutput['id']>
>}`
: never;
: `Error: The extension factory is missing the following output(s): ${JoinStringUnion<
Exclude<RequiredExtensionIds<UDeclaredOutput>, UFactoryOutput['id']>
>}`;
/** @ignore */
export type VerifyExtensionAttachTo<
UOutput extends ExtensionDataRef,
UParentInput extends ExtensionDataRef,
> = ExtensionDataRef extends UParentInput
? {}
: [RequiredExtensionIds<UParentInput>] extends [RequiredExtensionIds<UOutput>]
? {}
: `Error: This parent extension input requires the following extension data, but it is not declared as guaranteed output of this extension: ${JoinStringUnion<
Exclude<RequiredExtensionIds<UParentInput>, RequiredExtensionIds<UOutput>>
>}`;
/**
* Specifies where an extension should attach in the extension tree.
@@ -125,7 +137,7 @@ export type VerifyExtensionFactoryOutput<
* There are three more advanced forms that are available for more complex use-cases:
*
* 1. Relative attachment points: using the `relative` property instead of `id`, the attachment point is resolved relative to the current plugin.
* 2. Extension input references: using a reference obtained from another extension's `inputs` property.
* 2. Extension input references: using a reference in code to another extension's input in the same plugin. These references are always relative.
* 3. Array of attachment points: an array of attachment points can be used to clone and attach to multiple extensions at once.
*
* @example
@@ -149,10 +161,12 @@ export type VerifyExtensionFactoryOutput<
*
* @public
*/
export type ExtensionDefinitionAttachTo =
export type ExtensionDefinitionAttachTo<
UParentInputs extends ExtensionDataRef = ExtensionDataRef,
> =
| { id: string; input: string; relative?: never }
| { relative: { kind?: string; name?: string }; input: string; id?: never }
| ExtensionInput
| ExtensionInput<UParentInputs>
| Array<
| { id: string; input: string; relative?: never }
| {
@@ -160,7 +174,7 @@ export type ExtensionDefinitionAttachTo =
input: string;
id?: never;
}
| ExtensionInput
| ExtensionInput<UParentInputs>
>;
/** @public */
@@ -171,10 +185,12 @@ export type CreateExtensionOptions<
TInputs extends { [inputName in string]: ExtensionInput },
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
UParentInputs extends ExtensionDataRef,
> = {
kind?: TKind;
name?: TName;
attachTo: ExtensionDefinitionAttachTo;
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
@@ -227,7 +243,9 @@ export type ExtensionDefinition<
* References to the inputs of this extension, which can be used to attach child extensions.
*/
readonly inputs: {
[K in keyof T['inputs']]: ExtensionInput;
[K in keyof T['inputs']]: ExtensionInput<
T['inputs'][K] extends ExtensionInput<infer IData> ? IData : never
>;
};
override<
@@ -238,10 +256,17 @@ export type ExtensionDefinition<
UNewOutput extends ExtensionDataRef,
TExtraInputs extends { [inputName in string]: ExtensionInput },
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
>(
args: Expand<
{
attachTo?: ExtensionDefinitionAttachTo;
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: UNewOutput,
UParentInputs
>;
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof T['inputs']]?: `Error: Input '${KName &
@@ -389,6 +414,7 @@ export function createExtension<
UFactoryOutput extends ExtensionDataValue<any, any>,
const TKind extends string | undefined = undefined,
const TName extends string | undefined = undefined,
UParentInputs extends ExtensionDataRef = ExtensionDataRef,
>(
options: CreateExtensionOptions<
TKind,
@@ -396,7 +422,8 @@ export function createExtension<
UOutput,
TInputs,
TConfigSchema,
UFactoryOutput
UFactoryOutput,
UParentInputs
>,
): ExtensionDefinition<{
config: string extends keyof TConfigSchema
@@ -474,7 +501,8 @@ export function createExtension<
}
const attachTo = [options.attachTo]
.flat()
.map(a => {
.map(aAny => {
const a = aAny as ExtensionDefinitionAttachTo;
if (OpaqueExtensionInput.isType(a)) {
const { context } = OpaqueExtensionInput.toInternal(a);
if (!context) {
@@ -533,7 +561,8 @@ export function createExtension<
return createExtension({
kind: options.kind,
name: options.name,
attachTo: overrideOptions.attachTo ?? options.attachTo,
attachTo: (overrideOptions.attachTo ??
options.attachTo) as ExtensionDefinitionAttachTo,
disabled: overrideOptions.disabled ?? options.disabled,
inputs: bindInputs(
{
@@ -1463,6 +1463,115 @@ describe('createExtensionBlueprint', () => {
'ExtensionDefinition{kind=test,attachTo=page:<plugin>/index@otherTabs}',
);
});
it('should provide type safe attachments by reference', () => {
const stringDataRef = createExtensionDataRef<string>().with({
id: 'test.string',
});
const numberDataRef = createExtensionDataRef<number>().with({
id: 'test.number',
});
const parent = createExtensionBlueprint({
kind: 'test-parent',
attachTo: { id: 'root', input: 'children' },
inputs: {
string: createExtensionInput([stringDataRef]),
stringOpt: createExtensionInput([stringDataRef.optional()]),
number: createExtensionInput([numberDataRef]),
numberOpt: createExtensionInput([numberDataRef.optional()]),
both: createExtensionInput([stringDataRef, numberDataRef]),
bothOptString: createExtensionInput([
stringDataRef.optional(),
numberDataRef,
]),
bothOptNumber: createExtensionInput([
stringDataRef,
numberDataRef.optional(),
]),
bothOpt: createExtensionInput([
stringDataRef.optional(),
numberDataRef.optional(),
]),
},
output: [],
factory: () => [],
}).make({ params: {} });
const strOutExt = createExtensionBlueprint({
kind: 'test',
attachTo: parent.inputs.string,
output: [stringDataRef],
factory: () => [stringDataRef('str')],
});
strOutExt.make({
attachTo: parent.inputs.string,
params: {},
});
strOutExt.makeWithOverrides({
attachTo: parent.inputs.stringOpt,
factory: orig => orig({}),
});
strOutExt.make({
// @ts-expect-error
attachTo: parent.inputs.number,
params: {},
});
strOutExt.makeWithOverrides({
attachTo: parent.inputs.numberOpt,
factory: orig => orig({}),
});
strOutExt.make({
// @ts-expect-error
attachTo: parent.inputs.both,
params: {},
});
strOutExt.make({
attachTo: parent.inputs.bothOptNumber,
params: {},
});
strOutExt.make({
// @ts-expect-error
attachTo: parent.inputs.bothOptString,
params: {},
});
strOutExt.make({
attachTo: parent.inputs.bothOpt,
params: {},
});
const numberOutExt = createExtensionBlueprint({
kind: 'test',
// @ts-expect-error
attachTo: parent.inputs.string,
output: [numberDataRef],
factory: () => [numberDataRef(1)],
});
numberOutExt.make({
// @ts-expect-error
attachTo: parent.inputs.string,
params: {},
});
numberOutExt.makeWithOverrides({
attachTo: parent.inputs.number,
factory: orig => orig({}),
});
const bothOutExt = createExtensionBlueprint({
kind: 'test',
attachTo: parent.inputs.both,
output: [numberDataRef, stringDataRef],
factory: () => [numberDataRef(1), stringDataRef('str')],
});
bothOutExt.makeWithOverrides({
output: [numberDataRef.optional(), stringDataRef],
factory: orig => orig({}),
});
bothOutExt.makeWithOverrides({
// @ts-expect-error
attachTo: parent.inputs.both,
output: [numberDataRef.optional(), stringDataRef],
factory: orig => orig({}),
});
expect('types').not.toBe('broken');
});
});
describe('with advanced parameter types', () => {
@@ -24,6 +24,7 @@ import {
VerifyExtensionFactoryOutput,
createExtension,
ctxParamsSymbol,
VerifyExtensionAttachTo,
} from './createExtension';
import { z } from 'zod';
import { ExtensionInput } from './createExtensionInput';
@@ -107,9 +108,11 @@ export type CreateExtensionBlueprintOptions<
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends { [name in string]: ExtensionDataRef },
UParentInputs extends ExtensionDataRef,
> = {
kind: TKind;
attachTo: ExtensionDefinitionAttachTo;
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
@@ -212,9 +215,11 @@ export interface ExtensionBlueprint<
make<
TName extends string | undefined,
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
>(args: {
name?: TName;
attachTo?: ExtensionDefinitionAttachTo;
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<NonNullable<T['output']>, UParentInputs>;
disabled?: boolean;
params: TParamsInput extends ExtensionBlueprintDefineParams
? TParamsInput
@@ -245,9 +250,16 @@ export interface ExtensionBlueprint<
UFactoryOutput extends ExtensionDataValue<any, any>,
UNewOutput extends ExtensionDataRef,
TExtraInputs extends { [inputName in string]: ExtensionInput },
UParentInputs extends ExtensionDataRef,
>(args: {
name?: TName;
attachTo?: ExtensionDefinitionAttachTo;
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: UNewOutput,
UParentInputs
>;
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof T['inputs']]?: `Error: Input '${KName &
@@ -444,6 +456,7 @@ export function createExtensionBlueprint<
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
TKind extends string,
UParentInputs extends ExtensionDataRef,
TDataRefs extends { [name in string]: ExtensionDataRef } = never,
>(
options: CreateExtensionBlueprintOptions<
@@ -453,7 +466,8 @@ export function createExtensionBlueprint<
TInputs,
TConfigSchema,
UFactoryOutput,
TDataRefs
TDataRefs,
UParentInputs
>,
): ExtensionBlueprint<{
kind: TKind;
@@ -489,7 +503,8 @@ export function createExtensionBlueprint<
return createExtension({
kind: options.kind,
name: args.name,
attachTo: args.attachTo ?? options.attachTo,
attachTo: (args.attachTo ??
options.attachTo) as ExtensionDefinitionAttachTo,
disabled: args.disabled ?? options.disabled,
inputs: options.inputs,
output: options.output as ExtensionDataRef[],
@@ -505,7 +520,8 @@ export function createExtensionBlueprint<
return createExtension({
kind: options.kind,
name: args.name,
attachTo: args.attachTo ?? options.attachTo,
attachTo: (args.attachTo ??
options.attachTo) as ExtensionDefinitionAttachTo,
disabled: args.disabled ?? options.disabled,
inputs: { ...args.inputs, ...options.inputs },
output: (args.output ?? options.output) as ExtensionDataRef[],
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { OpaqueExtensionInput } from '@internal/frontend';
import { createExtensionDataRef } from './createExtensionDataRef';
import { ExtensionInput, createExtensionInput } from './createExtensionInput';
@@ -29,6 +30,7 @@ describe('createExtensionInput', () => {
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: false, optional: false },
withContext: expect.any(Function),
});
const x1: ExtensionInput<
@@ -54,6 +56,20 @@ describe('createExtensionInput', () => {
unused(x1, x2, x3, x4);
});
it('should attach a context to the input', () => {
const input = createExtensionInput([stringDataRef, numberDataRef]);
const context = { input: 'test1', kind: 'test2', name: 'test3' };
const inputWithContext =
OpaqueExtensionInput.toInternal(input).withContext(context);
expect(inputWithContext).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: false, optional: false },
withContext: expect.any(Function),
context,
});
});
it('should create a singleton input', () => {
const input = createExtensionInput([stringDataRef, numberDataRef], {
singleton: true,
@@ -62,6 +78,7 @@ describe('createExtensionInput', () => {
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: true, optional: false },
withContext: expect.any(Function),
});
const x1: ExtensionInput<
@@ -96,6 +113,7 @@ describe('createExtensionInput', () => {
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: true, optional: true },
withContext: expect.any(Function),
});
const x1: ExtensionInput<
@@ -170,7 +170,6 @@ describe('createFrontendPlugin', () => {
"override": [Function],
"toString": [Function],
"version": "v2",
Symbol(@backstage/ExtensionDefinition/internalInputs): {},
}
`);
// @ts-expect-error
@@ -359,7 +358,6 @@ describe('createFrontendPlugin', () => {
"override": [Function],
"toString": [Function],
"version": "v2",
Symbol(@backstage/ExtensionDefinition/internalInputs): {},
}
`);
});
@@ -18,9 +18,8 @@ export { coreExtensionData } from './coreExtensionData';
export {
createExtension,
type ExtensionDefinition,
type ExtensionDefinitionParameters,
type ExtensionDefinitionAttachTo,
type ExtensionInputRef,
type ExtensionDefinitionParameters,
type CreateExtensionOptions,
type ResolvedExtensionInput,
type ResolvedExtensionInputs,
@@ -14,11 +14,21 @@
* limitations under the License.
*/
import {
createExtensionDataRef,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import { ExtensionDefinition } from './createExtension';
import {
ResolveExtensionId,
resolveExtensionDefinition,
} from './resolveExtensionDefinition';
import {
OpaqueExtensionDefinition,
OpaqueExtensionInput,
} from '@internal/frontend';
const testDataRef = createExtensionDataRef<string>().with({ id: 'test' });
describe('resolveExtensionDefinition', () => {
const baseDef = {
@@ -26,8 +36,8 @@ describe('resolveExtensionDefinition', () => {
T: undefined as any,
version: 'v2',
attachTo: { id: '', input: '' },
disabled: false,
inputs: {},
disabled: false,
override: () => ({} as ExtensionDefinition),
};
@@ -62,99 +72,108 @@ describe('resolveExtensionDefinition', () => {
);
});
it('should resolve relative attachment points', () => {
const resolved = resolveExtensionDefinition(
{
...baseDef,
attachTo: [
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
],
} as ExtensionDefinition,
{ namespace: 'test' },
);
expect(resolved.attachTo).toEqual([
{ id: 'page:test', input: 'tabs' },
{ id: 'page:test/index', input: 'tabs' },
]);
});
it('should resolve extension input references', () => {
const resolved = resolveExtensionDefinition(
{
...baseDef,
attachTo: {
$$type: '@backstage/ExtensionInputRef',
kind: 'parent',
name: 'example',
input: 'children',
},
} as ExtensionDefinition,
{ namespace: 'test' },
const baseInpuf = OpaqueExtensionInput.toInternal(
createExtensionInput([testDataRef]),
);
expect(resolved.attachTo).toEqual({
id: 'parent:test/example',
input: 'children',
});
});
it('should resolve extension input references without name', () => {
const resolved = resolveExtensionDefinition(
{
...baseDef,
attachTo: {
$$type: '@backstage/ExtensionInputRef',
kind: 'parent',
input: 'children',
},
} as ExtensionDefinition,
{ namespace: 'test' },
);
expect(resolved.attachTo).toEqual({
id: 'parent:test',
input: 'children',
});
});
it('should resolve extension input references without kind', () => {
const resolved = resolveExtensionDefinition(
{
...baseDef,
attachTo: {
$$type: '@backstage/ExtensionInputRef',
name: 'example',
input: 'children',
},
} as ExtensionDefinition,
{ namespace: 'test' },
);
expect(resolved.attachTo).toEqual({
id: 'test/example',
input: 'children',
});
});
it('should resolve array with mixed attachment types including input references', () => {
const resolved = resolveExtensionDefinition(
{
...baseDef,
attachTo: [
{ id: 'page:home', input: 'widgets' },
{ relative: { kind: 'page' }, input: 'actions' },
{
$$type: '@backstage/ExtensionInputRef',
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
...baseDef,
attachTo: baseInpuf.withContext({
kind: 'parent',
name: 'example',
input: 'children',
},
],
} as ExtensionDefinition,
{ namespace: 'test' },
);
expect(resolved.attachTo).toEqual([
{ id: 'page:home', input: 'widgets' },
{ id: 'page:test', input: 'actions' },
{ id: 'parent:test/example', input: 'children' },
}),
}),
{ namespace: 'test' },
).attachTo,
).toEqual({
id: 'parent:test/example',
input: 'children',
});
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
...baseDef,
attachTo: baseInpuf.withContext({
name: 'example',
input: 'children',
}),
}),
{ namespace: 'test' },
).attachTo,
).toEqual({
id: 'test/example',
input: 'children',
});
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
...baseDef,
attachTo: baseInpuf.withContext({
kind: 'parent',
input: 'children',
}),
}),
{ namespace: 'test' },
).attachTo,
).toEqual({
id: 'parent:test',
input: 'children',
});
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
...baseDef,
attachTo: baseInpuf.withContext({
input: 'children',
}),
}),
{ namespace: 'test' },
).attachTo,
).toEqual({
id: 'test',
input: 'children',
});
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
...baseDef,
attachTo: [
baseInpuf.withContext({
kind: 'k1',
input: 'children',
}),
baseInpuf.withContext({
kind: 'k2',
input: 'children',
}),
baseInpuf.withContext({
kind: 'k3',
input: 'children',
}),
],
}),
{ namespace: 'test' },
).attachTo,
).toEqual([
{
id: 'k1:test',
input: 'children',
},
{
id: 'k2:test',
input: 'children',
},
{
id: 'k3:test',
input: 'children',
},
]);
});
});
@@ -165,8 +184,8 @@ describe('old resolveExtensionDefinition', () => {
T: undefined as any,
version: 'v1',
attachTo: { id: '', input: '' },
disabled: false,
inputs: {},
disabled: false,
override: () => ({} as ExtensionDefinition),
};
@@ -199,18 +199,6 @@ export function resolveExtensionDefinition<
): Extension<T['config'], T['configInput']> {
const internalDefinition = OpaqueExtensionDefinition.toInternal(definition);
// Restore internal inputs if they were saved under a symbol
// This is needed because we override the inputs property with ExtensionInputRef objects
const internalInputsSymbol = Symbol.for(
'@backstage/ExtensionDefinition/internalInputs',
);
if ((internalDefinition as any)[internalInputsSymbol]) {
// Restore the internal inputs from the symbol
(internalDefinition as any).inputs = (internalDefinition as any)[
internalInputsSymbol
];
}
const {
name,
kind,