frontend-plugin-api: add new internal option for extension inputs
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Implemented support for the `internal` extension input option.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
'@backstage/plugin-app': patch
|
||||
---
|
||||
|
||||
The deprecation of the following blueprints listed below is not complete. These blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored.
|
||||
|
||||
- `IconBundleBlueprint`
|
||||
- `NavContentBlueprint`
|
||||
- `RouterBlueprint`
|
||||
- `SignInPageBlueprint`
|
||||
- `SwappableComponentBlueprint`
|
||||
- `ThemeBlueprint`
|
||||
- `TranslationBlueprint`
|
||||
@@ -59,6 +59,14 @@ export type AppErrorTypes = {
|
||||
inputName: string;
|
||||
};
|
||||
};
|
||||
EXTENSION_INPUT_INTERNAL_IGNORED: {
|
||||
context: {
|
||||
node: AppNode;
|
||||
inputName: string;
|
||||
extensionId: string;
|
||||
plugin: FrontendPlugin;
|
||||
};
|
||||
};
|
||||
EXTENSION_ATTACHMENT_CONFLICT: {
|
||||
context: {
|
||||
node: AppNode;
|
||||
|
||||
@@ -142,6 +142,36 @@ function createV1Extension(opts: {
|
||||
return ext;
|
||||
}
|
||||
|
||||
function mirrorInputs(ctx: {
|
||||
inputs: {
|
||||
[name in string]:
|
||||
| undefined
|
||||
| ResolvedExtensionInput<ExtensionInput>
|
||||
| Array<ResolvedExtensionInput<ExtensionInput>>;
|
||||
};
|
||||
}) {
|
||||
return [
|
||||
inputMirrorDataRef(
|
||||
Object.fromEntries(
|
||||
Object.entries(ctx.inputs).map(([k, v]) => [
|
||||
k,
|
||||
Array.isArray(v)
|
||||
? v.map(vi => ({
|
||||
node: vi.node,
|
||||
test: vi.get(testDataRef),
|
||||
other: vi.get(otherDataRef),
|
||||
}))
|
||||
: {
|
||||
node: v?.node,
|
||||
test: v?.get(testDataRef),
|
||||
other: v?.get(otherDataRef),
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
describe('instantiateAppNodeTree', () => {
|
||||
describe('v1', () => {
|
||||
const simpleExtension = createV1Extension({
|
||||
@@ -237,6 +267,60 @@ describe('instantiateAppNodeTree', () => {
|
||||
expect(childNode?.instance).toBeDefined();
|
||||
});
|
||||
|
||||
it('should ignore non-matching plugin attachments for internal inputs', () => {
|
||||
const otherPlugin = createFrontendPlugin({ pluginId: 'other' });
|
||||
const tree = resolveAppTree(
|
||||
'root-node',
|
||||
[
|
||||
makeSpec(
|
||||
resolveExtensionDefinition(
|
||||
createExtension({
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
test: createExtensionInput([testDataRef], {
|
||||
singleton: true,
|
||||
internal: true,
|
||||
}),
|
||||
},
|
||||
output: [inputMirrorDataRef],
|
||||
factory: mirrorInputs,
|
||||
}),
|
||||
{ namespace: 'root-node' },
|
||||
),
|
||||
),
|
||||
makeSpec(simpleExtension, {
|
||||
id: 'child-node-app',
|
||||
attachTo: { id: 'root-node', input: 'test' },
|
||||
}),
|
||||
makeSpec(simpleExtension, {
|
||||
id: 'child-node-other',
|
||||
attachTo: { id: 'root-node', input: 'test' },
|
||||
plugin: otherPlugin,
|
||||
}),
|
||||
],
|
||||
collector,
|
||||
);
|
||||
|
||||
instantiateAppNodeTree(tree.root, testApis, collector);
|
||||
|
||||
expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({
|
||||
test: { node: { spec: { id: 'child-node-app' } }, test: 'test' },
|
||||
});
|
||||
expect(collector.collectErrors()).toEqual([
|
||||
{
|
||||
code: 'EXTENSION_INPUT_INTERNAL_IGNORED',
|
||||
message:
|
||||
"extension 'child-node-other' from plugin 'other' attached to input 'test' on 'root-node' was ignored, the input is marked as internal and attached extensions must therefore be provided via an override or a module for the 'app' plugin, not the 'other' plugin",
|
||||
context: {
|
||||
node: tree.root,
|
||||
inputName: 'test',
|
||||
extensionId: 'child-node-other',
|
||||
plugin: otherPlugin,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not instantiate disabled attachments', () => {
|
||||
const tree = resolveAppTree(
|
||||
'root-node',
|
||||
@@ -783,36 +867,6 @@ describe('instantiateAppNodeTree', () => {
|
||||
{ namespace: 'app' },
|
||||
);
|
||||
|
||||
function mirrorInputs(ctx: {
|
||||
inputs: {
|
||||
[name in string]:
|
||||
| undefined
|
||||
| ResolvedExtensionInput<ExtensionInput>
|
||||
| Array<ResolvedExtensionInput<ExtensionInput>>;
|
||||
};
|
||||
}) {
|
||||
return [
|
||||
inputMirrorDataRef(
|
||||
Object.fromEntries(
|
||||
Object.entries(ctx.inputs).map(([k, v]) => [
|
||||
k,
|
||||
Array.isArray(v)
|
||||
? v.map(vi => ({
|
||||
node: vi.node,
|
||||
test: vi.get(testDataRef),
|
||||
other: vi.get(otherDataRef),
|
||||
}))
|
||||
: {
|
||||
node: v?.node,
|
||||
test: v?.get(testDataRef),
|
||||
other: v?.get(otherDataRef),
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
it('should instantiate a single node', () => {
|
||||
const tree = resolveAppTree(
|
||||
'root-node',
|
||||
|
||||
@@ -246,10 +246,32 @@ function resolveV2Inputs(
|
||||
inputMap: { [inputName in string]: ExtensionInput },
|
||||
attachments: ReadonlyMap<string, AppNode[]>,
|
||||
parentCollector: ErrorCollector<{ node: AppNode }>,
|
||||
node: AppNode,
|
||||
): ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput }> {
|
||||
return mapValues(inputMap, (input, inputName) => {
|
||||
const attachedNodes = attachments.get(inputName) ?? [];
|
||||
const allAttachedNodes = attachments.get(inputName) ?? [];
|
||||
const collector = parentCollector.child({ inputName });
|
||||
const inputPluginId = node.spec.plugin.id;
|
||||
|
||||
const attachedNodes = input.config.internal
|
||||
? allAttachedNodes.filter(attachment => {
|
||||
const attachmentPluginId = attachment.spec.plugin.id;
|
||||
if (attachmentPluginId !== inputPluginId) {
|
||||
collector.report({
|
||||
code: 'EXTENSION_INPUT_INTERNAL_IGNORED',
|
||||
message:
|
||||
`extension '${attachment.spec.id}' from plugin '${attachmentPluginId}' attached to input '${inputName}' on '${node.spec.id}' was ignored, ` +
|
||||
`the input is marked as internal and attached extensions must therefore be provided via an override or a module for the '${inputPluginId}' plugin, not the '${attachmentPluginId}' plugin`,
|
||||
context: {
|
||||
extensionId: attachment.spec.id,
|
||||
plugin: attachment.spec.plugin,
|
||||
},
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
: allAttachedNodes;
|
||||
|
||||
if (input.config.singleton) {
|
||||
if (attachedNodes.length > 1) {
|
||||
@@ -354,6 +376,7 @@ export function createAppNodeInstance(options: {
|
||||
internalExtension.inputs,
|
||||
attachments,
|
||||
collector,
|
||||
node,
|
||||
),
|
||||
};
|
||||
const outputDataValues = options.extensionFactoryMiddleware
|
||||
|
||||
@@ -38,6 +38,14 @@ export type AppErrorTypes = {
|
||||
EXTENSION_INPUT_DATA_MISSING: {
|
||||
context: { node: AppNode; inputName: string };
|
||||
};
|
||||
EXTENSION_INPUT_INTERNAL_IGNORED: {
|
||||
context: {
|
||||
node: AppNode;
|
||||
inputName: string;
|
||||
extensionId: string;
|
||||
plugin: FrontendPlugin;
|
||||
};
|
||||
};
|
||||
EXTENSION_ATTACHMENT_CONFLICT: {
|
||||
context: { node: AppNode; inputName: string };
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ const DEFAULT_WARNING_CODES: Array<keyof AppErrorTypes> = [
|
||||
'EXTENSION_IGNORED',
|
||||
'INVALID_EXTENSION_CONFIG_KEY',
|
||||
'EXTENSION_INPUT_DATA_IGNORED',
|
||||
'EXTENSION_INPUT_INTERNAL_IGNORED',
|
||||
'EXTENSION_OUTPUT_IGNORED',
|
||||
];
|
||||
|
||||
|
||||
@@ -604,7 +604,7 @@ export function createExtensionDataRef<TData>(): {
|
||||
}): ConfigurableExtensionDataRef<TData, TId>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createExtensionInput<
|
||||
UExtensionData extends ExtensionDataRef<
|
||||
unknown,
|
||||
@@ -616,6 +616,7 @@ export function createExtensionInput<
|
||||
TConfig extends {
|
||||
singleton?: boolean;
|
||||
optional?: boolean;
|
||||
internal?: boolean;
|
||||
},
|
||||
>(
|
||||
extensionData: Array<UExtensionData>,
|
||||
@@ -630,6 +631,7 @@ export function createExtensionInput<
|
||||
{
|
||||
singleton: TConfig['singleton'] extends true ? true : false;
|
||||
optional: TConfig['optional'] extends true ? true : false;
|
||||
internal: TConfig['internal'] extends true ? true : false;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -1288,9 +1290,11 @@ export interface ExtensionInput<
|
||||
TConfig extends {
|
||||
singleton: boolean;
|
||||
optional: boolean;
|
||||
internal?: boolean;
|
||||
} = {
|
||||
singleton: boolean;
|
||||
optional: boolean;
|
||||
internal?: boolean;
|
||||
},
|
||||
> {
|
||||
// (undocumented)
|
||||
|
||||
@@ -29,28 +29,28 @@ describe('createExtensionInput', () => {
|
||||
expect(input).toEqual({
|
||||
$$type: '@backstage/ExtensionInput',
|
||||
extensionData: [stringDataRef, numberDataRef],
|
||||
config: { singleton: false, optional: false },
|
||||
config: { singleton: false, optional: false, internal: false },
|
||||
withContext: expect.any(Function),
|
||||
});
|
||||
|
||||
const x1: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: false }
|
||||
{ singleton: false; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x2: ExtensionInput<
|
||||
typeof stringDataRef,
|
||||
{ singleton: false; optional: false }
|
||||
{ singleton: false; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x3: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: true; optional: false }
|
||||
{ singleton: true; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x4: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: true }
|
||||
{ singleton: false; optional: true; internal: false }
|
||||
> = input;
|
||||
|
||||
unused(x1, x2, x3, x4);
|
||||
@@ -64,7 +64,7 @@ describe('createExtensionInput', () => {
|
||||
expect(inputWithContext).toEqual({
|
||||
$$type: '@backstage/ExtensionInput',
|
||||
extensionData: [stringDataRef, numberDataRef],
|
||||
config: { singleton: false, optional: false },
|
||||
config: { singleton: false, optional: false, internal: false },
|
||||
withContext: expect.any(Function),
|
||||
context,
|
||||
});
|
||||
@@ -77,28 +77,28 @@ describe('createExtensionInput', () => {
|
||||
expect(input).toEqual({
|
||||
$$type: '@backstage/ExtensionInput',
|
||||
extensionData: [stringDataRef, numberDataRef],
|
||||
config: { singleton: true, optional: false },
|
||||
config: { singleton: true, optional: false, internal: false },
|
||||
withContext: expect.any(Function),
|
||||
});
|
||||
|
||||
const x1: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: true; optional: false }
|
||||
{ singleton: true; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x2: ExtensionInput<
|
||||
typeof stringDataRef,
|
||||
{ singleton: true; optional: false }
|
||||
{ singleton: true; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x3: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: false }
|
||||
{ singleton: false; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x4: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: true }
|
||||
{ singleton: false; optional: true; internal: false }
|
||||
> = input;
|
||||
|
||||
unused(x1, x2, x3, x4);
|
||||
@@ -112,28 +112,28 @@ describe('createExtensionInput', () => {
|
||||
expect(input).toEqual({
|
||||
$$type: '@backstage/ExtensionInput',
|
||||
extensionData: [stringDataRef, numberDataRef],
|
||||
config: { singleton: true, optional: true },
|
||||
config: { singleton: true, optional: true, internal: false },
|
||||
withContext: expect.any(Function),
|
||||
});
|
||||
|
||||
const x1: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: true; optional: true }
|
||||
{ singleton: true; optional: true; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x2: ExtensionInput<
|
||||
typeof stringDataRef,
|
||||
{ singleton: true; optional: true }
|
||||
{ singleton: true; optional: true; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x3: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: false }
|
||||
{ singleton: false; optional: false; internal: false }
|
||||
> = input;
|
||||
// @ts-expect-error
|
||||
const x4: ExtensionInput<
|
||||
typeof stringDataRef | typeof numberDataRef,
|
||||
{ singleton: false; optional: true }
|
||||
{ singleton: false; optional: true; internal: false }
|
||||
> = input;
|
||||
|
||||
unused(x1, x2, x3, x4);
|
||||
@@ -144,4 +144,14 @@ describe('createExtensionInput', () => {
|
||||
createExtensionInput([stringDataRef, stringDataRef], { singleton: true }),
|
||||
).toThrow("ExtensionInput may not have duplicate data refs: 'str'");
|
||||
});
|
||||
|
||||
it('should create an internal input', () => {
|
||||
const input = createExtensionInput([stringDataRef], { internal: true });
|
||||
expect(input).toEqual({
|
||||
$$type: '@backstage/ExtensionInput',
|
||||
extensionData: [stringDataRef],
|
||||
config: { singleton: false, optional: false, internal: true },
|
||||
withContext: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,9 +27,14 @@ export interface ExtensionInput<
|
||||
string,
|
||||
{ optional?: true }
|
||||
> = ExtensionDataRef,
|
||||
TConfig extends { singleton: boolean; optional: boolean } = {
|
||||
TConfig extends {
|
||||
singleton: boolean;
|
||||
optional: boolean;
|
||||
internal?: boolean;
|
||||
} = {
|
||||
singleton: boolean;
|
||||
optional: boolean;
|
||||
internal?: boolean;
|
||||
},
|
||||
> {
|
||||
readonly $$type: '@backstage/ExtensionInput';
|
||||
@@ -38,10 +43,60 @@ export interface ExtensionInput<
|
||||
readonly replaces?: Array<{ id: string; input: string }>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Creates a new extension input to be passed to the input map of an extension.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Extension inputs created with this function can be passed to any `inputs` map
|
||||
* as part of creating or overriding an extension.
|
||||
*
|
||||
* The array of extension data references defines the data this input expects.
|
||||
* If the required data is not provided by the attached extension, the
|
||||
* attachment will fail.
|
||||
*
|
||||
* The `config` object can be used to restrict the behavior and shape of the
|
||||
* input. By default an input will accept zero or more extensions from any
|
||||
* plugin. The following options are available:
|
||||
*
|
||||
* - `singleton`: If set to `true`, only one extension can be attached to the
|
||||
* input at a time. Additional extension will trigger an app error and be
|
||||
* ignored.
|
||||
* - `optional`: If set to `true`, the input is optional and can be omitted,
|
||||
* this only has an effect if the `singleton` is set to `true`.
|
||||
* - `internal`: If set to `true`, only extensions from the same plugins will be
|
||||
* allowed to attach to this input. Other extensions will trigger an app error
|
||||
* and be ignored.
|
||||
*
|
||||
* @param extensionData - The array of extension data references that this input
|
||||
* expects.
|
||||
* @param config - The configuration object for the input.
|
||||
* @returns An extension input declaration.
|
||||
* @example
|
||||
* ```ts
|
||||
* const extension = createExtension({
|
||||
* attachTo: { id: 'example-parent', input: 'example-input' },
|
||||
* inputs: {
|
||||
* content: createExtensionInput([coreExtensionData.reactElement], {
|
||||
* singleton: true,
|
||||
* }),
|
||||
* },
|
||||
* output: [coreExtensionData.reactElement],
|
||||
* *factory({ inputs }) {
|
||||
* const content = inputs.content?.get(coreExtensionData.reactElement);
|
||||
* yield coreExtensionData.reactElement(<ContentWrapper>{content}</ContentWrapper>);
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function createExtensionInput<
|
||||
UExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
|
||||
TConfig extends { singleton?: boolean; optional?: boolean },
|
||||
TConfig extends {
|
||||
singleton?: boolean;
|
||||
optional?: boolean;
|
||||
internal?: boolean;
|
||||
},
|
||||
>(
|
||||
extensionData: Array<UExtensionData>,
|
||||
config?: TConfig & { replaces?: Array<{ id: string; input: string }> },
|
||||
@@ -50,6 +105,7 @@ export function createExtensionInput<
|
||||
{
|
||||
singleton: TConfig['singleton'] extends true ? true : false;
|
||||
optional: TConfig['optional'] extends true ? true : false;
|
||||
internal: TConfig['internal'] extends true ? true : false;
|
||||
}
|
||||
> {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
@@ -81,6 +137,9 @@ export function createExtensionInput<
|
||||
optional: Boolean(config?.optional) as TConfig['optional'] extends true
|
||||
? true
|
||||
: false,
|
||||
internal: Boolean(config?.internal) as TConfig['internal'] extends true
|
||||
? true
|
||||
: false,
|
||||
},
|
||||
replaces: config?.replaces,
|
||||
};
|
||||
@@ -90,6 +149,7 @@ export function createExtensionInput<
|
||||
{
|
||||
singleton: TConfig['singleton'] extends true ? true : false;
|
||||
optional: TConfig['optional'] extends true ? true : false;
|
||||
internal: TConfig['internal'] extends true ? true : false;
|
||||
}
|
||||
> {
|
||||
return OpaqueExtensionInput.createInstance(undefined, {
|
||||
|
||||
@@ -37,6 +37,7 @@ export type ResolvedInputValueOverrides<
|
||||
{
|
||||
optional: infer IOptional extends boolean;
|
||||
singleton: boolean;
|
||||
internal?: boolean;
|
||||
}
|
||||
>
|
||||
? IOptional extends true
|
||||
@@ -44,7 +45,11 @@ export type ResolvedInputValueOverrides<
|
||||
: KName
|
||||
: never]: TInputs[KName] extends ExtensionInput<
|
||||
infer IDataRefs,
|
||||
{ optional: boolean; singleton: infer ISingleton extends boolean }
|
||||
{
|
||||
optional: boolean;
|
||||
singleton: infer ISingleton extends boolean;
|
||||
internal?: boolean;
|
||||
}
|
||||
>
|
||||
? ISingleton extends true
|
||||
? Iterable<ExtensionDataRefToValue<IDataRefs>>
|
||||
@@ -56,6 +61,7 @@ export type ResolvedInputValueOverrides<
|
||||
{
|
||||
optional: infer IOptional extends boolean;
|
||||
singleton: boolean;
|
||||
internal?: boolean;
|
||||
}
|
||||
>
|
||||
? IOptional extends true
|
||||
@@ -63,7 +69,11 @@ export type ResolvedInputValueOverrides<
|
||||
: never
|
||||
: never]?: TInputs[KName] extends ExtensionInput<
|
||||
infer IDataRefs,
|
||||
{ optional: boolean; singleton: infer ISingleton extends boolean }
|
||||
{
|
||||
optional: boolean;
|
||||
singleton: infer ISingleton extends boolean;
|
||||
internal?: boolean;
|
||||
}
|
||||
>
|
||||
? ISingleton extends true
|
||||
? Iterable<ExtensionDataRefToValue<IDataRefs>>
|
||||
|
||||
@@ -40,6 +40,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -57,6 +58,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
content: ExtensionInput<
|
||||
@@ -64,6 +66,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -89,6 +92,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
content: ExtensionInput<
|
||||
@@ -100,6 +104,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -121,6 +126,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
signInPage: ExtensionInput<
|
||||
@@ -132,6 +138,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
children: ExtensionInput<
|
||||
@@ -139,6 +146,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
elements: ExtensionInput<
|
||||
@@ -146,6 +154,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
wrappers: ExtensionInput<
|
||||
@@ -157,6 +166,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -182,6 +192,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -218,6 +229,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -262,6 +274,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -471,6 +484,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -592,6 +606,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -669,6 +684,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -703,6 +719,7 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -90,18 +90,11 @@ export const AppNav = createExtension({
|
||||
content: createExtensionInput([NavContentBlueprint.dataRefs.component], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
internal: true,
|
||||
}),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
*factory({ inputs }) {
|
||||
if (inputs.content && inputs.content.node.spec.plugin?.id !== 'app') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: NavContent should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.content.node.spec.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const Content =
|
||||
inputs.content?.get(NavContentBlueprint.dataRefs.component) ??
|
||||
DefaultNavContent;
|
||||
|
||||
@@ -59,37 +59,26 @@ export const AppRoot = createExtension({
|
||||
router: createExtensionInput([RouterBlueprint.dataRefs.component], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
internal: true,
|
||||
}),
|
||||
signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
internal: true,
|
||||
}),
|
||||
children: createExtensionInput([coreExtensionData.reactElement], {
|
||||
singleton: true,
|
||||
}),
|
||||
elements: createExtensionInput([coreExtensionData.reactElement]),
|
||||
wrappers: createExtensionInput([
|
||||
AppRootWrapperBlueprint.dataRefs.component,
|
||||
]),
|
||||
wrappers: createExtensionInput(
|
||||
[AppRootWrapperBlueprint.dataRefs.component],
|
||||
{
|
||||
internal: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs, apis }) {
|
||||
if (inputs.router && inputs.router.node.spec.plugin?.id !== 'app') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: Router should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.router.node.spec.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (inputs.signInPage && inputs.signInPage.node.spec.plugin?.id !== 'app') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: SignInPage should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.signInPage.node.spec.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isProtectedApp()) {
|
||||
const identityApi = apis.get(identityApiRef);
|
||||
if (!identityApi) {
|
||||
@@ -117,16 +106,8 @@ export const AppRoot = createExtension({
|
||||
|
||||
for (const wrapper of inputs.wrappers) {
|
||||
const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);
|
||||
const pluginId = wrapper.node.spec.plugin.id;
|
||||
if (Component) {
|
||||
content = <Component>{content}</Component>;
|
||||
if (pluginId !== 'app') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: AppRootWrappers should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${wrapper.node.spec.id}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const AppThemeApi = ApiBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], {
|
||||
replaces: [{ id: 'app', input: 'themes' }],
|
||||
internal: true,
|
||||
}),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
@@ -45,19 +46,6 @@ export const AppThemeApi = ApiBlueprint.makeWithOverrides({
|
||||
api: appThemeApiRef,
|
||||
deps: {},
|
||||
factory: () => {
|
||||
const nonAppExtensions = inputs.themes.filter(
|
||||
i => i.node.spec.plugin?.id !== 'app',
|
||||
);
|
||||
|
||||
if (nonAppExtensions.length > 0) {
|
||||
const list = nonAppExtensions.map(i => i.node.spec.id).join(', ');
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: Theme should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`,
|
||||
);
|
||||
}
|
||||
|
||||
return AppThemeSelector.createWithStorage(
|
||||
inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)),
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons], {
|
||||
replaces: [{ id: 'app', input: 'icons' }],
|
||||
internal: true,
|
||||
}),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
@@ -41,19 +42,6 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({
|
||||
api: iconsApiRef,
|
||||
deps: {},
|
||||
factory: () => {
|
||||
const nonAppExtensions = inputs.icons.filter(
|
||||
i => i.node.spec.plugin?.id !== 'app',
|
||||
);
|
||||
|
||||
if (nonAppExtensions.length > 0) {
|
||||
const list = nonAppExtensions.map(i => i.node.spec.id).join(', ');
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: IconBundle should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`,
|
||||
);
|
||||
}
|
||||
|
||||
return new DefaultIconsApi(
|
||||
inputs.icons
|
||||
.map(i => i.get(IconBundleBlueprint.dataRefs.icons))
|
||||
|
||||
@@ -28,9 +28,12 @@ import { DefaultSwappableComponentsApi } from '../apis/SwappableComponentsApi';
|
||||
export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'swappable-components',
|
||||
inputs: {
|
||||
components: createExtensionInput([
|
||||
SwappableComponentBlueprint.dataRefs.component,
|
||||
]),
|
||||
components: createExtensionInput(
|
||||
[SwappableComponentBlueprint.dataRefs.component],
|
||||
{
|
||||
internal: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory(defineParams =>
|
||||
@@ -38,25 +41,8 @@ export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({
|
||||
api: swappableComponentsApiRef,
|
||||
deps: {},
|
||||
factory: () => {
|
||||
const nonAppExtensions = inputs.components.filter(
|
||||
i => i.node.spec.plugin?.id !== 'app',
|
||||
);
|
||||
|
||||
if (nonAppExtensions.length > 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`SwappableComponents should only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. Invalid extensions: ${nonAppExtensions
|
||||
.map(i => i.node.spec.id)
|
||||
.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const appExtensions = inputs.components.filter(
|
||||
i => i.node.spec.plugin?.id === 'app',
|
||||
);
|
||||
|
||||
return DefaultSwappableComponentsApi.fromComponents(
|
||||
appExtensions.map(i =>
|
||||
inputs.components.map(i =>
|
||||
i.get(SwappableComponentBlueprint.dataRefs.component),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
translations: createExtensionInput(
|
||||
[TranslationBlueprint.dataRefs.translation],
|
||||
{ replaces: [{ id: 'app', input: 'translations' }] },
|
||||
{ replaces: [{ id: 'app', input: 'translations' }], internal: true },
|
||||
),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
@@ -43,19 +43,6 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({
|
||||
api: translationApiRef,
|
||||
deps: { languageApi: appLanguageApiRef },
|
||||
factory: ({ languageApi }) => {
|
||||
const nonAppExtensions = inputs.translations.filter(
|
||||
i => i.node.spec.plugin?.id !== 'app',
|
||||
);
|
||||
|
||||
if (nonAppExtensions.length > 0) {
|
||||
const list = nonAppExtensions.map(i => i.node.spec.id).join(', ');
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: Translations should only be installed as an extension in the app plugin. ` +
|
||||
`You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`,
|
||||
);
|
||||
}
|
||||
|
||||
return I18nextTranslationApi.create({
|
||||
languageApi,
|
||||
resources: inputs.translations.map(i =>
|
||||
|
||||
@@ -345,6 +345,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -794,6 +795,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
cards: ExtensionInput<
|
||||
@@ -822,6 +824,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -1000,6 +1003,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -1064,6 +1068,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
contents: ExtensionInput<
|
||||
@@ -1105,6 +1110,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
contextMenuItems: ExtensionInput<
|
||||
@@ -1119,6 +1125,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -92,6 +92,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: true;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -219,6 +219,7 @@ export const formFieldsApi: OverridableExtensionDefinition<{
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -91,6 +91,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -118,6 +119,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -218,6 +220,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -411,6 +414,7 @@ export const formDecoratorsApi: OverridableExtensionDefinition<{
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -97,6 +97,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
resultTypes: ExtensionInput<
|
||||
@@ -112,6 +113,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
searchFilters: ExtensionInput<
|
||||
@@ -125,6 +127,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -215,6 +218,7 @@ export const searchPage: OverridableExtensionDefinition<{
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
resultTypes: ExtensionInput<
|
||||
@@ -230,6 +234,7 @@ export const searchPage: OverridableExtensionDefinition<{
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
searchFilters: ExtensionInput<
|
||||
@@ -243,6 +248,7 @@ export const searchPage: OverridableExtensionDefinition<{
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: boolean;
|
||||
optional: boolean;
|
||||
internal?: boolean;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -146,6 +147,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
emptyState: ExtensionInput<
|
||||
@@ -159,6 +161,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -290,6 +293,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ const _default: OverridableFrontendPlugin<
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user