frontend-plugin-api: remove support for v1 extensions

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-19 14:48:25 +02:00
parent 821f065ee9
commit af5796f5c3
8 changed files with 236 additions and 453 deletions
@@ -21,6 +21,9 @@ import { createExtensionInput } from './createExtensionInput';
const stringDataRef = createExtensionDataRef<string>().with({ id: 'string' });
const numberDataRef = createExtensionDataRef<number>().with({ id: 'number' });
const booleanDataRef = createExtensionDataRef<boolean>().with({
id: 'boolean',
});
function unused(..._any: any[]) {}
@@ -29,42 +32,35 @@ describe('createExtension', () => {
const baseConfig = {
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: {
foo: stringDataRef,
},
output: [stringDataRef],
};
const extension = createExtension({
...baseConfig,
factory() {
return {
foo: 'bar',
};
return [stringDataRef('bar')];
},
});
expect(extension).toMatchObject({ version: 'v1', namespace: 'test' });
expect(extension).toMatchObject({ version: 'v2', namespace: 'test' });
// When declared as an error function without a block the TypeScript errors
// are a more specific and will often point at the property that is problematic.
// Member arrow function declaration
createExtension({
...baseConfig,
factory: () => [
stringDataRef(
// @ts-expect-error
3,
),
],
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () => ({
foo: 3,
}),
factory: () => [numberDataRef(3)],
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () =>
// @ts-expect-error
({
bar: 'bar',
}),
});
createExtension({
...baseConfig,
factory: () =>
// @ts-expect-error
({}),
factory: () => [],
});
createExtension({
...baseConfig,
@@ -79,39 +75,37 @@ describe('createExtension', () => {
'bar',
});
// When declared as a function with a block the TypeScript error will instead
// be tied to the factory function declaration itself, but the error messages
// is still helpful and points to part of the return type that is problematic.
// Method declaration
createExtension({
...baseConfig,
// @ts-expect-error
factory() {
return {
foo: 3,
};
return [
stringDataRef(
// @ts-expect-error
3,
),
];
},
});
// @ts-expect-error
createExtension({
...baseConfig,
factory() {
return [numberDataRef(3)];
},
});
// @ts-expect-error
createExtension({
...baseConfig,
factory() {
return [];
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory() {
return {
bar: 'bar',
};
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory() {
return {};
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory() {
return {};
return;
},
});
createExtension({
@@ -122,36 +116,37 @@ describe('createExtension', () => {
},
});
// Member function declaration
createExtension({
...baseConfig,
// @ts-expect-error
factory: () => {
return {
foo: 3,
};
return [
stringDataRef(
// @ts-expect-error
3,
),
];
},
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () => {
return [numberDataRef(3)];
},
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () => {
return [];
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory: () => {
return {
bar: 'bar',
};
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory: () => {
return {};
},
});
createExtension({
...baseConfig,
// @ts-expect-error
factory: () => {
return {};
return;
},
});
createExtension({
@@ -167,52 +162,27 @@ describe('createExtension', () => {
const baseConfig = {
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: {
foo: stringDataRef,
bar: stringDataRef.optional(),
},
output: [stringDataRef, numberDataRef.optional()],
};
const extension = createExtension({
...baseConfig,
factory: () => ({
foo: 'bar',
}),
factory: () => [stringDataRef('bar')],
});
expect(extension).toMatchObject({ version: 'v1', namespace: 'test' });
expect(extension).toMatchObject({ version: 'v2', namespace: 'test' });
createExtension({
...baseConfig,
factory: () => ({
foo: 'bar',
bar: 'baz',
}),
factory: () => [stringDataRef('bar'), numberDataRef(3)],
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () => ({
foo: 3,
}),
factory: () => [numberDataRef(3)],
});
// @ts-expect-error
createExtension({
...baseConfig,
factory: () => ({
foo: 'bar',
bar: 3,
}),
});
createExtension({
...baseConfig,
factory: () =>
// @ts-expect-error
({ bar: 'bar' }),
});
createExtension({
...baseConfig,
factory: () =>
// @ts-expect-error
({}),
factory: () => [],
});
createExtension({
...baseConfig,
@@ -238,57 +208,48 @@ describe('createExtension', () => {
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
inputs: {
mixed: createExtensionInput({
required: stringDataRef,
optional: stringDataRef.optional(),
}),
onlyRequired: createExtensionInput({
required: stringDataRef,
}),
onlyOptional: createExtensionInput({
optional: stringDataRef.optional(),
}),
},
output: {
foo: stringDataRef,
mixed: createExtensionInput([stringDataRef, numberDataRef.optional()]),
onlyRequired: createExtensionInput([stringDataRef]),
onlyOptional: createExtensionInput([stringDataRef.optional()]),
},
output: [stringDataRef],
factory({ inputs }) {
const a1: string = inputs.mixed?.[0].output.required;
const a1: string = inputs.mixed?.[0].get(stringDataRef);
// @ts-expect-error
const a2: number = inputs.mixed?.[0].output.required;
const a2: number = inputs.mixed?.[0].get(stringDataRef);
// @ts-expect-error
const a3: any = inputs.mixed?.[0].output.nonExistent;
const a3: any = inputs.mixed?.[0].get(booleanDataRef);
unused(a1, a2, a3);
const b1: string | undefined = inputs.mixed?.[0].output.optional;
const b1: number | undefined = inputs.mixed?.[0].get(numberDataRef);
// @ts-expect-error
const b2: string = inputs.mixed?.[0].output.optional;
const b2: string = inputs.mixed?.[0].get(numberDataRef);
// @ts-expect-error
const b3: number = inputs.mixed?.[0].output.optional;
const b3: number = inputs.mixed?.[0].get(numberDataRef);
// @ts-expect-error
const b4: number | undefined = inputs.mixed?.[0].output.optional;
const b4: string | undefined = inputs.mixed?.[0].get(numberDataRef);
unused(b1, b2, b3, b4);
const c1: string = inputs.onlyRequired?.[0].output.required;
const c1: string = inputs.onlyRequired?.[0].get(stringDataRef);
// @ts-expect-error
const c2: number = inputs.onlyRequired?.[0].output.required;
const c2: number = inputs.onlyRequired?.[0].get(stringDataRef);
unused(c1, c2);
const d1: string | undefined = inputs.onlyOptional?.[0].output.optional;
const d1: string | undefined =
inputs.onlyOptional?.[0].get(stringDataRef);
// @ts-expect-error
const d2: string = inputs.onlyOptional?.[0].output.optional;
const d2: string = inputs.onlyOptional?.[0].get(stringDataRef);
// @ts-expect-error
const d3: number = inputs.onlyOptional?.[0].output.optional;
const d3: number = inputs.onlyOptional?.[0].get(stringDataRef);
// @ts-expect-error
const d4: number | undefined = inputs.onlyOptional?.[0].output.optional;
const d4: number | undefined =
inputs.onlyOptional?.[0].get(stringDataRef);
unused(d1, d2, d3, d4);
return {
foo: 'bar',
};
return [stringDataRef('bar')];
},
});
expect(extension).toMatchObject({ version: 'v1', namespace: 'test' });
expect(extension).toMatchObject({ version: 'v2', namespace: 'test' });
expect(String(extension)).toBe(
'ExtensionDefinition{namespace=test,attachTo=root@default}',
);
@@ -15,7 +15,7 @@
*/
import { AppNode } from '../apis';
import { PortableSchema, createSchemaFromZod } from '../schema';
import { PortableSchema } from '../schema';
import { Expand } from '../types';
import {
ResolveInputValueOverrides,
@@ -29,46 +29,9 @@ import {
AnyExtensionDataRef,
ExtensionDataValue,
} from './createExtensionDataRef';
import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput';
import { ExtensionInput } from './createExtensionInput';
import { z } from 'zod';
/**
* @public
* @deprecated Extension data maps will be removed.
*/
export type AnyExtensionDataMap = {
[name in string]: AnyExtensionDataRef;
};
/**
* @public
* @deprecated This type will be removed.
*/
export type AnyExtensionInputMap = {
[inputName in string]: LegacyExtensionInput<
AnyExtensionDataMap,
{ optional: boolean; singleton: boolean }
>;
};
/**
* Converts an extension data map into the matching concrete data values type.
* @public
* @deprecated Extension data maps will be removed.
*/
export type ExtensionDataValues<TExtensionData extends AnyExtensionDataMap> = {
[DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends {
optional: true;
}
? never
: DataName]: TExtensionData[DataName]['T'];
} & {
[DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends {
optional: true;
}
? DataName
: never]?: TExtensionData[DataName]['T'];
};
import { createSchemaFromZod } from '../schema/createSchemaFromZod';
/**
* Convert a single extension input into a matching resolved input.
@@ -80,11 +43,6 @@ export type ResolvedExtensionInput<
? {
node: AppNode;
} & ExtensionDataContainer<TExtensionInput['extensionData'][number]>
: TExtensionInput['extensionData'] extends AnyExtensionDataMap
? {
node: AppNode;
output: ExtensionDataValues<TExtensionInput['extensionData']>;
}
: never;
/**
@@ -93,7 +51,7 @@ export type ResolvedExtensionInput<
*/
export type ResolvedExtensionInputs<
TInputs extends {
[name in string]: ExtensionInput<any, any> | LegacyExtensionInput<any, any>;
[name in string]: ExtensionInput<any, any>;
},
> = {
[InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton']
@@ -103,31 +61,6 @@ export type ResolvedExtensionInputs<
: Expand<ResolvedExtensionInput<TInputs[InputName]> | undefined>;
};
/**
* @public
* @deprecated This way of structuring the options is deprecated, this type will be removed in the future
*/
export interface LegacyCreateExtensionOptions<
TOutput extends AnyExtensionDataMap,
TInputs extends AnyExtensionInputMap,
TConfig,
TConfigInput,
> {
kind?: string;
namespace?: string;
name?: string;
attachTo: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output: TOutput;
configSchema?: PortableSchema<TConfig, TConfigInput>;
factory(context: {
node: AppNode;
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): Expand<ExtensionDataValues<TOutput>>;
}
type ToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
@@ -326,13 +259,27 @@ export type InternalExtensionDefinition<
(
| {
readonly version: 'v1';
readonly inputs: AnyExtensionInputMap;
readonly output: AnyExtensionDataMap;
readonly inputs: {
[inputName in string]: {
$$type: '@backstage/ExtensionInput';
extensionData: {
[name in string]: AnyExtensionDataRef;
};
config: { optional: boolean; singleton: boolean };
};
};
readonly output: {
[name in string]: AnyExtensionDataRef;
};
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<AnyExtensionInputMap>;
}): ExtensionDataValues<any>;
inputs: {
[inputName in string]: unknown;
};
}): {
[inputName in string]: unknown;
};
}
| {
readonly version: 'v2';
@@ -419,23 +366,6 @@ export function createExtension<
name: string | undefined extends TName ? undefined : TName;
}
>;
/**
* @public
* @deprecated - use the array format of `output` instead, see TODO-doc-link
*/
export function createExtension<
TOutput extends AnyExtensionDataMap,
TInputs extends AnyExtensionInputMap,
TConfig,
TConfigInput,
>(
options: LegacyCreateExtensionOptions<
TOutput,
TInputs,
TConfig,
TConfigInput
>,
): ExtensionDefinition<TConfig, TConfigInput, never, never>;
export function createExtension<
const TKind extends string | undefined,
const TNamespace extends string | undefined,
@@ -447,43 +377,31 @@ export function createExtension<
{ optional: boolean; singleton: boolean }
>;
},
TLegacyInputs extends AnyExtensionInputMap,
TConfig,
TConfigInput,
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
options:
| CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
UFactoryOutput
>
| LegacyCreateExtensionOptions<
AnyExtensionDataMap,
TLegacyInputs,
TConfig,
TConfigInput
>,
options: CreateExtensionOptions<
TKind,
TNamespace,
TName,
UOutput,
TInputs,
TConfigSchema,
UFactoryOutput
>,
): ExtensionDefinition<
TConfig &
(string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
}),
TConfigInput &
(string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>),
string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
@@ -492,29 +410,20 @@ export function createExtension<
name: TName;
}
> {
if ('configSchema' in options && 'config' in options) {
throw new Error(`Cannot provide both configSchema and config.schema`);
}
let configSchema: PortableSchema<any, any> | undefined;
if ('configSchema' in options) {
configSchema = options.configSchema;
}
if ('config' in options) {
const newConfigSchema = options.config?.schema;
configSchema =
newConfigSchema &&
createSchemaFromZod(innerZ =>
innerZ.object(
Object.fromEntries(
Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]),
),
const schemaDeclaration = options.config?.schema;
const configSchema =
schemaDeclaration &&
createSchemaFromZod(innerZ =>
innerZ.object(
Object.fromEntries(
Object.entries(schemaDeclaration).map(([k, v]) => [k, v(innerZ)]),
),
);
}
),
);
return {
$$type: '@backstage/ExtensionDefinition',
version: Symbol.iterator in options.output ? 'v2' : 'v1',
version: 'v2',
kind: options.kind,
namespace: options.namespace,
name: options.name,
@@ -687,22 +596,18 @@ export function createExtension<
} as CreateExtensionOptions<TKind, TNamespace, TName, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UOverrideFactoryOutput>);
},
} as InternalExtensionDefinition<
TConfig &
(string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<
ReturnType<TConfigSchema[key]>
>;
}),
TConfigInput &
(string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>),
string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
@@ -15,11 +15,7 @@
*/
import { createExtensionDataRef } from './createExtensionDataRef';
import {
ExtensionInput,
LegacyExtensionInput,
createExtensionInput,
} from './createExtensionInput';
import { ExtensionInput, createExtensionInput } from './createExtensionInput';
const stringDataRef = createExtensionDataRef<string>().with({ id: 'str' });
const numberDataRef = createExtensionDataRef<number>().with({ id: 'num' });
@@ -130,40 +126,4 @@ describe('createExtensionInput', () => {
createExtensionInput([stringDataRef, stringDataRef], { singleton: true }),
).toThrow("ExtensionInput may not have duplicate data refs: 'str'");
});
describe('old api', () => {
it('should create a regular input', () => {
const input = createExtensionInput({
str: stringDataRef,
num: numberDataRef,
});
expect(input).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: { str: stringDataRef, num: numberDataRef },
config: { singleton: false, optional: false },
});
const x1: LegacyExtensionInput<
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x2: LegacyExtensionInput<
{ str: typeof numberDataRef; num: typeof stringDataRef }, // switched
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x3: LegacyExtensionInput<
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: true; optional: false }
> = input;
// @ts-expect-error
const x4: LegacyExtensionInput<
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: false; optional: true }
> = input;
unused(x1, x2, x3, x4);
});
});
});
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { AnyExtensionDataMap } from './createExtension';
import { ExtensionDataRef } from './createExtensionDataRef';
/** @public */
@@ -27,36 +26,6 @@ export interface ExtensionInput<
config: TConfig;
}
/**
* @public
* @deprecated This type will be removed. Use `ExtensionInput` instead.
*/
export interface LegacyExtensionInput<
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton: boolean; optional: boolean },
> {
$$type: '@backstage/ExtensionInput';
extensionData: TExtensionDataMap;
config: TConfig;
}
/**
* @public
* @deprecated Use the following form instead: `createExtensionInput([dataRef1, dataRef2])`
*/
export function createExtensionInput<
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton?: boolean; optional?: boolean },
>(
extensionData: TExtensionDataMap,
config?: TConfig,
): LegacyExtensionInput<
TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>;
/** @public */
export function createExtensionInput<
UExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
@@ -73,26 +42,17 @@ export function createExtensionInput<
>;
export function createExtensionInput<
TExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton?: boolean; optional?: boolean },
>(
extensionData: Array<TExtensionData> | TExtensionDataMap,
extensionData: Array<TExtensionData>,
config?: TConfig,
):
| LegacyExtensionInput<
TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>
| ExtensionInput<
TExtensionData,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
> {
): ExtensionInput<
TExtensionData,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
> {
if (process.env.NODE_ENV !== 'production') {
if (Array.isArray(extensionData)) {
const seen = new Set();
@@ -124,19 +84,11 @@ export function createExtensionInput<
? true
: false,
},
} as
| LegacyExtensionInput<
TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>
| ExtensionInput<
TExtensionData,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>;
} as ExtensionInput<
TExtensionData,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>;
}
@@ -40,22 +40,22 @@ describe('createExtensionOverrides', () => {
createExtension({
name: 'a',
attachTo: { id: 'app', input: 'apis' },
output: {},
factory: () => ({}),
output: [],
factory: () => [],
}),
createExtension({
namespace: 'b',
attachTo: { id: 'app', input: 'apis' },
output: {},
factory: () => ({}),
output: [],
factory: () => [],
}),
createExtension({
kind: 'k',
namespace: 'c',
name: 'n',
attachTo: { id: 'app', input: 'apis' },
output: {},
factory: () => ({}),
output: [],
factory: () => [],
}),
],
}),
@@ -122,8 +122,8 @@ describe('createExtensionOverrides', () => {
createExtension({
namespace: 'a',
attachTo: { id: 'app', input: 'apis' },
output: {},
factory: () => ({}),
output: [],
factory: () => [],
}),
],
});
@@ -17,7 +17,6 @@
import React from 'react';
import { createApp } from '@backstage/frontend-app-api';
import { screen } from '@testing-library/react';
import { createSchemaFromZod } from '../schema/createSchemaFromZod';
import { createFrontendPlugin } from './createFrontendPlugin';
import { JsonObject } from '@backstage/types';
import { createExtension } from './createExtension';
@@ -43,14 +42,14 @@ const Extension1 = createExtension({
const Extension2 = createExtension({
name: '2',
attachTo: { id: 'test/output', input: 'names' },
output: {
name: nameExtensionDataRef,
output: [nameExtensionDataRef],
config: {
schema: {
name: z => z.string().default('extension-2'),
},
},
configSchema: createSchemaFromZod(z =>
z.object({ name: z.string().default('extension-2') }),
),
factory({ config }) {
return { name: config.name };
return [nameExtensionDataRef(config.name)];
},
});
@@ -58,45 +57,45 @@ const Extension3 = createExtension({
name: '3',
attachTo: { id: 'test/output', input: 'names' },
inputs: {
addons: createExtensionInput({
name: nameExtensionDataRef,
}),
},
output: {
name: nameExtensionDataRef,
addons: createExtensionInput([nameExtensionDataRef]),
},
output: [nameExtensionDataRef],
factory({ inputs }) {
return {
name: `extension-3:${inputs.addons.map(n => n.output.name).join('-')}`,
};
return [
nameExtensionDataRef(
`extension-3:${inputs.addons
.map(n => n.get(nameExtensionDataRef))
.join('-')}`,
),
];
},
});
const Child = createExtension({
name: 'child',
attachTo: { id: 'test/3', input: 'addons' },
output: {
name: nameExtensionDataRef,
output: [nameExtensionDataRef],
config: {
schema: {
name: z => z.string().default('child'),
},
},
configSchema: createSchemaFromZod(z =>
z.object({ name: z.string().default('child') }),
),
factory({ config }) {
return { name: config.name };
return [nameExtensionDataRef(config.name)];
},
});
const Child2 = createExtension({
name: 'child2',
attachTo: { id: 'test/3', input: 'addons' },
output: {
name: nameExtensionDataRef,
output: [nameExtensionDataRef],
config: {
schema: {
name: z => z.string().default('child2'),
},
},
configSchema: createSchemaFromZod(z =>
z.object({ name: z.string().default('child2') }),
),
factory({ config }) {
return { name: config.name };
return [nameExtensionDataRef(config.name)];
},
});
@@ -104,19 +103,19 @@ const outputExtension = createExtension({
name: 'output',
attachTo: { id: 'app', input: 'root' },
inputs: {
names: createExtensionInput({
name: nameExtensionDataRef,
}),
},
output: {
element: coreExtensionData.reactElement,
names: createExtensionInput([nameExtensionDataRef]),
},
output: [coreExtensionData.reactElement],
factory({ inputs }) {
return {
element: React.createElement('span', {}, [
`Names: ${inputs.names.map(n => n.output.name).join(', ')}`,
]),
};
return [
coreExtensionData.reactElement(
React.createElement('span', {}, [
`Names: ${inputs.names
.map(n => n.get(nameExtensionDataRef))
.join(', ')}`,
]),
),
];
},
});
@@ -19,17 +19,12 @@ export {
createExtension,
type ExtensionDefinition,
type CreateExtensionOptions,
type ExtensionDataValues,
type ResolvedExtensionInput,
type ResolvedExtensionInputs,
type LegacyCreateExtensionOptions,
type AnyExtensionInputMap,
type AnyExtensionDataMap,
} from './createExtension';
export {
createExtensionInput,
type ExtensionInput,
type LegacyExtensionInput,
} from './createExtensionInput';
export { type ExtensionDataContainer } from './createExtensionDataContainer';
export {
@@ -16,9 +16,6 @@
import { AppNode } from '../apis';
import {
AnyExtensionDataMap,
AnyExtensionInputMap,
ExtensionDataValues,
ExtensionDefinition,
ResolvedExtensionInputs,
toInternalExtensionDefinition,
@@ -47,13 +44,27 @@ export type InternalExtension<TConfig, TConfigInput> = Extension<
(
| {
readonly version: 'v1';
readonly inputs: AnyExtensionInputMap;
readonly output: AnyExtensionDataMap;
factory(options: {
readonly inputs: {
[inputName in string]: {
$$type: '@backstage/ExtensionInput';
extensionData: {
[name in string]: AnyExtensionDataRef;
};
config: { optional: boolean; singleton: boolean };
};
};
readonly output: {
[name in string]: AnyExtensionDataRef;
};
factory(context: {
node: AppNode;
config: TConfig;
inputs: ResolvedExtensionInputs<AnyExtensionInputMap>;
}): ExtensionDataValues<any>;
inputs: {
[inputName in string]: unknown;
};
}): {
[inputName in string]: unknown;
};
}
| {
readonly version: 'v2';