Merge pull request #20494 from backstage/rugvip/undeclared

frontend-app-api: reject attachments to undeclared inputs
This commit is contained in:
Patrik Oldsberg
2023-10-10 17:25:58 +02:00
committed by GitHub
6 changed files with 121 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
The app will now reject any extensions that attach to nonexistent inputs.
@@ -27,6 +27,9 @@ export const Core = createExtension({
apis: createExtensionInput({
api: coreExtensionData.apiFactory,
}),
themes: createExtensionInput({
theme: coreExtensionData.theme,
}),
},
output: {},
factory() {},
@@ -48,7 +48,7 @@ function createTestExtension(options: {
id: options.id,
attachTo: options.parent
? { id: options.parent, input: 'children' }
: { id: 'core.routes', input: 'children' },
: { id: 'core.routes', input: 'routes' },
output: {
element: coreExtensionData.reactElement,
path: coreExtensionData.routePath.optional(),
@@ -305,6 +305,7 @@ describe('discovery', () => {
}),
createTestExtension({
id: 'fooEmpty',
parent: 'foo',
}),
createTestExtension({
id: 'page3',
@@ -16,6 +16,7 @@
import {
createExtension,
createExtensionInput,
createPageExtension,
createPlugin,
createThemeExtension,
@@ -143,6 +144,9 @@ describe('createApp', () => {
extension: createExtension({
id: 'root',
attachTo: { id: '', input: '' },
inputs: {
children: createExtensionInput({}),
},
output: {},
factory() {},
}),
@@ -181,6 +185,9 @@ describe('createApp', () => {
extension: createExtension({
id: 'root',
attachTo: { id: '', input: '' },
inputs: {
children: createExtensionInput({}),
},
output: {},
factory() {},
}),
@@ -250,6 +250,92 @@ describe('createExtensionInstance', () => {
);
});
it('should refuse to create an instance with undeclared inputs', () => {
expect(() =>
createExtensionInstance({
attachments: new Map([
[
'declared',
[
createExtensionInstance({
attachments: new Map(),
config: { output: 'many1' },
extension: simpleExtension,
}),
],
],
[
'undeclared',
[
createExtensionInstance({
attachments: new Map(),
config: { output: 'many1' },
extension: simpleExtension,
}),
],
],
]),
config: undefined,
extension: createExtension({
id: 'core.test',
attachTo: { id: 'ignored', input: 'ignored' },
inputs: {
declared: createExtensionInput({
test: testDataRef,
}),
},
output: {},
factory() {},
}),
}),
).toThrow(
"Failed to instantiate extension 'core.test', received undeclared input 'undeclared' from extension 'core.test'",
);
});
it('should refuse to create an instance with multiple undeclared inputs', () => {
expect(() =>
createExtensionInstance({
attachments: new Map([
[
'undeclared1',
[
createExtensionInstance({
attachments: new Map(),
config: { output: 'many1' },
extension: simpleExtension,
}),
],
],
[
'undeclared2',
[
createExtensionInstance({
attachments: new Map(),
config: { output: 'many1' },
extension: simpleExtension,
}),
createExtensionInstance({
attachments: new Map(),
config: { output: 'many1' },
extension: simpleExtension,
}),
],
],
]),
config: undefined,
extension: createExtension({
id: 'core.test',
attachTo: { id: 'ignored', input: 'ignored' },
output: {},
factory() {},
}),
}),
).toThrow(
"Failed to instantiate extension 'core.test', received undeclared inputs 'undeclared1' from extension 'core.test' and 'undeclared2' from extensions 'core.test', 'core.test'",
);
});
it('should refuse to create an instance with multiple inputs for required singleton', () => {
expect(() =>
createExtensionInstance({
@@ -60,6 +60,24 @@ function resolveInputs(
inputMap: AnyExtensionInputMap,
attachments: Map<string, ExtensionInstance[]>,
) {
const undeclaredAttachments = Array.from(attachments.entries()).filter(
([inputName]) => inputMap[inputName] === undefined,
);
if (undeclaredAttachments.length > 0) {
throw new Error(
`received undeclared input${
undeclaredAttachments.length > 1 ? 's' : ''
} ${undeclaredAttachments
.map(
([k, exts]) =>
`'${k}' from extension${exts.length > 1 ? 's' : ''} '${exts
.map(e => e.id)
.join("', '")}'`,
)
.join(' and ')}`,
);
}
return mapValues(inputMap, (input, inputName) => {
const attachedInstances = attachments.get(inputName) ?? [];
if (input.config.singleton) {