From de1c344077840264d0e0a6ff69d86445bbaf2ab1 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 11:45:47 -0400 Subject: [PATCH 1/8] feat(plugin-new): add local dev harnesses Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- CHANGELOG.md | 6 ++++ package.json | 2 +- .../new/__snapshots__/command.test.ts.snap | 32 +++++++++++++++++-- src/commands/new/command.test.ts | 12 ++++++- templates/plugin-new/backend/dev/index.ts | 6 ++++ templates/plugin-new/backend/package.json.hbs | 2 ++ templates/plugin-new/backend/tsconfig.json | 2 +- templates/plugin-new/frontend/dev/index.tsx | 9 ++++++ .../plugin-new/frontend/package.json.hbs | 3 ++ templates/plugin-new/frontend/tsconfig.json | 2 +- 10 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 templates/plugin-new/backend/dev/index.ts create mode 100644 templates/plugin-new/frontend/dev/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c4e8a..25ff44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `@red-hat-developer-hub/cli` are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.0.9 - 2026-09-15 + +### Added + +- **`plugin new`:** Add isolated local development harnesses and `yarn start` scripts for generated frontend and backend plugins. + ## 2.0.8 - 2026-09-15 ### Fixed diff --git a/package.json b/package.json index 45afae5..f2f7a0a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@red-hat-developer-hub/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "2.0.8", + "version": "2.0.9", "publishConfig": { "access": "public" }, diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index 3d016b4..b2b22fb 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -59,6 +59,7 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "types": "src/index.ts", "backstage": { "role": "backend-plugin", "pluginId": "example-plugin" }, "scripts": { + "start": "backstage-cli package start", "build": "yarn tsc && backstage-cli package build", "tsc": "tsc --declaration", "test": "backstage-cli package test --passWithNoTests" @@ -66,6 +67,7 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "dependencies": { "@backstage/backend-plugin-api": "1.10.0" }, "devDependencies": { "@backstage/cli": "0.36.5", + "@backstage/backend-defaults": "0.12.5", "@types/jest": "^29.5.14", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -114,12 +116,22 @@ export default plugin; "tsconfig.json", "{ "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": { "outDir": "dist-types", "rootDir": "." } } +", + ], + [ + "dev/index.ts", + "import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('../src')); +backend.start(); ", ], ] @@ -309,6 +321,7 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "types": "src/index.ts", "backstage": { "role": "frontend-plugin", "pluginId": "example-plugin" }, "scripts": { + "start": "backstage-cli package start", "build": "yarn tsc && backstage-cli package build", "tsc": "tsc --declaration", "test": "backstage-cli package test --passWithNoTests" @@ -319,8 +332,10 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p }, "devDependencies": { "@backstage/cli": "0.36.5", + "@backstage/frontend-defaults": "0.4.5", "@types/jest": "^29.5.14", "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.30.2", @@ -363,7 +378,7 @@ export default createFrontendPlugin({ "tsconfig.json", "{ "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": { "outDir": "dist-types", "rootDir": "." @@ -378,6 +393,19 @@ export default createFrontendPlugin({ export function PluginPage() { return React.createElement('h1', undefined, 'example-plugin'); } +", + ], + [ + "dev/index.tsx", + "import ReactDOM from 'react-dom/client'; + +import { createApp } from '@backstage/frontend-defaults'; + +import plugin from '../src'; + +const app = createApp({ features: [plugin] }); + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); ", ], ] diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index 284ead0..6f33d3c 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -40,10 +40,12 @@ describe('createPluginProject', () => { source: 'matrix', packages: new Map([ ['@backstage/backend-plugin-api', '1.10.0'], + ['@backstage/backend-defaults', '0.12.5'], ['@backstage/catalog-model', '1.10.0'], ['@backstage/cli', '0.36.5'], ['@backstage/core-plugin-api', '1.12.7'], ['@backstage/frontend-plugin-api', '0.17.2'], + ['@backstage/frontend-defaults', '0.4.5'], ['@backstage/plugin-catalog-node', '2.2.4'], ]), }); @@ -95,6 +97,11 @@ describe('createPluginProject', () => { '@red-hat-developer-hub/cli', ); expect(packageJson.packageManager).toBe('yarn@4.17.1'); + expect(packageJson.scripts.start).toBe( + type === 'catalog-processor-module' + ? undefined + : 'backstage-cli package start', + ); await expect( fs.readFile(path.join(output, '.yarnrc.yml'), 'utf8'), ).resolves.toBe('nodeLinker: node-modules\n'); @@ -115,7 +122,10 @@ describe('createPluginProject', () => { 'package.json', 'src/index.ts', 'tsconfig.json', - ...(type === 'frontend' ? ['src/PluginPage.tsx'] : []), + ...(type === 'frontend' + ? ['src/PluginPage.tsx', 'dev/index.tsx'] + : []), + ...(type === 'backend' ? ['dev/index.ts'] : []), ].map(async file => [ file, await fs.readFile(path.join(output, file), 'utf8'), diff --git a/templates/plugin-new/backend/dev/index.ts b/templates/plugin-new/backend/dev/index.ts new file mode 100644 index 0000000..8670438 --- /dev/null +++ b/templates/plugin-new/backend/dev/index.ts @@ -0,0 +1,6 @@ +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('../src')); +backend.start(); diff --git a/templates/plugin-new/backend/package.json.hbs b/templates/plugin-new/backend/package.json.hbs index 8ef1e7f..ae54ab8 100644 --- a/templates/plugin-new/backend/package.json.hbs +++ b/templates/plugin-new/backend/package.json.hbs @@ -8,6 +8,7 @@ "types": "src/index.ts", "backstage": { "role": "backend-plugin", "pluginId": "{{pluginId}}" }, "scripts": { + "start": "backstage-cli package start", "build": "yarn tsc && backstage-cli package build", "tsc": "tsc --declaration", "test": "backstage-cli package test --passWithNoTests" @@ -15,6 +16,7 @@ "dependencies": { "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}" }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", + "@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}", "@types/jest": "^29.5.14", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/templates/plugin-new/backend/tsconfig.json b/templates/plugin-new/backend/tsconfig.json index 7e1995b..2d98654 100644 --- a/templates/plugin-new/backend/tsconfig.json +++ b/templates/plugin-new/backend/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": { "outDir": "dist-types", "rootDir": "." diff --git a/templates/plugin-new/frontend/dev/index.tsx b/templates/plugin-new/frontend/dev/index.tsx new file mode 100644 index 0000000..c9e37ea --- /dev/null +++ b/templates/plugin-new/frontend/dev/index.tsx @@ -0,0 +1,9 @@ +import ReactDOM from 'react-dom/client'; + +import { createApp } from '@backstage/frontend-defaults'; + +import plugin from '../src'; + +const app = createApp({ features: [plugin] }); + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 48ec269..68b2d57 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -8,6 +8,7 @@ "types": "src/index.ts", "backstage": { "role": "frontend-plugin", "pluginId": "{{pluginId}}" }, "scripts": { + "start": "backstage-cli package start", "build": "yarn tsc && backstage-cli package build", "tsc": "tsc --declaration", "test": "backstage-cli package test --passWithNoTests" @@ -18,8 +19,10 @@ }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", + "@backstage/frontend-defaults": "{{versionQuery '@backstage/frontend-defaults'}}", "@types/jest": "^29.5.14", "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.30.2", diff --git a/templates/plugin-new/frontend/tsconfig.json b/templates/plugin-new/frontend/tsconfig.json index 7e1995b..2d98654 100644 --- a/templates/plugin-new/frontend/tsconfig.json +++ b/templates/plugin-new/frontend/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": { "outDir": "dist-types", "rootDir": "." From 3a0a2c5a64fd5ab4393024c6211be1c494edf459 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 12:20:29 -0400 Subject: [PATCH 2/8] docs(plugin-new): describe local dev harnesses Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- README.md | 2 +- src/commands/new/__snapshots__/command.test.ts.snap | 2 +- templates/plugin-new/backend/package.json.hbs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 937bde2..2339407 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Use `plugin new` to create a standalone, version-pinned dynamic plugin project: rhdh-cli plugin new my-plugin --type frontend --rhdh-version 2.1.0 ``` -Supported types are `frontend` (a New Frontend System, or NFS, page), `backend` (a minimal new-backend-system plugin), and `catalog-processor-module` (a catalog processor module). Use `--name ` as an alternative to the positional name, and `--output ` to select a destination. The generated project uses the target RHDH release's Backstage manifest for every `@backstage/*` dependency. For air-gapped environments, provide `--manifest-file` and set `RHDH_OFFLINE=true`. Export and package generated plugins with `npx @red-hat-developer-hub/cli`, or through RHDH Dynamic Plugin Factory, rather than adding the CLI as a project dependency. +Supported types are `frontend` (a New Frontend System, or NFS, page), `backend` (a minimal new-backend-system plugin), and `catalog-processor-module` (a catalog processor module). Frontend and backend projects include a `dev/` harness and `yarn start` for isolated development; catalog processor modules do not because they require a host backend plugin. Use `--name ` as an alternative to the positional name, and `--output ` to select a destination. The generated project uses the target RHDH release's Backstage manifest for every `@backstage/*` dependency. For air-gapped environments, provide `--manifest-file` and set `RHDH_OFFLINE=true`. Export and package generated plugins with `npx @red-hat-developer-hub/cli`, or through RHDH Dynamic Plugin Factory, rather than adding the CLI as a project dependency. ## Development diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index b2b22fb..e00e20a 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -66,8 +66,8 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p }, "dependencies": { "@backstage/backend-plugin-api": "1.10.0" }, "devDependencies": { - "@backstage/cli": "0.36.5", "@backstage/backend-defaults": "0.12.5", + "@backstage/cli": "0.36.5", "@types/jest": "^29.5.14", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/templates/plugin-new/backend/package.json.hbs b/templates/plugin-new/backend/package.json.hbs index ae54ab8..22efb5a 100644 --- a/templates/plugin-new/backend/package.json.hbs +++ b/templates/plugin-new/backend/package.json.hbs @@ -15,8 +15,8 @@ }, "dependencies": { "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}" }, "devDependencies": { - "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}", + "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@types/jest": "^29.5.14", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", From e25ae436ece740fe940d40ee79697d7207589c30 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 12:28:26 -0400 Subject: [PATCH 3/8] fix(plugin-new): use frontend dev app helper Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- src/commands/new/__snapshots__/command.test.ts.snap | 11 +++-------- src/commands/new/command.test.ts | 2 +- templates/plugin-new/frontend/dev/index.tsx | 8 ++------ templates/plugin-new/frontend/package.json.hbs | 3 +-- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index e00e20a..5427fb9 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -332,10 +332,9 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p }, "devDependencies": { "@backstage/cli": "0.36.5", - "@backstage/frontend-defaults": "0.4.5", + "@backstage/frontend-dev-utils": "0.4.5", "@types/jest": "^29.5.14", "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.30.2", @@ -397,15 +396,11 @@ export function PluginPage() { ], [ "dev/index.tsx", - "import ReactDOM from 'react-dom/client'; - -import { createApp } from '@backstage/frontend-defaults'; + "import { createDevApp } from '@backstage/frontend-dev-utils'; import plugin from '../src'; -const app = createApp({ features: [plugin] }); - -ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); +createDevApp({ features: [plugin] }); ", ], ] diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index 6f33d3c..0c8aacb 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -45,7 +45,7 @@ describe('createPluginProject', () => { ['@backstage/cli', '0.36.5'], ['@backstage/core-plugin-api', '1.12.7'], ['@backstage/frontend-plugin-api', '0.17.2'], - ['@backstage/frontend-defaults', '0.4.5'], + ['@backstage/frontend-dev-utils', '0.4.5'], ['@backstage/plugin-catalog-node', '2.2.4'], ]), }); diff --git a/templates/plugin-new/frontend/dev/index.tsx b/templates/plugin-new/frontend/dev/index.tsx index c9e37ea..21f8429 100644 --- a/templates/plugin-new/frontend/dev/index.tsx +++ b/templates/plugin-new/frontend/dev/index.tsx @@ -1,9 +1,5 @@ -import ReactDOM from 'react-dom/client'; - -import { createApp } from '@backstage/frontend-defaults'; +import { createDevApp } from '@backstage/frontend-dev-utils'; import plugin from '../src'; -const app = createApp({ features: [plugin] }); - -ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); +createDevApp({ features: [plugin] }); diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 68b2d57..4dd8bd3 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -19,10 +19,9 @@ }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", - "@backstage/frontend-defaults": "{{versionQuery '@backstage/frontend-defaults'}}", + "@backstage/frontend-dev-utils": "{{versionQuery '@backstage/frontend-dev-utils'}}", "@types/jest": "^29.5.14", "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.30.2", From 1961cc55d09bda15ba8a7ea6a745c6237c3a1569 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 13:06:45 -0400 Subject: [PATCH 4/8] test(plugin-new): sort mock package versions Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- src/commands/new/command.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index 0c8aacb..25a221b 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -39,13 +39,13 @@ describe('createPluginProject', () => { backstageVersion: '1.54.0', source: 'matrix', packages: new Map([ - ['@backstage/backend-plugin-api', '1.10.0'], ['@backstage/backend-defaults', '0.12.5'], + ['@backstage/backend-plugin-api', '1.10.0'], ['@backstage/catalog-model', '1.10.0'], ['@backstage/cli', '0.36.5'], ['@backstage/core-plugin-api', '1.12.7'], - ['@backstage/frontend-plugin-api', '0.17.2'], ['@backstage/frontend-dev-utils', '0.4.5'], + ['@backstage/frontend-plugin-api', '0.17.2'], ['@backstage/plugin-catalog-node', '2.2.4'], ]), }); From c33ffa10376b44b17943060238d9e4ef533d7570 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 13:16:47 -0400 Subject: [PATCH 5/8] feat(plugin-new): use JSX frontend template Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- src/commands/new/__snapshots__/command.test.ts.snap | 10 +++++----- src/commands/new/command.test.ts | 4 ++-- templates/plugin-new/frontend/package.json.hbs | 4 ++-- .../frontend/src/{index.ts.hbs => index.tsx.hbs} | 3 +-- templates/plugin-new/frontend/tsconfig.json | 1 + 5 files changed, 11 insertions(+), 11 deletions(-) rename templates/plugin-new/frontend/src/{index.ts.hbs => index.tsx.hbs} (80%) diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index 5427fb9..c68bc72 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -317,8 +317,8 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "packageManager": "yarn@4.17.1", "private": true, "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", + "main": "src/index.tsx", + "types": "src/index.tsx", "backstage": { "role": "frontend-plugin", "pluginId": "example-plugin" }, "scripts": { "start": "backstage-cli package start", @@ -351,10 +351,9 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p ", ], [ - "src/index.ts", + "src/index.tsx", "import { createRouteRef } from '@backstage/core-plugin-api'; import { createFrontendPlugin, PageBlueprint } from '@backstage/frontend-plugin-api'; -import React from 'react'; const rootRouteRef = createRouteRef({ id: 'example-plugin' }); const page = PageBlueprint.make({ @@ -362,7 +361,7 @@ const page = PageBlueprint.make({ title: 'example-plugin', path: '/example-plugin', routeRef: rootRouteRef, - loader: () => import('./PluginPage').then(m => React.createElement(m.PluginPage)), + loader: () => import('./PluginPage').then(m => ), }, }); @@ -379,6 +378,7 @@ export default createFrontendPlugin({ "extends": "@backstage/cli/config/tsconfig.json", "include": ["src", "dev"], "compilerOptions": { + "jsx": "react-jsx", "outDir": "dist-types", "rootDir": "." } diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index 25a221b..fd3a717 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -57,7 +57,7 @@ describe('createPluginProject', () => { }); it.each([ - ['frontend', 'src/index.ts', 'PageBlueprint'], + ['frontend', 'src/index.tsx', 'PageBlueprint'], ['backend', 'src/index.ts', 'createBackendPlugin'], [ 'catalog-processor-module', @@ -120,7 +120,7 @@ describe('createPluginProject', () => { 'README.md', 'backstage.json', 'package.json', - 'src/index.ts', + entryPoint, 'tsconfig.json', ...(type === 'frontend' ? ['src/PluginPage.tsx', 'dev/index.tsx'] diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 4dd8bd3..0047e15 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -4,8 +4,8 @@ "packageManager": "yarn@4.17.1", "private": true, "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", + "main": "src/index.tsx", + "types": "src/index.tsx", "backstage": { "role": "frontend-plugin", "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/templates/plugin-new/frontend/src/index.ts.hbs b/templates/plugin-new/frontend/src/index.tsx.hbs similarity index 80% rename from templates/plugin-new/frontend/src/index.ts.hbs rename to templates/plugin-new/frontend/src/index.tsx.hbs index be474c2..b1915cb 100644 --- a/templates/plugin-new/frontend/src/index.ts.hbs +++ b/templates/plugin-new/frontend/src/index.tsx.hbs @@ -1,6 +1,5 @@ import { createRouteRef } from '@backstage/core-plugin-api'; import { createFrontendPlugin, PageBlueprint } from '@backstage/frontend-plugin-api'; -import React from 'react'; const rootRouteRef = createRouteRef({ id: '{{pluginId}}' }); const page = PageBlueprint.make({ @@ -8,7 +7,7 @@ const page = PageBlueprint.make({ title: '{{pluginId}}', path: '/{{pluginId}}', routeRef: rootRouteRef, - loader: () => import('./PluginPage').then(m => React.createElement(m.PluginPage)), + loader: () => import('./PluginPage').then(m => ), }, }); diff --git a/templates/plugin-new/frontend/tsconfig.json b/templates/plugin-new/frontend/tsconfig.json index 2d98654..b7433db 100644 --- a/templates/plugin-new/frontend/tsconfig.json +++ b/templates/plugin-new/frontend/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@backstage/cli/config/tsconfig.json", "include": ["src", "dev"], "compilerOptions": { + "jsx": "react-jsx", "outDir": "dist-types", "rootDir": "." } From 7d137301ef46d00ebe17341e3a865c0efb42db69 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 13:21:34 -0400 Subject: [PATCH 6/8] fix(plugin-new): publish frontend federation assets Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- src/commands/new/__snapshots__/command.test.ts.snap | 1 + src/commands/new/command.test.ts | 3 +++ templates/plugin-new/frontend/package.json.hbs | 1 + 3 files changed, 5 insertions(+) diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index c68bc72..2616ec0 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -317,6 +317,7 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "packageManager": "yarn@4.17.1", "private": true, "license": "Apache-2.0", + "files": ["dist"], "main": "src/index.tsx", "types": "src/index.tsx", "backstage": { "role": "frontend-plugin", "pluginId": "example-plugin" }, diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index fd3a717..0757f70 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -97,6 +97,9 @@ describe('createPluginProject', () => { '@red-hat-developer-hub/cli', ); expect(packageJson.packageManager).toBe('yarn@4.17.1'); + expect(packageJson.files).toEqual( + type === 'frontend' ? ['dist'] : undefined, + ); expect(packageJson.scripts.start).toBe( type === 'catalog-processor-module' ? undefined diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 0047e15..865c324 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -4,6 +4,7 @@ "packageManager": "yarn@4.17.1", "private": true, "license": "Apache-2.0", + "files": ["dist"], "main": "src/index.tsx", "types": "src/index.tsx", "backstage": { "role": "frontend-plugin", "pluginId": "{{pluginId}}" }, From 6a8ef29ce61a543b1df2de24ccf8ff9bf241fe20 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 13:54:10 -0400 Subject: [PATCH 7/8] refactor(plugin-new): separate frontend entry facade Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- .../new/__snapshots__/command.test.ts.snap | 55 +++++++++++-------- src/commands/new/command.test.ts | 4 +- .../plugin-new/frontend/package.json.hbs | 13 ++++- .../frontend/src/PluginPage.tsx.hbs | 4 +- .../plugin-new/frontend/src/index.ts.hbs | 1 + .../src/{index.tsx.hbs => plugin.tsx.hbs} | 3 +- 6 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 templates/plugin-new/frontend/src/index.ts.hbs rename templates/plugin-new/frontend/src/{index.tsx.hbs => plugin.tsx.hbs} (91%) diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index 2616ec0..ece397e 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -318,8 +318,17 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "private": true, "license": "Apache-2.0", "files": ["dist"], - "main": "src/index.tsx", - "types": "src/index.tsx", + "main": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": ["package.json"] + } + }, "backstage": { "role": "frontend-plugin", "pluginId": "example-plugin" }, "scripts": { "start": "backstage-cli package start", @@ -352,7 +361,25 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p ", ], [ - "src/index.tsx", + "src/index.ts", + "export { plugin as default } from './plugin'; +", + ], + [ + "tsconfig.json", + "{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src", "dev"], + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "dist-types", + "rootDir": "." + } +} +", + ], + [ + "src/plugin.tsx", "import { createRouteRef } from '@backstage/core-plugin-api'; import { createFrontendPlugin, PageBlueprint } from '@backstage/frontend-plugin-api'; @@ -365,33 +392,17 @@ const page = PageBlueprint.make({ loader: () => import('./PluginPage').then(m => ), }, }); - -export default createFrontendPlugin({ +export const plugin = createFrontendPlugin({ pluginId: 'example-plugin', extensions: [page], routes: { root: rootRouteRef }, }); -", - ], - [ - "tsconfig.json", - "{ - "extends": "@backstage/cli/config/tsconfig.json", - "include": ["src", "dev"], - "compilerOptions": { - "jsx": "react-jsx", - "outDir": "dist-types", - "rootDir": "." - } -} ", ], [ "src/PluginPage.tsx", - "import React from 'react'; - -export function PluginPage() { - return React.createElement('h1', undefined, 'example-plugin'); + "export function PluginPage() { + return

example-plugin

; } ", ], diff --git a/src/commands/new/command.test.ts b/src/commands/new/command.test.ts index 0757f70..2280af1 100644 --- a/src/commands/new/command.test.ts +++ b/src/commands/new/command.test.ts @@ -57,7 +57,7 @@ describe('createPluginProject', () => { }); it.each([ - ['frontend', 'src/index.tsx', 'PageBlueprint'], + ['frontend', 'src/index.ts', 'export { plugin as default }'], ['backend', 'src/index.ts', 'createBackendPlugin'], [ 'catalog-processor-module', @@ -126,7 +126,7 @@ describe('createPluginProject', () => { entryPoint, 'tsconfig.json', ...(type === 'frontend' - ? ['src/PluginPage.tsx', 'dev/index.tsx'] + ? ['src/plugin.tsx', 'src/PluginPage.tsx', 'dev/index.tsx'] : []), ...(type === 'backend' ? ['dev/index.ts'] : []), ].map(async file => [ diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 865c324..7032bf7 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -5,8 +5,17 @@ "private": true, "license": "Apache-2.0", "files": ["dist"], - "main": "src/index.tsx", - "types": "src/index.tsx", + "main": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": ["package.json"] + } + }, "backstage": { "role": "frontend-plugin", "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/templates/plugin-new/frontend/src/PluginPage.tsx.hbs b/templates/plugin-new/frontend/src/PluginPage.tsx.hbs index ad388ee..0da35eb 100644 --- a/templates/plugin-new/frontend/src/PluginPage.tsx.hbs +++ b/templates/plugin-new/frontend/src/PluginPage.tsx.hbs @@ -1,5 +1,3 @@ -import React from 'react'; - export function PluginPage() { - return React.createElement('h1', undefined, '{{pluginId}}'); + return

{{pluginId}}

; } diff --git a/templates/plugin-new/frontend/src/index.ts.hbs b/templates/plugin-new/frontend/src/index.ts.hbs new file mode 100644 index 0000000..2f53769 --- /dev/null +++ b/templates/plugin-new/frontend/src/index.ts.hbs @@ -0,0 +1 @@ +export { plugin as default } from './plugin'; diff --git a/templates/plugin-new/frontend/src/index.tsx.hbs b/templates/plugin-new/frontend/src/plugin.tsx.hbs similarity index 91% rename from templates/plugin-new/frontend/src/index.tsx.hbs rename to templates/plugin-new/frontend/src/plugin.tsx.hbs index b1915cb..9a2635c 100644 --- a/templates/plugin-new/frontend/src/index.tsx.hbs +++ b/templates/plugin-new/frontend/src/plugin.tsx.hbs @@ -10,8 +10,7 @@ const page = PageBlueprint.make({ loader: () => import('./PluginPage').then(m => ), }, }); - -export default createFrontendPlugin({ +export const plugin = createFrontendPlugin({ pluginId: '{{pluginId}}', extensions: [page], routes: { root: rootRouteRef }, From 87ccaceadd3e0847962ac347570a308b4cb7d164 Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 15 Sep 2026 15:08:12 -0400 Subject: [PATCH 8/8] fix(plugin-new): align frontend package metadata Assisted-By: OpenCode Signed-off-by: Stan Lewis rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- src/commands/new/__snapshots__/command.test.ts.snap | 6 ++++++ templates/plugin-new/frontend/package.json.hbs | 5 +++++ templates/plugin-new/frontend/src/plugin.tsx.hbs | 1 + 3 files changed, 12 insertions(+) diff --git a/src/commands/new/__snapshots__/command.test.ts.snap b/src/commands/new/__snapshots__/command.test.ts.snap index ece397e..10ba274 100644 --- a/src/commands/new/__snapshots__/command.test.ts.snap +++ b/src/commands/new/__snapshots__/command.test.ts.snap @@ -320,6 +320,11 @@ RHDH Dynamic Plugin Factory is the preferred workflow for repeatable build and p "files": ["dist"], "main": "src/index.ts", "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, "exports": { ".": "./src/index.ts", "./package.json": "./package.json" @@ -392,6 +397,7 @@ const page = PageBlueprint.make({ loader: () => import('./PluginPage').then(m => ), }, }); + export const plugin = createFrontendPlugin({ pluginId: 'example-plugin', extensions: [page], diff --git a/templates/plugin-new/frontend/package.json.hbs b/templates/plugin-new/frontend/package.json.hbs index 7032bf7..7143035 100644 --- a/templates/plugin-new/frontend/package.json.hbs +++ b/templates/plugin-new/frontend/package.json.hbs @@ -7,6 +7,11 @@ "files": ["dist"], "main": "src/index.ts", "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, "exports": { ".": "./src/index.ts", "./package.json": "./package.json" diff --git a/templates/plugin-new/frontend/src/plugin.tsx.hbs b/templates/plugin-new/frontend/src/plugin.tsx.hbs index 9a2635c..1da5a55 100644 --- a/templates/plugin-new/frontend/src/plugin.tsx.hbs +++ b/templates/plugin-new/frontend/src/plugin.tsx.hbs @@ -10,6 +10,7 @@ const page = PageBlueprint.make({ loader: () => import('./PluginPage').then(m => ), }, }); + export const plugin = createFrontendPlugin({ pluginId: '{{pluginId}}', extensions: [page],