Merge pull request #29171 from backstage/sennyeya/module-new

module: move new commands to their own module
This commit is contained in:
Patrik Oldsberg
2025-04-14 12:48:25 +02:00
committed by GitHub
35 changed files with 306 additions and 79 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': minor
---
Internal update to move the `new` and `create-github-app` to their own module.
@@ -18,9 +18,11 @@ Commands:
config:docs
config:print
config:schema
create-github-app
help [command]
info
migrate [command]
new
package [command]
repo [command]
versions:bump
@@ -132,6 +134,15 @@ Options:
--version
```
### `backstage-cli-alpha create-github-app`
```
Usage: program [options] <github-org>
Options:
-h, --help
```
### `backstage-cli-alpha info`
```
@@ -204,6 +215,23 @@ Options:
-h, --help
```
### `backstage-cli-alpha new`
```
Usage: program [options]
Options:
--baseVersion <version>
--license <license>
--no-private
--npm-registry <URL>
--option <name>=<value>
--scope <scope>
--select <name>
--skip-install
-h, --help
```
### `backstage-cli-alpha package`
```
+2
View File
@@ -25,6 +25,8 @@ import chalk from 'chalk';
),
);
const initializer = new CliInitializer();
initializer.add(import('./modules/new/alpha'));
initializer.add(import('./modules/create-github-app/alpha'));
initializer.add(import('./modules/info/alpha'));
initializer.add(import('./modules/config/alpha'));
initializer.add(import('./modules/build/alpha'));
+6 -61
View File
@@ -15,7 +15,6 @@
*/
import { Command } from 'commander';
import { lazy } from '../lib/lazy';
import { registerCommands as registerConfigCommands } from '../modules/config';
import {
registerPackageCommands as registerPackageBuildCommands,
@@ -40,6 +39,9 @@ import {
registerPackageCommands as registerMaintenancePackageCommands,
registerRepoCommands as registerMaintenanceRepoCommands,
} from '../modules/maintenance';
import { removed } from '../lib/removed';
import { registerCommands as registerNewCommands } from '../modules/new';
import { registerCommands as registerCreateGithubAppCommands } from '../modules/create-github-app';
export function registerRepoCommand(program: Command) {
const command = program
@@ -66,62 +68,16 @@ export function registerScriptCommand(program: Command) {
}
export function registerCommands(program: Command) {
program
.command('new')
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./new/new'), 'default'));
registerConfigCommands(program);
registerRepoCommand(program);
registerScriptCommand(program);
registerMigrateCommand(program);
registerBuildCommands(program);
registerInfoCommands(program);
program
.command('create-github-app <github-org>')
.description('Create new GitHub App in your organization.')
.action(lazy(() => import('./create-github-app'), 'default'));
registerNewCommands(program);
registerCreateGithubAppCommands(program);
// Notifications for removed commands
program
.command('create')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('create-plugin')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('plugin:diff')
.allowUnknownOption(true)
@@ -145,14 +101,3 @@ export function registerCommands(program: Command) {
program.command('install').allowUnknownOption(true).action(removed());
program.command('onboard').allowUnknownOption(true).action(removed());
}
function removed(message?: string) {
return () => {
console.error(
message
? `This command has been removed, ${message}`
: 'This command has been removed',
);
process.exit(1);
};
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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.
*/
export function removed(message?: string) {
return () => {
console.error(
message
? `This command has been removed, ${message}`
: 'This command has been removed',
);
process.exit(1);
};
}
@@ -0,0 +1,38 @@
/*
* 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 { createCliPlugin } from '../../wiring/factory';
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
export default createCliPlugin({
pluginId: 'new',
init: async reg => {
reg.addCommand({
path: ['create-github-app'],
description: 'Create new GitHub App in your organization.',
execute: async ({ args }) => {
const command = new Command();
const defaultCommand = command
.argument('<github-org>')
.action(
lazy(() => import('./commands/create-github-app'), 'default'),
);
await defaultCommand.parseAsync(args, { from: 'user' });
},
});
},
});
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import chalk from 'chalk';
import { stringify as stringifyYaml } from 'yaml';
import inquirer, { Question, Answers } from 'inquirer';
import { paths } from '../../lib/paths';
import { paths } from '../../../../lib/paths';
import { GithubCreateAppServer } from './GithubCreateAppServer';
import openBrowser from 'react-dev-utils/openBrowser';
@@ -0,0 +1,29 @@
/*
* 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 { Command } from 'commander';
import { lazy } from '../../lib/lazy';
export function registerCommands(program: Command) {
program
.command('create-github-app <github-org>')
.description('Create new GitHub App in your organization.')
.action(
lazy(
() => import('../create-github-app/commands/create-github-app'),
'default',
),
);
}
+86
View File
@@ -0,0 +1,86 @@
/*
* 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 { createCliPlugin } from '../../wiring/factory';
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
import { removed } from '../../lib/removed';
export default createCliPlugin({
pluginId: 'new',
init: async reg => {
reg.addCommand({
path: ['new'],
description:
'Open up an interactive guide to creating new things in your app',
execute: async ({ args }) => {
const command = new Command();
const defaultCommand = command
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./commands/new'), 'default'));
await defaultCommand.parseAsync(args, { from: 'user' });
},
});
reg.addCommand({
path: ['create'],
description: 'Create a new Backstage app',
deprecated: true,
execute: async () => {
removed("use 'backstage-cli new' instead")();
},
});
reg.addCommand({
path: ['create-plugin'],
description: 'Create a new Backstage plugin',
deprecated: true,
execute: async () => {
removed("use 'backstage-cli new' instead")();
},
});
},
});
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { createNewPackage } from '../../lib/new/createNewPackage';
import { createNewPackage } from '../lib/createNewPackage';
import { default as newCommand } from './new';
jest.mock('../../lib/new/createNewPackage');
jest.mock('../lib/createNewPackage');
describe.each([
[undefined, undefined, undefined],
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createNewPackage } from '../../lib/new/createNewPackage';
import { createNewPackage } from '../lib/createNewPackage';
type ArgOptions = {
option: string[];
+65
View File
@@ -0,0 +1,65 @@
/*
* 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 { Command } from 'commander';
import { lazy } from '../../lib/lazy';
import { removed } from '../../lib/removed';
export function registerCommands(program: Command) {
program
.command('new')
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./commands/new'), 'default'));
program
.command('create')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('create-plugin')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
}
@@ -24,9 +24,9 @@ import startCase from 'lodash/startCase';
import upperCase from 'lodash/upperCase';
import upperFirst from 'lodash/upperFirst';
import lowerFirst from 'lodash/lowerFirst';
import { Lockfile } from '../../versioning';
import { paths } from '../../paths';
import { createPackageVersionProvider } from '../../version';
import { Lockfile } from '../../../../lib/versioning';
import { paths } from '../../../../lib/paths';
import { createPackageVersionProvider } from '../../../../lib/version';
const builtInHelpers = {
camelCase,
@@ -15,8 +15,8 @@
*/
import { assertError } from '@backstage/errors';
import { addCodeownersEntry } from '../../codeowners';
import { Task } from '../../tasks';
import { addCodeownersEntry } from '../../../../lib/codeowners';
import { Task } from '../../../../lib/tasks';
import {
PortableTemplate,
PortableTemplateConfig,
@@ -16,8 +16,8 @@
import fs from 'fs-extra';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import { paths } from '../../paths';
import { Task } from '../../tasks';
import { paths } from '../../../../lib/paths';
import { Task } from '../../../../lib/tasks';
import { PortableTemplateInput } from '../types';
export async function installNewPackage(input: PortableTemplateInput) {
@@ -17,7 +17,7 @@
import { relative as relativePath } from 'node:path';
import { writeTemplateContents } from './writeTemplateContents';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { paths } from '../../paths';
import { paths } from '../../../../lib/paths';
const baseConfig = {
version: '0.1.0',
@@ -17,7 +17,7 @@
import fs from 'fs-extra';
import { dirname, resolve as resolvePath } from 'path';
import { paths } from '../../paths';
import { paths } from '../../../../lib/paths';
import { PortableTemplate, PortableTemplateInput } from '../types';
import { ForwardedError, InputError } from '@backstage/errors';
import { isMonoRepo as getIsMonoRepo } from '@backstage/cli-node';
@@ -15,8 +15,11 @@
*/
import inquirer, { DistinctQuestion } from 'inquirer';
import { getCodeownersFilePath, parseOwnerIds } from '../../codeowners';
import { paths } from '../../paths';
import {
getCodeownersFilePath,
parseOwnerIds,
} from '../../../../lib/codeowners';
import { paths } from '../../../../lib/paths';
import {
PortableTemplateConfig,
PortableTemplateInput,
@@ -20,7 +20,7 @@ import recursiveReaddir from 'recursive-readdir';
import { resolve as resolvePath, relative as relativePath } from 'path';
import { dirname } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { paths } from '../../paths';
import { paths } from '../../../../lib/paths';
import {
PortableTemplateFile,
PortableTemplatePointer,
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { resolve as resolvePath, dirname, isAbsolute } from 'node:path';
import { paths } from '../../paths';
import { paths } from '../../../../lib/paths';
import { defaultTemplates } from '../defaultTemplates';
import {
PortableTemplateConfig,
+1 -1
View File
@@ -88,7 +88,7 @@ export class CliInitializer {
);
} else {
argParser
.command(node.name)
.command(node.name, { hidden: !!node.command.deprecated })
.description(node.command.description)
.helpOption(false)
.allowUnknownOption(true)
+1
View File
@@ -18,6 +18,7 @@ import { CommandRegistry } from './CommandRegistry';
export interface BackstageCommand {
path: string[];
description: string;
deprecated?: boolean;
execute: (options: { args: string[] }) => Promise<void>;
}