frontend-plugin-api: refactor ExtensionDefinition and ExtensionBlueprint type params
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user