Add support for pluginPackage to frontend modules

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2026-02-11 15:13:05 +01:00
committed by Patrik Oldsberg
parent 971cc9457a
commit 7191abef86
3 changed files with 120 additions and 11 deletions
@@ -215,4 +215,70 @@ describe('collectTemplateParams', () => {
});
});
});
describe('frontend-plugin-module with pluginPackage', () => {
const frontendModuleOptions = {
...baseOptions,
template: {
name: 'test-module',
role: 'frontend-plugin-module' as const,
files: [],
values: {},
},
};
it('should auto-fill pluginPackage for catalog plugin without prompting', async () => {
await expect(
collectPortableTemplateInput({
...frontendModuleOptions,
prefilledParams: {
pluginId: 'catalog',
moduleId: 'my-module',
},
}),
).resolves.toEqual({
roleParams: {
role: 'frontend-plugin-module',
pluginId: 'catalog',
moduleId: 'my-module',
pluginPackage: '@backstage/plugin-catalog',
},
owner: undefined,
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packageName: '@internal/plugin-catalog-module-my-module',
packagePath: 'plugins/catalog-module-my-module',
});
});
it('should prompt for pluginPackage for unknown plugins', async () => {
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({
pluginPackage: '@mycompany/plugin-custom',
});
await expect(
collectPortableTemplateInput({
...frontendModuleOptions,
prefilledParams: {
pluginId: 'custom',
moduleId: 'my-extension',
},
}),
).resolves.toEqual({
roleParams: {
role: 'frontend-plugin-module',
pluginId: 'custom',
moduleId: 'my-extension',
pluginPackage: '@mycompany/plugin-custom',
},
owner: undefined,
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packageName: '@internal/plugin-custom-module-my-extension',
packagePath: 'plugins/custom-module-my-extension',
});
});
});
});
@@ -28,11 +28,32 @@ import {
import { PortableTemplate } from '../types';
import { resolvePackageParams } from './resolvePackageParams';
const knownPluginPackages: Record<string, string> = {
const knownBackendPluginPackages: Record<string, string> = {
app: '@backstage/plugin-app-backend',
auth: '@backstage/plugin-auth-backend',
catalog: '@backstage/plugin-catalog-backend',
events: '@backstage/plugin-events-backend',
kubernetes: '@backstage/plugin-kubernetes-backend',
notifications: '@backstage/plugin-notifications-backend',
permission: '@backstage/plugin-permission-backend',
proxy: '@backstage/plugin-proxy-backend',
scaffolder: '@backstage/plugin-scaffolder-backend',
search: '@backstage/plugin-search-backend',
techdocs: '@backstage/plugin-techdocs-backend',
};
const knownFrontendPluginPackages: Record<string, string> = {
app: '@backstage/plugin-app',
auth: '@backstage/plugin-auth',
catalog: '@backstage/plugin-catalog',
events: '@backstage/plugin-events',
kubernetes: '@backstage/plugin-kubernetes',
notifications: '@backstage/plugin-notifications',
permission: '@backstage/plugin-permission',
proxy: '@backstage/plugin-proxy',
scaffolder: '@backstage/plugin-scaffolder',
search: '@backstage/plugin-search',
techdocs: '@backstage/plugin-techdocs',
};
type CollectTemplateParamsOptions = {
@@ -87,9 +108,20 @@ export async function collectPortableTemplateInput(
...promptAnswers,
};
const pluginPackage =
knownPluginPackages[answers.pluginId as string] ??
(answers.pluginPackage as string);
let pluginPackage: string | undefined;
if (
template.role === 'backend-plugin-module' ||
template.role === 'frontend-plugin-module'
) {
const knownPackages =
template.role === 'backend-plugin-module'
? knownBackendPluginPackages
: knownFrontendPluginPackages;
pluginPackage =
knownPackages[answers.pluginId as string] ??
(answers.pluginPackage as string);
}
const roleParams = {
role: template.role,
@@ -165,23 +197,34 @@ export function moduleIdIdPrompt(): DistinctQuestion {
};
}
export function pluginPackagePrompt(): DistinctQuestion {
export function pluginPackagePrompt(
role: 'backend-plugin-module' | 'frontend-plugin-module',
): DistinctQuestion {
const knownPackages =
role === 'backend-plugin-module'
? knownBackendPluginPackages
: knownFrontendPluginPackages;
const examplePackage =
role === 'backend-plugin-module'
? '@backstage/plugin-catalog-backend'
: '@backstage/plugin-catalog';
return {
type: 'input',
name: 'pluginPackage',
message:
'Enter the package name of the plugin this module extends (e.g. @backstage/plugin-catalog-backend) [required]',
message: `Enter the package name of the plugin this module extends (e.g. ${examplePackage}) [required]`,
validate: (value: string) => {
if (!value) {
return 'Please enter the package name of the plugin';
}
if (!isValidNpmPackageName(value)) {
return 'Please enter a valid npm package name (e.g. @backstage/plugin-catalog-backend or my-plugin-backend)';
return `Please enter a valid npm package name (e.g. ${examplePackage} or my-plugin)`;
}
return true;
},
when: (answers: PortableTemplateParams) =>
!knownPluginPackages[answers.pluginId as string],
!knownPackages[answers.pluginId as string],
};
}
@@ -201,9 +244,8 @@ export function getPromptsForRole(
case 'backend-plugin':
return [pluginIdPrompt()];
case 'frontend-plugin-module':
return [pluginIdPrompt(), moduleIdIdPrompt()];
case 'backend-plugin-module':
return [pluginIdPrompt(), moduleIdIdPrompt(), pluginPackagePrompt()];
return [pluginIdPrompt(), moduleIdIdPrompt(), pluginPackagePrompt(role)];
default:
return [];
}
+1
View File
@@ -97,6 +97,7 @@ export type PortableTemplateInputRoleParams =
role: 'frontend-plugin-module';
pluginId: string;
moduleId: string;
pluginPackage: string;
}
| {
role: 'backend-plugin-module';