Internalize CLI lib utilities into consuming modules

Move typeDistProject.ts into the build module, duplicate optionsParser.ts
into build and lint modules, inline configOption in the build module,
and simplify the deprecated migrate package-exports command to remove
cross-module dependencies.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-25 11:47:27 +01:00
parent c7d2bf99ee
commit 2fcba39dea
14 changed files with 89 additions and 21 deletions
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies.
@@ -22,7 +22,7 @@ import { targetPaths } from '@backstage/cli-common';
import fs from 'fs-extra';
import { publishPreflightCheck } from '../../lib/publishing';
import { createTypeDistProject } from '../../../../lib/typeDistProject';
import { createTypeDistProject } from '../../../build/lib/typeDistProject';
export const pre = async () => {
publishPreflightCheck({
@@ -28,7 +28,7 @@ import {
} from '@backstage/cli-node';
import { buildFrontend } from '../../lib/buildFrontend';
import { buildBackend } from '../../lib/buildBackend';
import { createScriptOptionsParser } from '../../../../lib/optionsParser';
import { createScriptOptionsParser } from '../../lib/optionsParser';
export async function command(opts: OptionValues, cmd: Command): Promise<void> {
let packages = await PackageGraph.listTargetPackages();
+7 -1
View File
@@ -17,7 +17,13 @@
import { Command, Option } from 'commander';
import { createCliPlugin } from '../../wiring/factory';
import { lazy } from '../../wiring/lazy';
import { configOption } from '../config';
const configOption = [
'--config <path>',
'Config files to load instead of app-config.yaml',
(opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]),
Array<string>(),
] as const;
export function registerPackageCommands(command: Command) {
command
@@ -20,7 +20,7 @@ import { readEntryPoints } from '../entryPoints';
import {
createTypeDistProject,
getEntryPointDefaultFeatureType,
} from '../../../../lib/typeDistProject';
} from '../typeDistProject';
import {
BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL,
defaultRemoteSharedDependencies,
@@ -44,7 +44,7 @@ import {
PackageGraphNode,
runConcurrentTasks,
} from '@backstage/cli-node';
import { createTypeDistProject } from '../../../../lib/typeDistProject';
import { createTypeDistProject } from '../typeDistProject';
// These packages aren't safe to pack in parallel since the CLI depends on them
const UNSAFE_PACKAGES = [
@@ -19,7 +19,7 @@ import npmPackList from 'npm-packlist';
import { resolve as resolvePath, posix as posixPath } from 'node:path';
import { BackstagePackageJson } from '@backstage/cli-node';
import { readEntryPoints } from '../entryPoints';
import { getEntryPointDefaultFeatureType } from '../../../../lib/typeDistProject';
import { getEntryPointDefaultFeatureType } from '../typeDistProject';
import { Project } from 'ts-morph';
const PKG_PATH = 'package.json';
@@ -27,7 +27,7 @@ import {
} from '@backstage/cli-node';
import { targetPaths } from '@backstage/cli-common';
import { createScriptOptionsParser } from '../../../../lib/optionsParser';
import { createScriptOptionsParser } from '../../lib/optionsParser';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
function depCount(pkg: BackstagePackageJson) {
@@ -0,0 +1,70 @@
/*
* 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';
export function createScriptOptionsParser(
anyCmd: Command,
commandPath: string[],
) {
// Regardless of what command instance is passed in we want to find
// the root command and resolve the path from there
let rootCmd = anyCmd;
while (rootCmd.parent) {
rootCmd = rootCmd.parent;
}
// Now find the command that was requested
let targetCmd = rootCmd as Command | undefined;
for (const name of commandPath) {
targetCmd = targetCmd?.commands.find(c => c.name() === name) as
| Command
| undefined;
}
if (!targetCmd) {
throw new Error(
`Could not find package command '${commandPath.join(' ')}'`,
);
}
const cmd = targetCmd;
const expectedScript = `backstage-cli ${commandPath.join(' ')}`;
return (scriptStr?: string) => {
if (!scriptStr || !scriptStr.startsWith(expectedScript)) {
return undefined;
}
const argsStr = scriptStr.slice(expectedScript.length).trim();
// Can't clone or copy or even use commands as prototype, so we mutate
// the necessary members instead, and then reset them once we're done
const currentOpts = (cmd as any)._optionValues;
const currentStore = (cmd as any)._storeOptionsAsProperties;
const result: Record<string, any> = {};
(cmd as any)._storeOptionsAsProperties = false;
(cmd as any)._optionValues = result;
// Triggers the writing of options to the result object
cmd.parseOptions(argsStr.split(' '));
(cmd as any)._storeOptionsAsProperties = currentOpts;
(cmd as any)._optionValues = currentStore;
return result;
};
}
@@ -14,21 +14,8 @@
* limitations under the License.
*/
import {
fixPackageExports,
readFixablePackages,
writeFixedPackages,
} from '../../maintenance/commands/repo/fix';
export async function command() {
console.log(
'The `migrate package-exports` command is deprecated, use `repo fix` instead.',
'The `migrate package-exports` command has been removed, use `repo fix` instead.',
);
const packages = await readFixablePackages();
for (const pkg of packages) {
fixPackageExports(pkg);
}
await writeFixedPackages(packages);
}