From 7191abef8662d94ec7e1a46a99320d201c13f94f Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 11 Feb 2026 15:13:05 +0100 Subject: [PATCH] Add support for pluginPackage to frontend modules Signed-off-by: Vincenzo Scamporlino --- .../collectPortableTemplateInput.test.ts | 66 +++++++++++++++++++ .../collectPortableTemplateInput.ts | 64 ++++++++++++++---- packages/cli-module-new/src/lib/types.ts | 1 + 3 files changed, 120 insertions(+), 11 deletions(-) diff --git a/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.test.ts b/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.test.ts index ee45f7a708..c0cb4dd563 100644 --- a/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.test.ts +++ b/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.test.ts @@ -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', + }); + }); + }); }); diff --git a/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.ts b/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.ts index d7521407dc..d1676ce931 100644 --- a/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.ts +++ b/packages/cli-module-new/src/lib/preparation/collectPortableTemplateInput.ts @@ -28,11 +28,32 @@ import { import { PortableTemplate } from '../types'; import { resolvePackageParams } from './resolvePackageParams'; -const knownPluginPackages: Record = { +const knownBackendPluginPackages: Record = { + 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 = { + 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 []; } diff --git a/packages/cli-module-new/src/lib/types.ts b/packages/cli-module-new/src/lib/types.ts index 913c74b672..6c6cd8b584 100644 --- a/packages/cli-module-new/src/lib/types.ts +++ b/packages/cli-module-new/src/lib/types.ts @@ -97,6 +97,7 @@ export type PortableTemplateInputRoleParams = role: 'frontend-plugin-module'; pluginId: string; moduleId: string; + pluginPackage: string; } | { role: 'backend-plugin-module';