@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin, Extension, ExtensionDataRef } from '../../wiring';
|
||||
import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring';
|
||||
|
||||
/**
|
||||
* The specification for this {@link AppNode} in the {@link AppTree}.
|
||||
@@ -32,7 +32,7 @@ export interface AppNodeSpec {
|
||||
readonly extension: Extension<unknown, unknown>;
|
||||
readonly disabled: boolean;
|
||||
readonly config?: unknown;
|
||||
readonly source?: BackstagePlugin;
|
||||
readonly source?: FrontendPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,6 @@ describe('ApiBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "api",
|
||||
"name": "test",
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
@@ -123,7 +122,6 @@ describe('ApiBlueprint', () => {
|
||||
},
|
||||
"kind": "api",
|
||||
"name": "test",
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -38,7 +38,6 @@ describe('AppRootElementBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "app-root-element",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -46,7 +46,6 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "app-root-wrapper",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -56,7 +56,6 @@ describe('NavItemBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "nav-item",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -41,7 +41,6 @@ describe('NavLogoBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "nav-logo",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -66,7 +66,6 @@ describe('PageBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "page",
|
||||
"name": "test-page",
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
[Function],
|
||||
|
||||
@@ -46,7 +46,6 @@ describe('RouterBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "app-router-component",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -42,7 +42,6 @@ describe('SignInPageBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "sign-in-page",
|
||||
"name": undefined,
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -42,7 +42,6 @@ describe('ThemeBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "theme",
|
||||
"name": "light",
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -57,7 +57,6 @@ describe('TranslationBlueprint', () => {
|
||||
"inputs": {},
|
||||
"kind": "translation",
|
||||
"name": "blob",
|
||||
"namespace": undefined,
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import React, { Component, ComponentType, PropsWithChildren } from 'react';
|
||||
import { BackstagePlugin } from '../wiring';
|
||||
import { FrontendPlugin } from '../wiring';
|
||||
import { CoreErrorBoundaryFallbackProps } from '../types';
|
||||
|
||||
type ErrorBoundaryProps = PropsWithChildren<{
|
||||
plugin?: BackstagePlugin;
|
||||
plugin?: FrontendPlugin;
|
||||
Fallback: ComponentType<CoreErrorBoundaryFallbackProps>;
|
||||
}>;
|
||||
type ErrorBoundaryState = { error?: Error };
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { BackstagePlugin } from './wiring';
|
||||
import { FrontendPlugin } from './wiring';
|
||||
|
||||
// TODO(Rugvip): This might be a quite useful utility type, maybe add to @backstage/types?
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ export type CoreNotFoundErrorPageProps = {
|
||||
|
||||
/** @public */
|
||||
export type CoreErrorBoundaryFallbackProps = {
|
||||
plugin?: BackstagePlugin;
|
||||
plugin?: FrontendPlugin;
|
||||
error: Error;
|
||||
resetError: () => void;
|
||||
};
|
||||
|
||||
@@ -718,7 +718,6 @@ describe('createExtension', () => {
|
||||
|
||||
it('should complain when overriding with incompatible output', () => {
|
||||
const testExtension = createExtension({
|
||||
namespace: 'test',
|
||||
attachTo: { id: 'root', input: 'blob' },
|
||||
output: [stringDataRef],
|
||||
factory() {
|
||||
|
||||
@@ -115,7 +115,6 @@ export type VerifyExtensionFactoryOutput<
|
||||
/** @public */
|
||||
export type CreateExtensionOptions<
|
||||
TKind extends string | undefined,
|
||||
TNamespace extends string | undefined,
|
||||
TName extends string | undefined,
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
@@ -128,7 +127,6 @@ export type CreateExtensionOptions<
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
> = {
|
||||
kind?: TKind;
|
||||
namespace?: TNamespace;
|
||||
name?: TName;
|
||||
attachTo: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
@@ -150,7 +148,6 @@ export type CreateExtensionOptions<
|
||||
/** @public */
|
||||
export type ExtensionDefinitionParameters = {
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
name?: string;
|
||||
configInput?: { [K in string]: any };
|
||||
config?: { [K in string]: any };
|
||||
@@ -233,7 +230,6 @@ export type ExtensionDefinition<
|
||||
>,
|
||||
): ExtensionDefinition<{
|
||||
kind: T['kind'];
|
||||
namespace: T['namespace'];
|
||||
name: T['name'];
|
||||
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
|
||||
inputs: T['inputs'] & TExtraInputs;
|
||||
@@ -265,12 +261,10 @@ export function createExtension<
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
const TKind extends string | undefined = undefined,
|
||||
const TNamespace extends string | undefined = undefined,
|
||||
const TName extends string | undefined = undefined,
|
||||
>(
|
||||
options: CreateExtensionOptions<
|
||||
TKind,
|
||||
undefined,
|
||||
TName,
|
||||
UOutput,
|
||||
TInputs,
|
||||
@@ -294,97 +288,6 @@ export function createExtension<
|
||||
inputs: TInputs;
|
||||
params: never;
|
||||
kind: string | undefined extends TKind ? undefined : TKind;
|
||||
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
|
||||
name: string | undefined extends TName ? undefined : TName;
|
||||
}>;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release.
|
||||
*/
|
||||
export function createExtension<
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
const TKind extends string | undefined = undefined,
|
||||
const TNamespace extends string | undefined = undefined,
|
||||
const TName extends string | undefined = undefined,
|
||||
>(
|
||||
options: CreateExtensionOptions<
|
||||
TKind,
|
||||
TNamespace,
|
||||
TName,
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfigSchema,
|
||||
UFactoryOutput
|
||||
>,
|
||||
): 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;
|
||||
params: never;
|
||||
kind: string | undefined extends TKind ? undefined : TKind;
|
||||
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
|
||||
name: string | undefined extends TName ? undefined : TName;
|
||||
}>;
|
||||
export function createExtension<
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
const TKind extends string | undefined = undefined,
|
||||
const TNamespace extends string | undefined = undefined,
|
||||
const TName extends string | undefined = undefined,
|
||||
>(
|
||||
options: CreateExtensionOptions<
|
||||
TKind,
|
||||
TNamespace,
|
||||
TName,
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfigSchema,
|
||||
UFactoryOutput
|
||||
>,
|
||||
): 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;
|
||||
params: object;
|
||||
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;
|
||||
@@ -417,11 +320,9 @@ export function createExtension<
|
||||
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;
|
||||
},
|
||||
kind: options.kind,
|
||||
namespace: options.namespace,
|
||||
name: options.name,
|
||||
attachTo: options.attachTo,
|
||||
disabled: options.disabled ?? false,
|
||||
@@ -434,9 +335,6 @@ export function createExtension<
|
||||
if (options.kind) {
|
||||
parts.push(`kind=${options.kind}`);
|
||||
}
|
||||
if (options.namespace) {
|
||||
parts.push(`namespace=${options.namespace}`);
|
||||
}
|
||||
if (options.name) {
|
||||
parts.push(`name=${options.name}`);
|
||||
}
|
||||
@@ -467,7 +365,6 @@ export function createExtension<
|
||||
|
||||
return createExtension({
|
||||
kind: options.kind,
|
||||
namespace: options.namespace,
|
||||
name: options.name,
|
||||
attachTo: overrideOptions.attachTo ?? options.attachTo,
|
||||
disabled: overrideOptions.disabled ?? options.disabled,
|
||||
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
*/
|
||||
export type CreateExtensionBlueprintOptions<
|
||||
TKind extends string,
|
||||
TNamespace extends string | undefined,
|
||||
TName extends string | undefined,
|
||||
TParams,
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
@@ -58,7 +57,6 @@ export type CreateExtensionBlueprintOptions<
|
||||
TDataRefs extends { [name in string]: AnyExtensionDataRef },
|
||||
> = {
|
||||
kind: TKind;
|
||||
namespace?: TNamespace;
|
||||
attachTo: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
@@ -85,7 +83,6 @@ export type CreateExtensionBlueprintOptions<
|
||||
/** @public */
|
||||
export type ExtensionBlueprintParameters = {
|
||||
kind: string;
|
||||
namespace?: string;
|
||||
name?: string;
|
||||
params?: object;
|
||||
configInput?: { [K in string]: any };
|
||||
@@ -108,18 +105,13 @@ export interface ExtensionBlueprint<
|
||||
> {
|
||||
dataRefs: T['dataRefs'];
|
||||
|
||||
make<
|
||||
TNewNamespace extends string | undefined,
|
||||
TNewName extends string | undefined,
|
||||
>(args: {
|
||||
namespace?: undefined;
|
||||
make<TNewName extends string | undefined>(args: {
|
||||
name?: TNewName;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
params: T['params'];
|
||||
}): ExtensionDefinition<{
|
||||
kind: T['kind'];
|
||||
namespace: undefined;
|
||||
name: string | undefined extends TNewName ? T['name'] : TNewName;
|
||||
config: T['config'];
|
||||
configInput: T['configInput'];
|
||||
@@ -127,27 +119,6 @@ export interface ExtensionBlueprint<
|
||||
inputs: T['inputs'];
|
||||
params: T['params'];
|
||||
}>;
|
||||
/** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */
|
||||
make<
|
||||
TNewNamespace extends string | undefined,
|
||||
TNewName extends string | undefined,
|
||||
>(args: {
|
||||
namespace?: TNewNamespace;
|
||||
name?: TNewName;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
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.
|
||||
@@ -156,7 +127,6 @@ export interface ExtensionBlueprint<
|
||||
* optionally call the original factory with the same params.
|
||||
*/
|
||||
makeWithOverrides<
|
||||
TNewNamespace extends string | undefined,
|
||||
TNewName extends string | undefined,
|
||||
TExtensionConfigSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
@@ -170,7 +140,6 @@ export interface ExtensionBlueprint<
|
||||
>;
|
||||
},
|
||||
>(args: {
|
||||
namespace?: undefined;
|
||||
name?: TNewName;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
@@ -232,93 +201,9 @@ export interface ExtensionBlueprint<
|
||||
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
|
||||
inputs: T['inputs'] & TExtraInputs;
|
||||
kind: T['kind'];
|
||||
namespace: undefined;
|
||||
name: string | undefined extends TNewName ? T['name'] : TNewName;
|
||||
params: T['params'];
|
||||
}>;
|
||||
/** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */
|
||||
makeWithOverrides<
|
||||
TNewNamespace extends string | undefined,
|
||||
TNewName extends string | undefined,
|
||||
TExtensionConfigSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
UNewOutput extends AnyExtensionDataRef,
|
||||
TExtraInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
>(args: {
|
||||
namespace: TNewNamespace;
|
||||
name?: TNewName;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TExtraInputs & {
|
||||
[KName in keyof T['inputs']]?: `Error: Input '${KName &
|
||||
string}' is already defined in parent definition`;
|
||||
};
|
||||
output?: Array<UNewOutput>;
|
||||
config?: {
|
||||
schema: TExtensionConfigSchema & {
|
||||
[KName in keyof T['config']]?: `Error: Config key '${KName &
|
||||
string}' is already defined in parent schema`;
|
||||
};
|
||||
};
|
||||
factory(
|
||||
originalFactory: (
|
||||
params: T['params'],
|
||||
context?: {
|
||||
config?: T['config'];
|
||||
inputs?: ResolveInputValueOverrides<NonNullable<T['inputs']>>;
|
||||
},
|
||||
) => ExtensionDataContainer<NonNullable<T['output']>>,
|
||||
context: {
|
||||
node: AppNode;
|
||||
apis: ApiHolder;
|
||||
config: T['config'] & {
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
|
||||
},
|
||||
): Iterable<UFactoryOutput> &
|
||||
VerifyExtensionFactoryOutput<
|
||||
AnyExtensionDataRef extends UNewOutput
|
||||
? NonNullable<T['output']>
|
||||
: UNewOutput,
|
||||
UFactoryOutput
|
||||
>;
|
||||
}): 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;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,13 +224,11 @@ export function createExtensionBlueprint<
|
||||
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
TKind extends string,
|
||||
TNamespace extends undefined = undefined,
|
||||
TName extends string | undefined = undefined,
|
||||
TDataRefs extends { [name in string]: AnyExtensionDataRef } = never,
|
||||
>(
|
||||
options: CreateExtensionBlueprintOptions<
|
||||
TKind,
|
||||
undefined,
|
||||
TName,
|
||||
TParams,
|
||||
UOutput,
|
||||
@@ -356,57 +239,6 @@ export function createExtensionBlueprint<
|
||||
>,
|
||||
): ExtensionBlueprint<{
|
||||
kind: TKind;
|
||||
namespace: undefined;
|
||||
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]>> };
|
||||
configInput: string extends keyof TConfigSchema
|
||||
? {}
|
||||
: z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>;
|
||||
dataRefs: TDataRefs;
|
||||
}>;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated the namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release.
|
||||
*/
|
||||
export function createExtensionBlueprint<
|
||||
TParams extends object,
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
TKind extends string,
|
||||
TNamespace extends string | undefined = undefined,
|
||||
TName extends string | undefined = undefined,
|
||||
TDataRefs extends { [name in string]: AnyExtensionDataRef } = never,
|
||||
>(
|
||||
options: CreateExtensionBlueprintOptions<
|
||||
TKind,
|
||||
TNamespace,
|
||||
TName,
|
||||
TParams,
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfigSchema,
|
||||
UFactoryOutput,
|
||||
TDataRefs
|
||||
>,
|
||||
): ExtensionBlueprint<{
|
||||
kind: TKind;
|
||||
namespace: TNamespace;
|
||||
name: TName;
|
||||
params: TParams;
|
||||
output: UOutput;
|
||||
@@ -435,13 +267,11 @@ export function createExtensionBlueprint<
|
||||
TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
TKind extends string,
|
||||
TNamespace extends string | undefined = undefined,
|
||||
TName extends string | undefined = undefined,
|
||||
TDataRefs extends { [name in string]: AnyExtensionDataRef } = never,
|
||||
>(
|
||||
options: CreateExtensionBlueprintOptions<
|
||||
TKind,
|
||||
TNamespace,
|
||||
TName,
|
||||
TParams,
|
||||
UOutput,
|
||||
@@ -452,7 +282,6 @@ export function createExtensionBlueprint<
|
||||
>,
|
||||
): ExtensionBlueprint<{
|
||||
kind: TKind;
|
||||
namespace: TNamespace;
|
||||
name: TName;
|
||||
params: TParams;
|
||||
output: UOutput;
|
||||
@@ -474,7 +303,6 @@ export function createExtensionBlueprint<
|
||||
make(args) {
|
||||
return createExtension({
|
||||
kind: options.kind,
|
||||
namespace: args.namespace ?? options.namespace,
|
||||
name: args.name ?? options.name,
|
||||
attachTo: args.attachTo ?? options.attachTo,
|
||||
disabled: args.disabled ?? options.disabled,
|
||||
@@ -491,7 +319,6 @@ export function createExtensionBlueprint<
|
||||
makeWithOverrides(args) {
|
||||
return createExtension({
|
||||
kind: options.kind,
|
||||
namespace: args.namespace ?? options.namespace,
|
||||
name: args.name ?? options.name,
|
||||
attachTo: args.attachTo ?? options.attachTo,
|
||||
disabled: args.disabled ?? options.disabled,
|
||||
@@ -539,7 +366,6 @@ export function createExtensionBlueprint<
|
||||
},
|
||||
} as ExtensionBlueprint<{
|
||||
kind: TKind;
|
||||
namespace: TNamespace;
|
||||
name: TName;
|
||||
params: TParams;
|
||||
output: UOutput;
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createExtension } from './createExtension';
|
||||
import {
|
||||
createExtensionOverrides,
|
||||
toInternalExtensionOverrides,
|
||||
} from './createExtensionOverrides';
|
||||
|
||||
describe('createExtensionOverrides', () => {
|
||||
it('should create overrides without extensions', () => {
|
||||
expect(createExtensionOverrides({ extensions: [] })).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$$type": "@backstage/ExtensionOverrides",
|
||||
"extensions": [],
|
||||
"featureFlags": [],
|
||||
"toString": [Function],
|
||||
"version": "v1",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should create overrides with extensions', () => {
|
||||
expect(
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({
|
||||
name: 'a',
|
||||
attachTo: { id: 'app', input: 'apis' },
|
||||
output: [],
|
||||
factory: () => [],
|
||||
}),
|
||||
createExtension({
|
||||
namespace: 'b',
|
||||
attachTo: { id: 'app', input: 'apis' },
|
||||
output: [],
|
||||
factory: () => [],
|
||||
}),
|
||||
createExtension({
|
||||
kind: 'k',
|
||||
namespace: 'c',
|
||||
name: 'n',
|
||||
attachTo: { id: 'app', input: 'apis' },
|
||||
output: [],
|
||||
factory: () => [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$$type": "@backstage/ExtensionOverrides",
|
||||
"extensions": [
|
||||
{
|
||||
"$$type": "@backstage/Extension",
|
||||
"T": undefined,
|
||||
"attachTo": {
|
||||
"id": "app",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "a",
|
||||
"inputs": {},
|
||||
"output": [],
|
||||
"toString": [Function],
|
||||
"version": "v2",
|
||||
},
|
||||
{
|
||||
"$$type": "@backstage/Extension",
|
||||
"T": undefined,
|
||||
"attachTo": {
|
||||
"id": "app",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "b",
|
||||
"inputs": {},
|
||||
"output": [],
|
||||
"toString": [Function],
|
||||
"version": "v2",
|
||||
},
|
||||
{
|
||||
"$$type": "@backstage/Extension",
|
||||
"T": undefined,
|
||||
"attachTo": {
|
||||
"id": "app",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "k:c/n",
|
||||
"inputs": {},
|
||||
"output": [],
|
||||
"toString": [Function],
|
||||
"version": "v2",
|
||||
},
|
||||
],
|
||||
"featureFlags": [],
|
||||
"toString": [Function],
|
||||
"version": "v1",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should convert to internal overrides', () => {
|
||||
const overrides = createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({
|
||||
namespace: 'a',
|
||||
attachTo: { id: 'app', input: 'apis' },
|
||||
output: [],
|
||||
factory: () => [],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const internal = toInternalExtensionOverrides(overrides);
|
||||
expect(internal).toBe(overrides);
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ExtensionDefinition } from './createExtension';
|
||||
import {
|
||||
Extension,
|
||||
resolveExtensionDefinition,
|
||||
} from './resolveExtensionDefinition';
|
||||
import { ExtensionOverrides, FeatureFlagConfig } from './types';
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link createFrontendModule} instead.
|
||||
* @public
|
||||
*/
|
||||
export interface ExtensionOverridesOptions {
|
||||
extensions: ExtensionDefinition[];
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalExtensionOverrides extends ExtensionOverrides {
|
||||
readonly version: 'v1';
|
||||
readonly extensions: Extension<unknown>[];
|
||||
readonly featureFlags: FeatureFlagConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link createFrontendModule} instead.
|
||||
* @public
|
||||
*/
|
||||
export function createExtensionOverrides(
|
||||
options: ExtensionOverridesOptions,
|
||||
): ExtensionOverrides {
|
||||
const extensions = options.extensions.map(def =>
|
||||
resolveExtensionDefinition(def),
|
||||
);
|
||||
const featureFlags = options.featureFlags ?? [];
|
||||
return {
|
||||
$$type: '@backstage/ExtensionOverrides',
|
||||
version: 'v1',
|
||||
extensions,
|
||||
featureFlags,
|
||||
toString() {
|
||||
const ex = extensions.map(String).join(',');
|
||||
const ff = featureFlags.map(f => f.name).join(',');
|
||||
return `ExtensionOverrides{extensions=[${ex}],featureFlags=[${ff}]}`;
|
||||
},
|
||||
} as InternalExtensionOverrides;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function toInternalExtensionOverrides(
|
||||
overrides: ExtensionOverrides,
|
||||
): InternalExtensionOverrides {
|
||||
const internal = overrides as InternalExtensionOverrides;
|
||||
if (internal.$$type !== '@backstage/ExtensionOverrides') {
|
||||
throw new Error(
|
||||
`Invalid extension overrides instance, bad type '${internal.$$type}'`,
|
||||
);
|
||||
}
|
||||
if (internal.version !== 'v1') {
|
||||
throw new Error(
|
||||
`Invalid extension overrides instance, bad version '${internal.version}'`,
|
||||
);
|
||||
}
|
||||
return internal;
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createApp } from '@backstage/frontend-app-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { createApp } from '../../../frontend-defaults/src/createApp';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { FrontendPlugin, createFrontendPlugin } from './createFrontendPlugin';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
@@ -170,7 +171,9 @@ describe('createFrontendPlugin', () => {
|
||||
}
|
||||
`);
|
||||
// @ts-expect-error
|
||||
expect(plugin.getExtension('nonexistent')).toBeUndefined();
|
||||
expect(() => plugin.getExtension('nonexistent')).toThrow(
|
||||
/Attempted to get non-existent extension/,
|
||||
);
|
||||
|
||||
await renderWithEffects(
|
||||
createTestAppRoot({
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OpaqueExtensionDefinition } from '@internal/frontend';
|
||||
import {
|
||||
OpaqueExtensionDefinition,
|
||||
OpaqueFrontendPlugin,
|
||||
} from '@internal/frontend';
|
||||
import { ExtensionDefinition } from './createExtension';
|
||||
import {
|
||||
Extension,
|
||||
@@ -27,7 +30,9 @@ import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types';
|
||||
export interface FrontendPlugin<
|
||||
TRoutes extends AnyRoutes = AnyRoutes,
|
||||
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
|
||||
TExtensionMap extends { [id in string]: ExtensionDefinition } = {},
|
||||
TExtensionMap extends { [id in string]: ExtensionDefinition } = {
|
||||
[id in string]: ExtensionDefinition;
|
||||
},
|
||||
> {
|
||||
readonly $$type: '@backstage/FrontendPlugin';
|
||||
readonly id: string;
|
||||
@@ -39,15 +44,6 @@ export interface FrontendPlugin<
|
||||
}): FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use {@link FrontendPlugin} instead.
|
||||
*/
|
||||
export type BackstagePlugin<
|
||||
TRoutes extends AnyRoutes = AnyRoutes,
|
||||
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
|
||||
TExtensionMap extends { [id in string]: ExtensionDefinition } = {},
|
||||
> = FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
|
||||
/** @public */
|
||||
export interface PluginOptions<
|
||||
TId extends string,
|
||||
@@ -62,16 +58,6 @@ export interface PluginOptions<
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface InternalFrontendPlugin<
|
||||
TRoutes extends AnyRoutes = AnyRoutes,
|
||||
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
|
||||
> extends FrontendPlugin<TRoutes, TExternalRoutes> {
|
||||
readonly version: 'v1';
|
||||
readonly extensions: Extension<unknown>[];
|
||||
readonly featureFlags: FeatureFlagConfig[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createFrontendPlugin<
|
||||
TId extends string,
|
||||
@@ -121,16 +107,20 @@ export function createFrontendPlugin<
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
$$type: '@backstage/FrontendPlugin',
|
||||
version: 'v1',
|
||||
return OpaqueFrontendPlugin.createInstance('v1', {
|
||||
id: options.id,
|
||||
routes: options.routes ?? ({} as TRoutes),
|
||||
externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),
|
||||
featureFlags: options.featureFlags ?? [],
|
||||
extensions,
|
||||
extensions: extensions,
|
||||
getExtension(id) {
|
||||
return extensionDefinitionsById.get(id);
|
||||
const ext = extensionDefinitionsById.get(id);
|
||||
if (!ext) {
|
||||
throw new Error(
|
||||
`Attempted to get non-existent extension '${id}' from plugin '${options.id}'`,
|
||||
);
|
||||
}
|
||||
return ext;
|
||||
},
|
||||
toString() {
|
||||
return `Plugin{id=${options.id}}`;
|
||||
@@ -152,45 +142,5 @@ export function createFrontendPlugin<
|
||||
extensions: [...nonOverriddenExtensions, ...overrides.extensions],
|
||||
});
|
||||
},
|
||||
} as InternalFrontendPlugin<TRoutes, TExternalRoutes>;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isInternalFrontendPlugin(opaque: {
|
||||
$$type: string;
|
||||
}): opaque is InternalFrontendPlugin {
|
||||
if (
|
||||
opaque.$$type === '@backstage/FrontendPlugin' ||
|
||||
opaque.$$type === '@backstage/BackstagePlugin'
|
||||
) {
|
||||
// Make sure we throw if invalid
|
||||
toInternalFrontendPlugin(opaque as FrontendPlugin);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function toInternalFrontendPlugin(
|
||||
plugin: FrontendPlugin,
|
||||
): InternalFrontendPlugin {
|
||||
const internal = plugin as InternalFrontendPlugin;
|
||||
if (
|
||||
internal.$$type !== '@backstage/FrontendPlugin' &&
|
||||
internal.$$type !== '@backstage/BackstagePlugin'
|
||||
) {
|
||||
throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);
|
||||
}
|
||||
if (internal.version !== 'v1') {
|
||||
throw new Error(
|
||||
`Invalid plugin instance, bad version '${internal.version}'`,
|
||||
);
|
||||
}
|
||||
return internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use {@link createFrontendPlugin} instead.
|
||||
*/
|
||||
export const createPlugin = createFrontendPlugin;
|
||||
|
||||
@@ -37,10 +37,8 @@ export {
|
||||
type ConfigurableExtensionDataRef,
|
||||
} from './createExtensionDataRef';
|
||||
export {
|
||||
createPlugin,
|
||||
createFrontendPlugin,
|
||||
type FrontendPlugin,
|
||||
type BackstagePlugin,
|
||||
type PluginOptions,
|
||||
} from './createFrontendPlugin';
|
||||
export {
|
||||
@@ -48,10 +46,6 @@ export {
|
||||
type FrontendModule,
|
||||
type CreateFrontendModuleOptions,
|
||||
} from './createFrontendModule';
|
||||
export {
|
||||
createExtensionOverrides,
|
||||
type ExtensionOverridesOptions,
|
||||
} from './createExtensionOverrides';
|
||||
export { type Extension } from './resolveExtensionDefinition';
|
||||
export {
|
||||
type AnyRoutes,
|
||||
|
||||
@@ -108,103 +108,42 @@ describe('ResolveExtensionId', () => {
|
||||
it('should resolve extension IDs correctly', () => {
|
||||
type NamedExtension<
|
||||
TKind extends string | undefined,
|
||||
TNamespace extends string | undefined,
|
||||
TName extends string | undefined,
|
||||
> = ExtensionDefinition<{
|
||||
kind: TKind;
|
||||
namespace: TNamespace;
|
||||
name: TName;
|
||||
output: any;
|
||||
}>;
|
||||
|
||||
const id1: 'k:ns' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'ns', undefined>,
|
||||
undefined
|
||||
NamedExtension<'k', undefined>,
|
||||
'ns'
|
||||
>;
|
||||
const id2: 'k:n' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', undefined, 'n'>,
|
||||
undefined
|
||||
|
||||
const id2: 'ns/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, 'n'>,
|
||||
'ns'
|
||||
>;
|
||||
const id3: 'ns/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, 'ns', 'n'>,
|
||||
undefined
|
||||
|
||||
const id3: 'ns' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, undefined>,
|
||||
'ns'
|
||||
>;
|
||||
const id4: never = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', undefined, undefined>,
|
||||
undefined
|
||||
>;
|
||||
const id5: 'ns' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, 'ns', undefined>,
|
||||
undefined
|
||||
>;
|
||||
const id6: 'n' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, undefined, 'n'>,
|
||||
undefined
|
||||
>;
|
||||
const id7: 'k:ns/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'ns', 'n'>,
|
||||
undefined
|
||||
>;
|
||||
const id8: 'k:ns2' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'ns', undefined>,
|
||||
'ns2'
|
||||
>;
|
||||
const id9: 'k:ns2/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', undefined, 'n'>,
|
||||
'ns2'
|
||||
>;
|
||||
const ida: 'ns2/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, 'ns', 'n'>,
|
||||
'ns2'
|
||||
>;
|
||||
const idb: 'k:ns2' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', undefined, undefined>,
|
||||
'ns2'
|
||||
>;
|
||||
const idc: 'ns2' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, 'ns', undefined>,
|
||||
'ns2'
|
||||
>;
|
||||
const idd: 'ns2/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<undefined, undefined, 'n'>,
|
||||
'ns2'
|
||||
>;
|
||||
const ide: 'k:ns2/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'ns', 'n'>,
|
||||
'ns2'
|
||||
|
||||
const id4: 'k:ns/n' = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'n'>,
|
||||
'ns'
|
||||
>;
|
||||
|
||||
const invalid1: never = {} as ResolveExtensionId<
|
||||
NamedExtension<string | undefined, 'ns', 'n'>,
|
||||
undefined
|
||||
>;
|
||||
const invalid2: never = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', string | undefined, 'n'>,
|
||||
undefined
|
||||
>;
|
||||
const invalid3: never = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', 'ns', string | undefined>,
|
||||
undefined
|
||||
NamedExtension<'k', string | undefined>,
|
||||
'ns'
|
||||
>;
|
||||
|
||||
expect([
|
||||
id1,
|
||||
id2,
|
||||
id3,
|
||||
id4,
|
||||
id5,
|
||||
id6,
|
||||
id7,
|
||||
id8,
|
||||
id9,
|
||||
ida,
|
||||
idb,
|
||||
idc,
|
||||
idd,
|
||||
ide,
|
||||
invalid1,
|
||||
invalid2,
|
||||
invalid3,
|
||||
]).toBeDefined();
|
||||
const invalid2: never = {} as ResolveExtensionId<
|
||||
NamedExtension<'k', string>,
|
||||
'ns'
|
||||
>;
|
||||
|
||||
expect([id1, id2, id3, id4, invalid1, invalid2]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,22 +111,15 @@ export function toInternalExtension<TConfig, TConfigInput>(
|
||||
/** @ignore */
|
||||
export type ResolveExtensionId<
|
||||
TExtension extends ExtensionDefinition,
|
||||
TDefaultNamespace extends string | undefined,
|
||||
TNamespace extends string,
|
||||
> = TExtension extends ExtensionDefinition<{
|
||||
kind: infer IKind extends string | undefined;
|
||||
namespace: infer INamespace extends string | undefined;
|
||||
name: infer IName extends string | undefined;
|
||||
}>
|
||||
? [string | undefined] extends [IKind | INamespace | IName]
|
||||
? [string] extends [IKind | IName]
|
||||
? never
|
||||
: (
|
||||
(
|
||||
undefined extends TDefaultNamespace ? INamespace : TDefaultNamespace
|
||||
) extends infer ISelectedNamespace extends string
|
||||
? undefined extends IName
|
||||
? ISelectedNamespace
|
||||
: `${ISelectedNamespace}/${IName}`
|
||||
: IName
|
||||
undefined extends IName ? TNamespace : `${TNamespace}/${IName}`
|
||||
) extends infer INamePart extends string
|
||||
? IKind extends string
|
||||
? `${IKind}:${INamePart}`
|
||||
|
||||
Reference in New Issue
Block a user