diff --git a/.changeset/cli-defaults-introduce.md b/.changeset/cli-defaults-introduce.md new file mode 100644 index 0000000000..b6bb175e62 --- /dev/null +++ b/.changeset/cli-defaults-introduce.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-defaults': minor +--- + +Introduced `@backstage/cli-defaults`, a convenience package that bundles all standard Backstage CLI modules. Install this single package as a `devDependency` to get the full default set of CLI commands without listing each module individually. diff --git a/.changeset/cli-discover-modules.md b/.changeset/cli-discover-modules.md index 428a6eb883..751facdf1d 100644 --- a/.changeset/cli-discover-modules.md +++ b/.changeset/cli-discover-modules.md @@ -4,7 +4,23 @@ 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`: +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 `@backstage/cli-defaults` as a `devDependency` in your root `package.json`: + +```json +{ + "devDependencies": { + "@backstage/cli-defaults": "backstage:^" + } +} +``` + +If you are not using the Backstage Yarn plugin, run the following instead: + +```sh +yarn workspace root add --dev @backstage/cli-defaults +``` + +For fine-grained control you can instead install individual CLI modules: ```json { @@ -23,9 +39,3 @@ If no CLI modules are found in the project dependencies, the CLI falls back to t } } ``` - -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 -``` diff --git a/.changeset/create-app-cli-modules.md b/.changeset/create-app-cli-modules.md index 4e01b65260..c1a8ca9a26 100644 --- a/.changeset/create-app-cli-modules.md +++ b/.changeset/create-app-cli-modules.md @@ -2,4 +2,4 @@ '@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. +The create-app templates now include `@backstage/cli-defaults` as a `devDependency`, enabling the CLI's automatic module discovery for newly created projects. diff --git a/packages/cli-defaults/.eslintrc.js b/packages/cli-defaults/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/cli-defaults/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/cli-defaults/catalog-info.yaml b/packages/cli-defaults/catalog-info.yaml new file mode 100644 index 0000000000..3cac883c36 --- /dev/null +++ b/packages/cli-defaults/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-cli-defaults + title: '@backstage/cli-defaults' + description: Default set of CLI modules for the Backstage CLI +spec: + lifecycle: experimental + type: backstage-cli-module + owner: tooling-maintainers diff --git a/packages/cli-defaults/package.json b/packages/cli-defaults/package.json new file mode 100644 index 0000000000..587e54cfe9 --- /dev/null +++ b/packages/cli-defaults/package.json @@ -0,0 +1,48 @@ +{ + "name": "@backstage/cli-defaults", + "version": "0.0.0", + "description": "Default set of CLI modules for the Backstage CLI", + "backstage": { + "role": "cli-module" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/cli-defaults" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@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:^" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + } +} diff --git a/packages/cli-defaults/src/index.ts b/packages/cli-defaults/src/index.ts new file mode 100644 index 0000000000..95c9052b1d --- /dev/null +++ b/packages/cli-defaults/src/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2025 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 auth from '@backstage/cli-module-auth'; +import build from '@backstage/cli-module-build'; +import config from '@backstage/cli-module-config'; +import createGithubApp from '@backstage/cli-module-create-github-app'; +import info from '@backstage/cli-module-info'; +import lint from '@backstage/cli-module-lint'; +import maintenance from '@backstage/cli-module-maintenance'; +import migrate from '@backstage/cli-module-migrate'; +import newModule from '@backstage/cli-module-new'; +import testJest from '@backstage/cli-module-test-jest'; +import translations from '@backstage/cli-module-translations'; + +export default [ + auth, + build, + config, + createGithubApp, + info, + lint, + maintenance, + migrate, + newModule, + testJest, + translations, +]; diff --git a/packages/cli/package.json b/packages/cli/package.json index 2158b2b85e..600f150654 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,17 +48,7 @@ }, "dependencies": { "@backstage/cli-common": "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/cli-defaults": "workspace:^", "@backstage/cli-node": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/eslint-plugin": "workspace:^", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index f5056f8ba3..483717fc64 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -35,22 +35,13 @@ import { discoverCliModules } from './wiring/discoverCliModules'; `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`, + `Please add @backstage/cli-defaults as a devDependency ` + + `in your root package.json, or install individual ` + + `@backstage/cli-module-* packages for fine-grained control.\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')); + initializer.add(import('@backstage/cli-defaults')); } await initializer.run(); diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts index a09adbbc89..4e1067d780 100644 --- a/packages/cli/src/wiring/CliInitializer.ts +++ b/packages/cli/src/wiring/CliInitializer.ts @@ -39,18 +39,23 @@ function isNodeHidden(node: CommandNode): boolean { return children.every(child => isNodeHidden(child)); } -type UninitializedFeature = CliModule | Promise<{ default: CliModule }>; +type UninitializedFeature = + | CliModule + | CliModule[] + | Promise<{ default: CliModule | CliModule[] }>; export class CliInitializer { private graph = new CommandGraph(); private commandRegistry = new CommandRegistry(this.graph); - #uninitiazedFeatures: Promise[] = []; + #uninitiazedFeatures: Promise[] = []; add(feature: UninitializedFeature) { if (isPromise(feature)) { this.#uninitiazedFeatures.push( feature.then(f => unwrapFeature(f.default)), ); + } else if (Array.isArray(feature)) { + this.#uninitiazedFeatures.push(Promise.resolve(feature)); } else { this.#uninitiazedFeatures.push(Promise.resolve(feature)); } @@ -68,9 +73,14 @@ export class CliInitializer { } async #doInit() { - const features = await Promise.all(this.#uninitiazedFeatures); - for (const feature of features) { - await this.#register(feature); + const resolved = await Promise.all(this.#uninitiazedFeatures); + for (const featureOrArray of resolved) { + const features = Array.isArray(featureOrArray) + ? featureOrArray + : [featureOrArray]; + for (const feature of features) { + await this.#register(feature); + } } } @@ -186,8 +196,12 @@ export class CliInitializer { /** @internal */ export function unwrapFeature( - feature: CliModule | { default: CliModule }, -): CliModule { + feature: CliModule | CliModule[] | { default: CliModule | CliModule[] }, +): CliModule | CliModule[] { + if (Array.isArray(feature)) { + return feature; + } + if ('$$type' in feature) { return feature; } diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index 0115790b81..ff2f3eb4d6 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -50,6 +50,7 @@ jest.mock('./versions', () => ({ packageVersions: { root: '1.2.3', '@backstage/cli': '1.0.0', + '@backstage/cli-defaults': '1.0.0', '@backstage/cli-module-auth': '1.0.0', '@backstage/cli-module-build': '1.0.0', '@backstage/cli-module-config': '1.0.0', diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index bd19366e55..e770b88f3f 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -38,6 +38,7 @@ import { version as backendDefaults } from '../../../backend-defaults/package.js import { version as catalogClient } from '../../../catalog-client/package.json'; import { version as catalogModel } from '../../../catalog-model/package.json'; import { version as cli } from '../../../cli/package.json'; +import { version as cliDefaults } from '../../../cli-defaults/package.json'; import { version as cliModuleAuth } from '../../../cli-module-auth/package.json'; import { version as cliModuleBuild } from '../../../cli-module-build/package.json'; import { version as cliModuleConfig } from '../../../cli-module-config/package.json'; @@ -118,6 +119,7 @@ export const packageVersions = { '@backstage/catalog-client': catalogClient, '@backstage/catalog-model': catalogModel, '@backstage/cli': cli, + '@backstage/cli-defaults': cliDefaults, '@backstage/cli-module-auth': cliModuleAuth, '@backstage/cli-module-build': cliModuleBuild, '@backstage/cli-module-config': cliModuleConfig, diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index ca29f02dc0..4262e0fc82 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -28,17 +28,7 @@ ], "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/cli-defaults": "^{{version '@backstage/cli-defaults'}}", "@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}", "@jest/environment-jsdom-abstract": "^30.0.0", "@playwright/test": "^1.32.3", diff --git a/yarn.lock b/yarn.lock index 7b20688f16..643fa1d573 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2801,6 +2801,25 @@ __metadata: languageName: unknown linkType: soft +"@backstage/cli-defaults@workspace:^, @backstage/cli-defaults@workspace:packages/cli-defaults": + version: 0.0.0-use.local + resolution: "@backstage/cli-defaults@workspace:packages/cli-defaults" + 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:^" + languageName: unknown + linkType: soft + "@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" @@ -3121,17 +3140,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/cli-common": "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/cli-defaults": "workspace:^" "@backstage/cli-node": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^"