frontend-plugin-api: remove support for v1 extensions
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user