cli: initial repo start implementation with package selection
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -57,8 +57,28 @@ export const buildPlugin = createCliPlugin({
|
||||
reg.addCommand({
|
||||
path: ['repo', 'start'],
|
||||
description: 'Starts packages in the repo for local development',
|
||||
execute: async () => {
|
||||
throw new Error('Not implemented');
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
|
||||
const defaultCommand = command
|
||||
.argument(
|
||||
'[...packageName]',
|
||||
'Run the specified package instead of the defaults.',
|
||||
)
|
||||
.option(...configOption)
|
||||
.option(
|
||||
'--plugin <pluginId>',
|
||||
'Start the dev entry-point for any matching plugin package in the repo',
|
||||
(opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]),
|
||||
Array<string>(),
|
||||
)
|
||||
.option(
|
||||
'--link <path>',
|
||||
'Link an external workspace for module resolution',
|
||||
)
|
||||
.action(lazy(() => import('./commands/repo/start'), 'command'));
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 {
|
||||
BackstagePackage,
|
||||
PackageGraph,
|
||||
PackageRole,
|
||||
} from '@backstage/cli-node';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { paths } from '../../../../lib/paths';
|
||||
|
||||
const ACCEPTED_PACKAGE_ROLES: Array<PackageRole | undefined> = [
|
||||
'frontend',
|
||||
'backend',
|
||||
'frontend-plugin',
|
||||
'backend-plugin',
|
||||
];
|
||||
|
||||
export async function command(
|
||||
packageNames: string[],
|
||||
options: { plugin: string[]; config: string[] },
|
||||
) {
|
||||
const targetPackages = await findTargetPackages(packageNames, options.plugin);
|
||||
console.log(
|
||||
`Starting ${targetPackages.map(p => p.packageJson.name).join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function findTargetPackages(packageNames: string[], pluginIds: string[]) {
|
||||
const targetPackages = new Array<BackstagePackage>();
|
||||
|
||||
const packages = await PackageGraph.listTargetPackages();
|
||||
|
||||
// Priorotize plugin options, so that the `start` script can contain a list of packages,
|
||||
// but make them easy to override by running for example `yarn start --plugin catalog`
|
||||
for (const pluginId of pluginIds) {
|
||||
const matchingPackages = packages.filter(pkg => {
|
||||
return (
|
||||
pluginId === pkg.packageJson.backstage?.pluginId &&
|
||||
ACCEPTED_PACKAGE_ROLES.includes(pkg.packageJson.backstage.role)
|
||||
);
|
||||
});
|
||||
if (!matchingPackages) {
|
||||
throw new Error(
|
||||
`Unable to find any plugin packages with plugin ID '${pluginId}'. Make sure backstage.pluginId is set in your package.json files by running 'yarn fix --publish'.`,
|
||||
);
|
||||
}
|
||||
targetPackages.push(...matchingPackages);
|
||||
}
|
||||
if (targetPackages.length > 0) {
|
||||
return targetPackages;
|
||||
}
|
||||
|
||||
// Next check if explicit package names are provided, use them in that case.
|
||||
for (const packageName of packageNames) {
|
||||
const matchingPackage = packages.find(pkg => {
|
||||
return packageName === pkg.packageJson.name;
|
||||
});
|
||||
if (!matchingPackage) {
|
||||
throw new Error(`Unable to find package by name '${packageName}'`);
|
||||
}
|
||||
targetPackages.push(matchingPackage);
|
||||
}
|
||||
|
||||
if (targetPackages.length > 0) {
|
||||
return targetPackages;
|
||||
}
|
||||
|
||||
// If on package names are provided, default to expect a single frontend and/or backend package
|
||||
for (const role of ['frontend', 'backend']) {
|
||||
const matchingPackages = packages.filter(
|
||||
pkg => pkg.packageJson.backstage?.role === role,
|
||||
);
|
||||
if (matchingPackages.length > 1) {
|
||||
// Final fallback is to check for the package path within the monorepo, packages/app or packages/backend
|
||||
const expectedPath = paths.resolveTargetRoot(
|
||||
role === 'frontend' ? 'packages/app' : 'packages/backend',
|
||||
);
|
||||
const matchByPath = matchingPackages.find(
|
||||
pkg => relativePath(expectedPath, pkg.dir) === '',
|
||||
);
|
||||
if (matchByPath) {
|
||||
targetPackages.push(matchByPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Found multiple packages with role '${role}' but none of the use the default path '${expectedPath}',` +
|
||||
`choose which packages you want to run by passing the package names explicitly ` +
|
||||
`as arguments, for example 'yarn backstage-cli repo start my-app my-backend'.`,
|
||||
);
|
||||
}
|
||||
|
||||
targetPackages.push(...matchingPackages);
|
||||
}
|
||||
if (targetPackages.length === 0) {
|
||||
throw new Error(
|
||||
`Unable to find any packages with role 'frontend' or 'backend'`,
|
||||
);
|
||||
}
|
||||
return targetPackages;
|
||||
}
|
||||
@@ -21,16 +21,19 @@ export function registerRepoCommands(command: Command) {
|
||||
command
|
||||
.command('start')
|
||||
.description('Starts packages in the repo for local development')
|
||||
.action(
|
||||
lazy(
|
||||
async () => ({
|
||||
command: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
}),
|
||||
'command',
|
||||
),
|
||||
);
|
||||
.argument(
|
||||
'[packageName...]',
|
||||
'Run the specified package instead of the defaults.',
|
||||
)
|
||||
.option(...configOption)
|
||||
.option(
|
||||
'--plugin <pluginId>',
|
||||
'Start the dev entry-point for any matching plugin package in the repo',
|
||||
(opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]),
|
||||
Array<string>(),
|
||||
)
|
||||
.option('--link <path>', 'Link an external workspace for module resolution')
|
||||
.action(lazy(() => import('./commands/repo/start'), 'command'));
|
||||
}
|
||||
|
||||
export function registerPackageCommands(command: Command) {
|
||||
|
||||
Reference in New Issue
Block a user