cli: migrate start command to module

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-04-04 21:19:43 +01:00
parent 80b82f1493
commit bd3943b7ae
7 changed files with 353 additions and 22 deletions
+7 -22
View File
@@ -16,15 +16,16 @@
import { Command } from 'commander';
import { lazy } from '../lib/lazy';
import {
configOption,
registerCommands as registerConfigCommands,
} from '../modules/config';
import { registerCommands as registerConfigCommands } from '../modules/config';
import {
registerPackageCommands as registerPackageBuildCommands,
registerRepoCommands as registerRepoBuildCommands,
registerCommands as registerBuildCommands,
} from '../modules/build';
import {
registerPackageCommands as registerPackageStartCommands,
registerRepoCommands as registerRepoStartCommands,
} from '../modules/start';
import { registerCommands as registerInfoCommands } from '../modules/info';
import { registerCommands as registerMigrateCommand } from '../modules/migrate';
import {
@@ -45,6 +46,7 @@ export function registerRepoCommand(program: Command) {
.command('repo [command]')
.description('Command that run across an entire Backstage project');
registerRepoStartCommands(command);
registerRepoBuildCommands(command);
registerRepoTestCommands(command);
registerRepoLintCommands(command);
@@ -56,24 +58,7 @@ export function registerScriptCommand(program: Command) {
.command('package [command]')
.description('Lifecycle scripts for individual packages');
command
.command('start')
.description('Start a package for local development')
.option(...configOption)
.option('--role <name>', 'Run the command with an explicit package role')
.option('--check', 'Enable type checking and linting if available')
.option('--inspect [host]', 'Enable debugger in Node.js environments')
.option(
'--inspect-brk [host]',
'Enable debugger in Node.js environments, breaking before code starts',
)
.option(
'--require <path...>',
'Add a --require argument to the node process',
)
.option('--link <path>', 'Link an external workspace for module resolution')
.action(lazy(() => import('./start'), 'command'));
registerPackageStartCommands(command);
registerPackageBuildCommands(command);
registerPackageTestCommands(command);
registerMaintenancePackageCommands(command);
+67
View File
@@ -0,0 +1,67 @@
/*
* 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 { Command } from 'commander';
import { createCliPlugin } from '../../wiring/factory';
import { lazy } from '../../lib/lazy';
import { configOption } from '../config';
export const buildPlugin = createCliPlugin({
pluginId: 'build',
init: async reg => {
reg.addCommand({
path: ['package', 'start'],
description: 'Start a package for local development',
execute: async ({ args }) => {
const command = new Command();
const defaultCommand = command
.option(...configOption)
.option(
'--role <name>',
'Run the command with an explicit package role',
)
.option('--check', 'Enable type checking and linting if available')
.option('--inspect [host]', 'Enable debugger in Node.js environments')
.option(
'--inspect-brk [host]',
'Enable debugger in Node.js environments, breaking before code starts',
)
.option(
'--require <path...>',
'Add a --require argument to the node process',
)
.option(
'--link <path>',
'Link an external workspace for module resolution',
)
.action(lazy(() => import('./commands/package/start'), 'command'));
await defaultCommand.parseAsync(args, { from: 'user' });
},
});
reg.addCommand({
path: ['repo', 'start'],
description: 'Starts packages in the repo for local development',
execute: async () => {
throw new Error('Not implemented');
},
});
},
});
export default buildPlugin;
@@ -0,0 +1,90 @@
/*
* Copyright 2020 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 fs from 'fs-extra';
import { OptionValues } from 'commander';
import { resolve as resolvePath } from 'node:path';
import { PackageRole } from '@backstage/cli-node';
import { findRoleFromCommand } from '../../../../../lib/role';
import { startBackend, startBackendPlugin } from './startBackend';
import { startFrontend } from './startFrontend';
import { ForwardedError } from '@backstage/errors';
export async function command(opts: OptionValues): Promise<void> {
const role = await findRoleFromCommand(opts);
if (opts.link) {
const dir = resolvePath(opts.link);
if (!fs.pathExistsSync(dir)) {
throw new Error(
`Invalid workspace link, directory does not exist: ${dir}`,
);
}
const pkgJson = await fs
.readJson(resolvePath(dir, 'package.json'))
.catch(error => {
throw new ForwardedError(
'Failed to read package.json in linked workspace',
error,
);
});
if (!pkgJson.workspaces) {
throw new Error(
`Invalid workspace link, directory is not a workspace: ${dir}`,
);
}
}
const options = {
configPaths: opts.config as string[],
checksEnabled: Boolean(opts.check),
linkedWorkspace: opts.link,
inspectEnabled: opts.inspect,
inspectBrkEnabled: opts.inspectBrk,
require: opts.require,
};
switch (role) {
case 'backend':
return startBackend(options);
case 'backend-plugin':
case 'backend-plugin-module':
case 'node-library':
return startBackendPlugin(options);
case 'frontend':
return startFrontend({
...options,
entry: 'src/index',
verifyVersions: true,
});
case 'web-library':
case 'frontend-plugin':
case 'frontend-plugin-module':
return startFrontend({ entry: 'dev/index', ...options });
case 'frontend-dynamic-container' as PackageRole: // experimental
return startFrontend({
entry: 'src/index',
...options,
skipOpenBrowser: true,
isModuleFederationRemote: true,
});
default:
throw new Error(
`Start command is not supported for package role '${role}'`,
);
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2022 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 { command } from './command';
@@ -0,0 +1,61 @@
/*
* Copyright 2020 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 fs from 'fs-extra';
import { paths } from '../../../../../lib/paths';
import { runBackend } from '../../../../../lib/runner';
interface StartBackendOptions {
checksEnabled: boolean;
inspectEnabled: boolean;
inspectBrkEnabled: boolean;
linkedWorkspace?: string;
require?: string;
}
export async function startBackend(options: StartBackendOptions) {
const waitForExit = await runBackend({
entry: 'src/index',
inspectEnabled: options.inspectEnabled,
inspectBrkEnabled: options.inspectBrkEnabled,
linkedWorkspace: options.linkedWorkspace,
require: options.require,
});
await waitForExit();
}
export async function startBackendPlugin(options: StartBackendOptions) {
const hasDevIndexEntry = await fs.pathExists(
paths.resolveTarget('dev', 'index.ts'),
);
if (!hasDevIndexEntry) {
console.warn(
`The 'dev' directory is missing. Please create a proper dev/index.ts in order to start the plugin.`,
);
return;
}
const waitForExit = await runBackend({
entry: 'dev/index',
inspectEnabled: options.inspectEnabled,
inspectBrkEnabled: options.inspectBrkEnabled,
require: options.require,
linkedWorkspace: options.linkedWorkspace,
});
await waitForExit();
}
@@ -0,0 +1,57 @@
/*
* Copyright 2020 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 { readJson } from 'fs-extra';
import { resolve as resolvePath } from 'path';
import {
getModuleFederationOptions,
serveBundle,
} from '../../../../build/lib/bundler';
import { paths } from '../../../../../lib/paths';
import { BackstagePackageJson } from '@backstage/cli-node';
interface StartAppOptions {
verifyVersions?: boolean;
entry: string;
checksEnabled: boolean;
configPaths: string[];
skipOpenBrowser?: boolean;
isModuleFederationRemote?: boolean;
linkedWorkspace?: string;
}
export async function startFrontend(options: StartAppOptions) {
const packageJson = (await readJson(
paths.resolveTarget('package.json'),
)) as BackstagePackageJson;
const waitForExit = await serveBundle({
entry: options.entry,
checksEnabled: options.checksEnabled,
configPaths: options.configPaths,
verifyVersions: options.verifyVersions,
skipOpenBrowser: options.skipOpenBrowser,
linkedWorkspace: options.linkedWorkspace,
moduleFederation: await getModuleFederationOptions(
packageJson,
resolvePath(paths.targetDir),
options.isModuleFederationRemote,
),
});
await waitForExit();
}
+54
View File
@@ -0,0 +1,54 @@
/*
* 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 { Command } from 'commander';
import { lazy } from '../../lib/lazy';
import { configOption } from '../config';
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',
),
);
}
export function registerPackageCommands(command: Command) {
command
.command('start')
.description('Start a package for local development')
.option(...configOption)
.option('--role <name>', 'Run the command with an explicit package role')
.option('--check', 'Enable type checking and linting if available')
.option('--inspect [host]', 'Enable debugger in Node.js environments')
.option(
'--inspect-brk [host]',
'Enable debugger in Node.js environments, breaking before code starts',
)
.option(
'--require <path...>',
'Add a --require argument to the node process',
)
.option('--link <path>', 'Link an external workspace for module resolution')
.action(lazy(() => import('./commands/package/start'), 'command'));
}