frontend-plugin-api: add initial support for v2 extensions for blueprints

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-07-28 21:24:03 +02:00
parent 2ba97666dd
commit 47bc4046c5
8 changed files with 235 additions and 160 deletions
+2 -6
View File
@@ -11,13 +11,9 @@ This allows the creation of extension instances with the following pattern:
const EntityCardBlueprint = createExtensionBlueprint({
kind: 'entity-card',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
factory(params: { text: string }) {
return {
element: <h1>{params.text}</h1>,
};
return [coreExtensionData.reactElement(<h1>{params.text}</h1>)];
},
});
+86 -57
View File
@@ -593,24 +593,38 @@ export function createExtension<
// @public
export function createExtensionBlueprint<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{
optional: boolean;
singleton: boolean;
}
>;
},
UExtraOutput extends AnyExtensionDataRef,
TConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
TDataRefs extends AnyExtensionDataMap = never,
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends {
[name in string]: AnyExtensionDataRef;
} = never,
>(
options: CreateExtensionBlueprintOptions<
TParams,
UOutput,
TInputs,
TOutput,
TConfigSchema,
UFactoryOutput,
TDataRefs
>,
): ExtensionBlueprint<
TParams,
UOutput,
TInputs,
TOutput,
UExtraOutput,
string extends keyof TConfigSchema
? {}
: {
@@ -627,29 +641,38 @@ export function createExtensionBlueprint<
>;
// @public (undocumented)
export interface CreateExtensionBlueprintOptions<
export type CreateExtensionBlueprintOptions<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{
optional: boolean;
singleton: boolean;
}
>;
},
TConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
TDataRefs extends AnyExtensionDataMap,
> {
// (undocumented)
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends {
[name in string]: AnyExtensionDataRef;
},
> = {
kind: string;
namespace?: string;
attachTo: {
id: string;
input: string;
};
// (undocumented)
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
config?: {
schema: TConfigSchema;
};
// (undocumented)
dataRefs?: TDataRefs;
// (undocumented)
disabled?: boolean;
// (undocumented)
factory(
params: TParams,
context: {
@@ -659,16 +682,9 @@ export interface CreateExtensionBlueprintOptions<
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Expand<ExtensionDataValues<TOutput>>;
// (undocumented)
inputs?: TInputs;
// (undocumented)
kind: string;
// (undocumented)
namespace?: string;
// (undocumented)
output: TOutput;
}
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
// @public @deprecated (undocumented)
export function createExtensionDataRef<TData>(
@@ -769,22 +785,7 @@ export type CreateExtensionOptions<
});
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & ((
UOutput extends any
? UOutput['config']['optional'] extends true
? never
: UOutput['id']
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? {}
: {
'Error: The extension factory is missing the following outputs': Exclude<
IRequiredOutputIds,
UFactoryOutput['id']
>;
}
: never);
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
// @public (undocumented)
export function createExtensionOverrides(
@@ -1081,15 +1082,26 @@ export interface Extension<TConfig, TConfigInput = TConfig> {
// @public (undocumented)
export interface ExtensionBlueprint<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{
optional: boolean;
singleton: boolean;
}
>;
},
UExtraOutput extends AnyExtensionDataRef,
TConfig extends {
[key in string]: unknown;
},
TConfigInput extends {
[key in string]: unknown;
},
TDataRefs extends AnyExtensionDataMap,
TDataRefs extends {
[name in string]: AnyExtensionDataRef;
},
> {
// (undocumented)
dataRefs: TDataRefs;
@@ -1097,6 +1109,7 @@ export interface ExtensionBlueprint<
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
args: {
namespace?: string;
@@ -1107,7 +1120,7 @@ export interface ExtensionBlueprint<
};
disabled?: boolean;
inputs?: TInputs;
output?: TOutput;
output?: Array<UExtraOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
@@ -1115,7 +1128,7 @@ export interface ExtensionBlueprint<
};
};
} & (
| {
| ({
factory(
originalFactory: (
params: TParams,
@@ -1123,7 +1136,7 @@ export interface ExtensionBlueprint<
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => Expand<ExtensionDataValues<TOutput>>,
) => Iterable<ExtensionDataRefToValue<UOutput>>,
context: {
node: AppNode;
config: TConfig & {
@@ -1133,8 +1146,11 @@ export interface ExtensionBlueprint<
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Expand<ExtensionDataValues<TOutput>>;
}
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
UOutput & UExtraOutput,
UFactoryOutput
>)
| {
params: TParams;
}
@@ -1196,6 +1212,12 @@ export type ExtensionDataRef<
readonly config: TConfig;
};
// @public (undocumented)
export type ExtensionDataRefToValue<TDataRef extends AnyExtensionDataRef> =
TDataRef extends ExtensionDataRef<infer IData, infer IId, any>
? ExtensionDataValue<IData, IId>
: never;
// @public (undocumented)
export type ExtensionDataValue<TData, TId extends string> = {
readonly $$type: '@backstage/ExtensionDataValue';
@@ -1323,16 +1345,23 @@ export const IconBundleBlueprint: ExtensionBlueprint<
[x: string]: IconComponent;
};
},
AnyExtensionInputMap,
ConfigurableExtensionDataRef<
{
[x: string]: IconComponent;
},
'core.icons',
{}
>,
{
icons: ConfigurableExtensionDataRef<
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
[x: string]: IconComponent;
},
'core.icons',
{}
optional: boolean;
singleton: boolean;
}
>;
},
AnyExtensionDataRef,
{
icons: string;
test: string;
@@ -25,16 +25,16 @@ export const IconBundleBlueprint = createExtensionBlueprint({
kind: 'icon-bundle',
namespace: 'app',
attachTo: { id: 'app', input: 'icons' },
output: {
icons: iconsDataRef,
},
output: [iconsDataRef],
config: {
schema: {
icons: z => z.string().default('blob'),
test: z => z.string(),
},
},
factory: (params: { icons: { [key in string]: IconComponent } }) => params,
factory: (params: { icons: { [key in string]: IconComponent } }) => [
iconsDataRef(params.icons),
],
dataRefs: {
icons: iconsDataRef,
},
@@ -145,6 +145,27 @@ export interface LegacyCreateExtensionOptions<
}): Expand<ExtensionDataValues<TOutput>>;
}
/** @ignore */
export type VerifyExtensionFactoryOutput<
UDeclaredOutput extends AnyExtensionDataRef,
UFactoryOutput extends ExtensionDataValue<any, any>,
> = (
UDeclaredOutput extends any
? UDeclaredOutput['config']['optional'] extends true
? never
: UDeclaredOutput['id']
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? {}
: {
'Error: The extension factory is missing the following outputs': Exclude<
IRequiredOutputIds,
UFactoryOutput['id']
>;
}
: never;
/** @public */
export type CreateExtensionOptions<
UOutput extends AnyExtensionDataRef,
@@ -183,22 +204,7 @@ export type CreateExtensionOptions<
});
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & ((
UOutput extends any
? UOutput['config']['optional'] extends true
? never
: UOutput['id']
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? {}
: {
'Error: The extension factory is missing the following outputs': Exclude<
IRequiredOutputIds,
UFactoryOutput['id']
>;
}
: never);
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/** @public */
export interface ExtensionDefinition<TConfig, TConfigInput = TConfig> {
@@ -27,13 +27,9 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
factory(params: { text: string }) {
return {
element: <h1>{params.text}</h1>,
};
return [coreExtensionData.reactElement(<h1>{params.text}</h1>)];
},
});
@@ -56,12 +52,10 @@ describe('createExtensionBlueprint', () => {
kind: 'test-extension',
name: 'my-extension',
namespace: undefined,
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
factory: expect.any(Function),
toString: expect.any(Function),
version: 'v1',
version: 'v2',
});
const { container } = createExtensionTester(extension).render();
@@ -72,13 +66,9 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
factory(params: { text: string }) {
return {
element: <h1>{params.text}</h1>,
};
return [coreExtensionData.reactElement(<h1>{params.text}</h1>)];
},
});
@@ -103,16 +93,12 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
dataRefs: {
data: dataRef,
},
factory(params: { text: string }) {
return {
element: <h1>{params.text}</h1>,
};
return [coreExtensionData.reactElement(<h1>{params.text}</h1>)];
},
});
@@ -125,9 +111,7 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
config: {
schema: {
text: z => z.string(),
@@ -142,9 +126,7 @@ describe('createExtensionBlueprint', () => {
expect(config.text).toBe('Hello, world!');
return {
element: <h1>{config.text}</h1>,
};
return [coreExtensionData.reactElement(<h1>{config.text}</h1>)];
},
});
@@ -188,18 +170,14 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
config: {
schema: {
text: z => z.string(),
},
},
factory(params: { text: string }) {
return {
element: <div>{params.text}</div>,
};
return [coreExtensionData.reactElement(<div>{params.text}</div>)];
},
});
@@ -224,16 +202,12 @@ describe('createExtensionBlueprint', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: {
element: coreExtensionData.reactElement,
},
output: [coreExtensionData.reactElement],
factory(_, { config }) {
// @ts-expect-error
const b = config.something;
return {
element: <div />,
};
return [coreExtensionData.reactElement(<div />)];
},
});
@@ -17,31 +17,42 @@
import { AppNode } from '../apis';
import { Expand } from '../types';
import {
AnyExtensionDataMap,
AnyExtensionInputMap,
ExtensionDataValues,
CreateExtensionOptions,
ExtensionDefinition,
ResolvedExtensionInputs,
VerifyExtensionFactoryOutput,
createExtension,
} from './createExtension';
import { z } from 'zod';
import { ExtensionInput } from './createExtensionInput';
import {
AnyExtensionDataRef,
ExtensionDataRefToValue,
ExtensionDataValue,
} from './createExtensionDataRef';
/**
* @public
*/
export interface CreateExtensionBlueprintOptions<
export type CreateExtensionBlueprintOptions<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
TDataRefs extends AnyExtensionDataMap,
> {
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends { [name in string]: AnyExtensionDataRef },
> = {
kind: string;
namespace?: string;
attachTo: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output: TOutput;
output: Array<UOutput>;
config?: {
schema: TConfigSchema;
};
@@ -54,21 +65,27 @@ export interface CreateExtensionBlueprintOptions<
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Expand<ExtensionDataValues<TOutput>>;
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
}
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/**
* @public
*/
export interface ExtensionBlueprint<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
UExtraOutput extends AnyExtensionDataRef,
TConfig extends { [key in string]: unknown },
TConfigInput extends { [key in string]: unknown },
TDataRefs extends AnyExtensionDataMap,
TDataRefs extends { [name in string]: AnyExtensionDataRef },
> {
dataRefs: TDataRefs;
@@ -82,6 +99,7 @@ export interface ExtensionBlueprint<
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
args: {
namespace?: string;
@@ -89,7 +107,7 @@ export interface ExtensionBlueprint<
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output?: TOutput;
output?: Array<UExtraOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
@@ -97,7 +115,7 @@ export interface ExtensionBlueprint<
};
};
} & (
| {
| ({
factory(
originalFactory: (
params: TParams,
@@ -105,7 +123,7 @@ export interface ExtensionBlueprint<
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => Expand<ExtensionDataValues<TOutput>>,
) => Iterable<ExtensionDataRefToValue<UOutput>>,
context: {
node: AppNode;
config: TConfig & {
@@ -115,8 +133,11 @@ export interface ExtensionBlueprint<
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Expand<ExtensionDataValues<TOutput>>;
}
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
UOutput & UExtraOutput,
UFactoryOutput
>)
| {
params: TParams;
}
@@ -143,17 +164,24 @@ export interface ExtensionBlueprint<
*/
class ExtensionBlueprintImpl<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
UExtraOutput extends AnyExtensionDataRef,
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
TDataRefs extends AnyExtensionDataMap,
TDataRefs extends { [name in string]: AnyExtensionDataRef },
> {
constructor(
private readonly options: CreateExtensionBlueprintOptions<
TParams,
UOutput,
TInputs,
TOutput,
TConfigSchema,
any,
TDataRefs
>,
) {
@@ -166,13 +194,14 @@ class ExtensionBlueprintImpl<
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
UFactoryOutput extends ExtensionDataValue<any, any>,
>(args: {
namespace?: string;
name?: string;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output?: TOutput;
output?: Array<UExtraOutput>;
params?: TParams;
config?: {
schema: TExtensionConfigSchema;
@@ -188,7 +217,7 @@ class ExtensionBlueprintImpl<
};
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => Expand<ExtensionDataValues<TOutput>>,
) => Iterable<ExtensionDataRefToValue<UOutput>>,
context: {
node: AppNode;
config: {
@@ -200,7 +229,7 @@ class ExtensionBlueprintImpl<
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Expand<ExtensionDataValues<TOutput>>;
): Iterable<UFactoryOutput>;
}): ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
@@ -232,7 +261,7 @@ class ExtensionBlueprintImpl<
attachTo: args.attachTo ?? this.options.attachTo,
disabled: args.disabled ?? this.options.disabled,
inputs: args.inputs ?? this.options.inputs,
output: args.output ?? this.options.output,
output: [...(args.output ?? []), ...this.options.output],
config: Object.keys(schema).length === 0 ? undefined : { schema },
factory: ({ node, config, inputs }) => {
if (args.factory) {
@@ -247,12 +276,13 @@ class ExtensionBlueprintImpl<
};
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) =>
this.options.factory(innerParams, {
): Iterable<ExtensionDataRefToValue<UOutput>> => {
return this.options.factory(innerParams, {
node,
config: innerContext?.config ?? config,
inputs: innerContext?.inputs ?? inputs,
}),
});
},
{
node,
config,
@@ -268,7 +298,30 @@ class ExtensionBlueprintImpl<
}
throw new Error('Either params or factory must be provided');
},
});
} as CreateExtensionOptions<
UOutput,
TInputs,
{
[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]>;
}
>
>,
TConfigSchema,
UFactoryOutput
>);
}
}
@@ -280,22 +333,31 @@ class ExtensionBlueprintImpl<
*/
export function createExtensionBlueprint<
TParams,
TInputs extends AnyExtensionInputMap,
TOutput extends AnyExtensionDataMap,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
UExtraOutput extends AnyExtensionDataRef,
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
TDataRefs extends AnyExtensionDataMap = never,
UFactoryOutput extends ExtensionDataValue<any, any>,
TDataRefs extends { [name in string]: AnyExtensionDataRef } = never,
>(
options: CreateExtensionBlueprintOptions<
TParams,
UOutput,
TInputs,
TOutput,
TConfigSchema,
UFactoryOutput,
TDataRefs
>,
): ExtensionBlueprint<
TParams,
UOutput,
TInputs,
TOutput,
UExtraOutput,
string extends keyof TConfigSchema
? {}
: { [key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>> },
@@ -310,8 +372,9 @@ export function createExtensionBlueprint<
> {
return new ExtensionBlueprintImpl(options) as ExtensionBlueprint<
TParams,
UOutput,
TInputs,
TOutput,
UExtraOutput,
string extends keyof TConfigSchema
? {}
: {
@@ -33,6 +33,12 @@ export type ExtensionDataRef<
readonly config: TConfig;
};
/** @public */
export type ExtensionDataRefToValue<TDataRef extends AnyExtensionDataRef> =
TDataRef extends ExtensionDataRef<infer IData, infer IId, any>
? ExtensionDataValue<IData, IId>
: never;
/** @public */
export type AnyExtensionDataRef = ExtensionDataRef<
unknown,
@@ -36,6 +36,7 @@ export {
createExtensionDataRef,
type AnyExtensionDataRef,
type ExtensionDataRef,
type ExtensionDataRefToValue,
type ExtensionDataValue,
type ConfigurableExtensionDataRef,
} from './createExtensionDataRef';