Add automatic discovery of CLI modules from project dependencies

The CLI now scans the project root's dependencies and devDependencies
for packages with the cli-module role, loading them automatically.
Falls back to the built-in set with a deprecation warning when no
modules are found. Updated create-app templates and the monorepo root
to include all CLI modules as explicit devDependencies.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-14 12:34:24 +01:00
parent 64a96d9d9f
commit d806b0cc9f
8 changed files with 191 additions and 22 deletions
+31
View File
@@ -0,0 +1,31 @@
---
'@backstage/cli': minor
---
The CLI now automatically discovers CLI modules from the project root's `dependencies` and `devDependencies`. Any installed package with the `cli-module` Backstage role will be loaded automatically without needing to be hardcoded in the CLI itself.
If no CLI modules are found in the project dependencies, the CLI falls back to the built-in set of modules and prints a deprecation warning. This fallback will be removed in a future release. To prepare for this, add the following CLI modules as `devDependencies` in your root `package.json`:
```json
{
"devDependencies": {
"@backstage/cli-module-auth": "backstage:^",
"@backstage/cli-module-build": "backstage:^",
"@backstage/cli-module-config": "backstage:^",
"@backstage/cli-module-create-github-app": "backstage:^",
"@backstage/cli-module-info": "backstage:^",
"@backstage/cli-module-lint": "backstage:^",
"@backstage/cli-module-maintenance": "backstage:^",
"@backstage/cli-module-migrate": "backstage:^",
"@backstage/cli-module-new": "backstage:^",
"@backstage/cli-module-test-jest": "backstage:^",
"@backstage/cli-module-translations": "backstage:^"
}
}
```
If you are not using the Backstage Yarn plugin, run the following instead:
```sh
yarn workspace root add --dev @backstage/cli-module-auth @backstage/cli-module-build @backstage/cli-module-config @backstage/cli-module-create-github-app @backstage/cli-module-info @backstage/cli-module-lint @backstage/cli-module-maintenance @backstage/cli-module-migrate @backstage/cli-module-new @backstage/cli-module-test-jest @backstage/cli-module-translations
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
The create-app templates now include all standard `@backstage/cli-module-*` packages as `devDependencies`, enabling the CLI's automatic module discovery for newly created projects.
+11
View File
@@ -125,6 +125,17 @@
},
"devDependencies": {
"@backstage/cli": "workspace:*",
"@backstage/cli-module-auth": "workspace:*",
"@backstage/cli-module-build": "workspace:*",
"@backstage/cli-module-config": "workspace:*",
"@backstage/cli-module-create-github-app": "workspace:*",
"@backstage/cli-module-info": "workspace:*",
"@backstage/cli-module-lint": "workspace:*",
"@backstage/cli-module-maintenance": "workspace:*",
"@backstage/cli-module-migrate": "workspace:*",
"@backstage/cli-module-new": "workspace:*",
"@backstage/cli-module-test-jest": "workspace:*",
"@backstage/cli-module-translations": "workspace:*",
"@backstage/codemods": "workspace:*",
"@backstage/create-app": "workspace:*",
"@backstage/e2e-test-utils": "workspace:*",
+35 -11
View File
@@ -14,20 +14,44 @@
* limitations under the License.
*/
import chalk from 'chalk';
import { CliInitializer } from './wiring/CliInitializer';
import { discoverCliModules } from './wiring/discoverCliModules';
(async () => {
const initializer = new CliInitializer();
initializer.add(import('@backstage/cli-module-build'));
initializer.add(import('@backstage/cli-module-config'));
initializer.add(import('@backstage/cli-module-create-github-app'));
initializer.add(import('@backstage/cli-module-info'));
initializer.add(import('@backstage/cli-module-lint'));
initializer.add(import('@backstage/cli-module-maintenance'));
initializer.add(import('@backstage/cli-module-migrate'));
initializer.add(import('@backstage/cli-module-new'));
initializer.add(import('@backstage/cli-module-test-jest'));
initializer.add(import('@backstage/cli-module-translations'));
initializer.add(import('@backstage/cli-module-auth'));
const discoveredModules = discoverCliModules();
if (discoveredModules.length > 0) {
for (const moduleName of discoveredModules) {
initializer.add(import(moduleName));
}
} else {
// No CLI modules found in the project root; fall back to the built-in
// set while printing a deprecation warning.
console.error(
chalk.yellow(
`No CLI modules found in the project root dependencies. ` +
`Falling back to the built-in set of modules.\n` +
`This fallback will be removed in a future release. ` +
`Please add the CLI modules you need as devDependencies ` +
`in your root package.json.\n`,
),
);
initializer.add(import('@backstage/cli-module-build'));
initializer.add(import('@backstage/cli-module-config'));
initializer.add(import('@backstage/cli-module-create-github-app'));
initializer.add(import('@backstage/cli-module-info'));
initializer.add(import('@backstage/cli-module-lint'));
initializer.add(import('@backstage/cli-module-maintenance'));
initializer.add(import('@backstage/cli-module-migrate'));
initializer.add(import('@backstage/cli-module-new'));
initializer.add(import('@backstage/cli-module-test-jest'));
initializer.add(import('@backstage/cli-module-translations'));
initializer.add(import('@backstage/cli-module-auth'));
}
await initializer.run();
})();
@@ -0,0 +1,65 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { targetPaths } from '@backstage/cli-common';
import { PackageRoles } from '@backstage/cli-node';
import fs from 'node:fs';
import { resolve as resolvePath } from 'node:path';
/**
* Scans the target project root's package.json for dependencies that are CLI
* modules (packages with `backstage.role === 'cli-module'`).
*
* Returns the names of discovered CLI module packages, or an empty array if
* none are found or the project root cannot be read.
*/
export function discoverCliModules(): string[] {
const rootDir = targetPaths.rootDir;
const pkgJsonPath = resolvePath(rootDir, 'package.json');
let projectPkg: {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
try {
projectPkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
} catch {
return [];
}
const allDeps = {
...projectPkg.dependencies,
...projectPkg.devDependencies,
};
const modules: string[] = [];
for (const depName of Object.keys(allDeps)) {
try {
const depPkgPath = require.resolve(`${depName}/package.json`, {
paths: [rootDir],
});
const depPkg = JSON.parse(fs.readFileSync(depPkgPath, 'utf8'));
if (PackageRoles.getRoleFromPackage(depPkg) === 'cli-module') {
modules.push(depName);
}
} catch {
// Skip packages that can't be resolved or read
}
}
return modules;
}
@@ -28,6 +28,17 @@
],
"devDependencies": {
"@backstage/cli": "^{{version '@backstage/cli'}}",
"@backstage/cli-module-auth": "^{{version '@backstage/cli-module-auth'}}",
"@backstage/cli-module-build": "^{{version '@backstage/cli-module-build'}}",
"@backstage/cli-module-config": "^{{version '@backstage/cli-module-config'}}",
"@backstage/cli-module-create-github-app": "^{{version '@backstage/cli-module-create-github-app'}}",
"@backstage/cli-module-info": "^{{version '@backstage/cli-module-info'}}",
"@backstage/cli-module-lint": "^{{version '@backstage/cli-module-lint'}}",
"@backstage/cli-module-maintenance": "^{{version '@backstage/cli-module-maintenance'}}",
"@backstage/cli-module-migrate": "^{{version '@backstage/cli-module-migrate'}}",
"@backstage/cli-module-new": "^{{version '@backstage/cli-module-new'}}",
"@backstage/cli-module-test-jest": "^{{version '@backstage/cli-module-test-jest'}}",
"@backstage/cli-module-translations": "^{{version '@backstage/cli-module-translations'}}",
"@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}",
"@jest/environment-jsdom-abstract": "^30.0.0",
"@playwright/test": "^1.32.3",
@@ -50,6 +50,17 @@
],
"devDependencies": {
"@backstage/cli": "^{{version '@backstage/cli'}}",
"@backstage/cli-module-auth": "^{{version '@backstage/cli-module-auth'}}",
"@backstage/cli-module-build": "^{{version '@backstage/cli-module-build'}}",
"@backstage/cli-module-config": "^{{version '@backstage/cli-module-config'}}",
"@backstage/cli-module-create-github-app": "^{{version '@backstage/cli-module-create-github-app'}}",
"@backstage/cli-module-info": "^{{version '@backstage/cli-module-info'}}",
"@backstage/cli-module-lint": "^{{version '@backstage/cli-module-lint'}}",
"@backstage/cli-module-maintenance": "^{{version '@backstage/cli-module-maintenance'}}",
"@backstage/cli-module-migrate": "^{{version '@backstage/cli-module-migrate'}}",
"@backstage/cli-module-new": "^{{version '@backstage/cli-module-new'}}",
"@backstage/cli-module-test-jest": "^{{version '@backstage/cli-module-test-jest'}}",
"@backstage/cli-module-translations": "^{{version '@backstage/cli-module-translations'}}",
"@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}",
"@jest/environment-jsdom-abstract": "^30.0.0",
"@playwright/test": "^1.32.3",
+22 -11
View File
@@ -2801,7 +2801,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-auth@workspace:^, @backstage/cli-module-auth@workspace:packages/cli-module-auth":
"@backstage/cli-module-auth@workspace:*, @backstage/cli-module-auth@workspace:^, @backstage/cli-module-auth@workspace:packages/cli-module-auth":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-auth@workspace:packages/cli-module-auth"
dependencies:
@@ -2824,7 +2824,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-build@workspace:^, @backstage/cli-module-build@workspace:packages/cli-module-build":
"@backstage/cli-module-build@workspace:*, @backstage/cli-module-build@workspace:^, @backstage/cli-module-build@workspace:packages/cli-module-build":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-build@workspace:packages/cli-module-build"
dependencies:
@@ -2888,7 +2888,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-config@workspace:^, @backstage/cli-module-config@workspace:packages/cli-module-config":
"@backstage/cli-module-config@workspace:*, @backstage/cli-module-config@workspace:^, @backstage/cli-module-config@workspace:packages/cli-module-config":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-config@workspace:packages/cli-module-config"
dependencies:
@@ -2910,7 +2910,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-create-github-app@workspace:^, @backstage/cli-module-create-github-app@workspace:packages/cli-module-create-github-app":
"@backstage/cli-module-create-github-app@workspace:*, @backstage/cli-module-create-github-app@workspace:^, @backstage/cli-module-create-github-app@workspace:packages/cli-module-create-github-app":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-create-github-app@workspace:packages/cli-module-create-github-app"
dependencies:
@@ -2932,7 +2932,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-info@workspace:^, @backstage/cli-module-info@workspace:packages/cli-module-info":
"@backstage/cli-module-info@workspace:*, @backstage/cli-module-info@workspace:^, @backstage/cli-module-info@workspace:packages/cli-module-info":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-info@workspace:packages/cli-module-info"
dependencies:
@@ -2948,7 +2948,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-lint@workspace:^, @backstage/cli-module-lint@workspace:packages/cli-module-lint":
"@backstage/cli-module-lint@workspace:*, @backstage/cli-module-lint@workspace:^, @backstage/cli-module-lint@workspace:packages/cli-module-lint":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-lint@workspace:packages/cli-module-lint"
dependencies:
@@ -2968,7 +2968,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-maintenance@workspace:^, @backstage/cli-module-maintenance@workspace:packages/cli-module-maintenance":
"@backstage/cli-module-maintenance@workspace:*, @backstage/cli-module-maintenance@workspace:^, @backstage/cli-module-maintenance@workspace:packages/cli-module-maintenance":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-maintenance@workspace:packages/cli-module-maintenance"
dependencies:
@@ -2985,7 +2985,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-migrate@workspace:^, @backstage/cli-module-migrate@workspace:packages/cli-module-migrate":
"@backstage/cli-module-migrate@workspace:*, @backstage/cli-module-migrate@workspace:^, @backstage/cli-module-migrate@workspace:packages/cli-module-migrate":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-migrate@workspace:packages/cli-module-migrate"
dependencies:
@@ -3005,7 +3005,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-new@workspace:^, @backstage/cli-module-new@workspace:packages/cli-module-new":
"@backstage/cli-module-new@workspace:*, @backstage/cli-module-new@workspace:^, @backstage/cli-module-new@workspace:packages/cli-module-new":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-new@workspace:packages/cli-module-new"
dependencies:
@@ -3016,7 +3016,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-test-jest@workspace:^, @backstage/cli-module-test-jest@workspace:packages/cli-module-test-jest":
"@backstage/cli-module-test-jest@workspace:*, @backstage/cli-module-test-jest@workspace:^, @backstage/cli-module-test-jest@workspace:packages/cli-module-test-jest":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-test-jest@workspace:packages/cli-module-test-jest"
dependencies:
@@ -3027,7 +3027,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/cli-module-translations@workspace:^, @backstage/cli-module-translations@workspace:packages/cli-module-translations":
"@backstage/cli-module-translations@workspace:*, @backstage/cli-module-translations@workspace:^, @backstage/cli-module-translations@workspace:packages/cli-module-translations":
version: 0.0.0-use.local
resolution: "@backstage/cli-module-translations@workspace:packages/cli-module-translations"
dependencies:
@@ -45616,6 +45616,17 @@ __metadata:
resolution: "root@workspace:."
dependencies:
"@backstage/cli": "workspace:*"
"@backstage/cli-module-auth": "workspace:*"
"@backstage/cli-module-build": "workspace:*"
"@backstage/cli-module-config": "workspace:*"
"@backstage/cli-module-create-github-app": "workspace:*"
"@backstage/cli-module-info": "workspace:*"
"@backstage/cli-module-lint": "workspace:*"
"@backstage/cli-module-maintenance": "workspace:*"
"@backstage/cli-module-migrate": "workspace:*"
"@backstage/cli-module-new": "workspace:*"
"@backstage/cli-module-test-jest": "workspace:*"
"@backstage/cli-module-translations": "workspace:*"
"@backstage/codemods": "workspace:*"
"@backstage/create-app": "workspace:*"
"@backstage/e2e-test-utils": "workspace:*"