frontend-plugin-api: add new internal option for extension inputs

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-01-22 14:22:56 +01:00
parent 24eb7d7933
commit 7edb810248
27 changed files with 315 additions and 149 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Implemented support for the `internal` extension input option.
+5
View File
@@ -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.
+13
View File
@@ -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`
+8
View File
@@ -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',
];
+5 -1
View File
@@ -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>>
+17
View File
@@ -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;
}
>;
};
+1 -8
View File
@@ -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;
+8 -27
View File
@@ -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}`,
);
}
}
}
+1 -13
View File
@@ -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)),
);
+1 -13
View File
@@ -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),
),
);
+1 -14
View File
@@ -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 =>
+7
View File
@@ -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;
}
>;
};
+1
View File
@@ -92,6 +92,7 @@ const _default: OverridableFrontendPlugin<
{
singleton: false;
optional: true;
internal: false;
}
>;
};
+1
View File
@@ -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;
}
>;
};
+4
View File
@@ -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;
}
>;
};
+6
View File
@@ -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;
}
>;
};
+4
View File
@@ -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;
}
>;
};