From 2fcba39deaf8050fb256fdc9fc0e033df4e10bba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 11:47:27 +0100 Subject: [PATCH 1/4] 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 --- .changeset/cli-internalize-lib-modules.md | 5 ++ .../modules/build/commands/package/pack.ts | 2 +- .../src/modules/build/commands/repo/build.ts | 2 +- packages/cli/src/modules/build/index.ts | 8 ++- .../__testUtils__/createFeatureEnvironment.ts | 0 .../build/lib/bundler/moduleFederation.ts | 2 +- .../{ => modules/build}/lib/optionsParser.ts | 0 .../build/lib/packager/createDistWorkspace.ts | 2 +- .../build/lib/packager/productionPack.ts | 2 +- .../build}/lib/typeDistProject.test.ts | 0 .../build}/lib/typeDistProject.ts | 0 .../src/modules/lint/commands/repo/lint.ts | 2 +- .../cli/src/modules/lint/lib/optionsParser.ts | 70 +++++++++++++++++++ .../migrate/commands/packageExports.ts | 15 +--- 14 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 .changeset/cli-internalize-lib-modules.md rename packages/cli/src/{ => modules/build}/lib/__testUtils__/createFeatureEnvironment.ts (100%) rename packages/cli/src/{ => modules/build}/lib/optionsParser.ts (100%) rename packages/cli/src/{ => modules/build}/lib/typeDistProject.test.ts (100%) rename packages/cli/src/{ => modules/build}/lib/typeDistProject.ts (100%) create mode 100644 packages/cli/src/modules/lint/lib/optionsParser.ts diff --git a/.changeset/cli-internalize-lib-modules.md b/.changeset/cli-internalize-lib-modules.md new file mode 100644 index 0000000000..f9ef219a3a --- /dev/null +++ b/.changeset/cli-internalize-lib-modules.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies. diff --git a/packages/cli/src/modules/build/commands/package/pack.ts b/packages/cli/src/modules/build/commands/package/pack.ts index 737b4b6931..67ef7bca8b 100644 --- a/packages/cli/src/modules/build/commands/package/pack.ts +++ b/packages/cli/src/modules/build/commands/package/pack.ts @@ -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({ diff --git a/packages/cli/src/modules/build/commands/repo/build.ts b/packages/cli/src/modules/build/commands/repo/build.ts index dd65e926b2..59d0a839e4 100644 --- a/packages/cli/src/modules/build/commands/repo/build.ts +++ b/packages/cli/src/modules/build/commands/repo/build.ts @@ -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 { let packages = await PackageGraph.listTargetPackages(); diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index 277993d252..30a9b3a19e 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -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 ', + 'Config files to load instead of app-config.yaml', + (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), + Array(), +] as const; export function registerPackageCommands(command: Command) { command diff --git a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts b/packages/cli/src/modules/build/lib/__testUtils__/createFeatureEnvironment.ts similarity index 100% rename from packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts rename to packages/cli/src/modules/build/lib/__testUtils__/createFeatureEnvironment.ts diff --git a/packages/cli/src/modules/build/lib/bundler/moduleFederation.ts b/packages/cli/src/modules/build/lib/bundler/moduleFederation.ts index 307cb5cebc..69e1415b5d 100644 --- a/packages/cli/src/modules/build/lib/bundler/moduleFederation.ts +++ b/packages/cli/src/modules/build/lib/bundler/moduleFederation.ts @@ -20,7 +20,7 @@ import { readEntryPoints } from '../entryPoints'; import { createTypeDistProject, getEntryPointDefaultFeatureType, -} from '../../../../lib/typeDistProject'; +} from '../typeDistProject'; import { BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, defaultRemoteSharedDependencies, diff --git a/packages/cli/src/lib/optionsParser.ts b/packages/cli/src/modules/build/lib/optionsParser.ts similarity index 100% rename from packages/cli/src/lib/optionsParser.ts rename to packages/cli/src/modules/build/lib/optionsParser.ts diff --git a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts index 50fb0f4e31..9f63525186 100644 --- a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts @@ -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 = [ diff --git a/packages/cli/src/modules/build/lib/packager/productionPack.ts b/packages/cli/src/modules/build/lib/packager/productionPack.ts index ce9f15388c..55eb6edc48 100644 --- a/packages/cli/src/modules/build/lib/packager/productionPack.ts +++ b/packages/cli/src/modules/build/lib/packager/productionPack.ts @@ -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'; diff --git a/packages/cli/src/lib/typeDistProject.test.ts b/packages/cli/src/modules/build/lib/typeDistProject.test.ts similarity index 100% rename from packages/cli/src/lib/typeDistProject.test.ts rename to packages/cli/src/modules/build/lib/typeDistProject.test.ts diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/modules/build/lib/typeDistProject.ts similarity index 100% rename from packages/cli/src/lib/typeDistProject.ts rename to packages/cli/src/modules/build/lib/typeDistProject.ts diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index dafcff45c6..d0b4f6f37e 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -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) { diff --git a/packages/cli/src/modules/lint/lib/optionsParser.ts b/packages/cli/src/modules/lint/lib/optionsParser.ts new file mode 100644 index 0000000000..45c7eac0aa --- /dev/null +++ b/packages/cli/src/modules/lint/lib/optionsParser.ts @@ -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 = {}; + (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; + }; +} diff --git a/packages/cli/src/modules/migrate/commands/packageExports.ts b/packages/cli/src/modules/migrate/commands/packageExports.ts index 4eb741ce4d..0d02782509 100644 --- a/packages/cli/src/modules/migrate/commands/packageExports.ts +++ b/packages/cli/src/modules/migrate/commands/packageExports.ts @@ -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); } From b36a60dc45660ebacf0241450f0de808d83d3141 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 14:08:47 +0100 Subject: [PATCH 2/4] Address review feedback Add separate breaking changeset for the removal of `migrate package-exports`, and fix a pre-existing bug where the restore assignments in `createScriptOptionsParser` were swapped. Signed-off-by: Patrik Oldsberg --- .changeset/cli-remove-migrate-package-exports.md | 5 +++++ packages/cli/src/modules/build/lib/optionsParser.ts | 4 ++-- packages/cli/src/modules/lint/lib/optionsParser.ts | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/cli-remove-migrate-package-exports.md diff --git a/.changeset/cli-remove-migrate-package-exports.md b/.changeset/cli-remove-migrate-package-exports.md new file mode 100644 index 0000000000..03ad1974d8 --- /dev/null +++ b/.changeset/cli-remove-migrate-package-exports.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: The `migrate package-exports` command has been removed. Use `repo fix` instead. diff --git a/packages/cli/src/modules/build/lib/optionsParser.ts b/packages/cli/src/modules/build/lib/optionsParser.ts index 45c7eac0aa..15561a2e42 100644 --- a/packages/cli/src/modules/build/lib/optionsParser.ts +++ b/packages/cli/src/modules/build/lib/optionsParser.ts @@ -62,8 +62,8 @@ export function createScriptOptionsParser( // Triggers the writing of options to the result object cmd.parseOptions(argsStr.split(' ')); - (cmd as any)._storeOptionsAsProperties = currentOpts; - (cmd as any)._optionValues = currentStore; + (cmd as any)._optionValues = currentOpts; + (cmd as any)._storeOptionsAsProperties = currentStore; return result; }; diff --git a/packages/cli/src/modules/lint/lib/optionsParser.ts b/packages/cli/src/modules/lint/lib/optionsParser.ts index 45c7eac0aa..15561a2e42 100644 --- a/packages/cli/src/modules/lint/lib/optionsParser.ts +++ b/packages/cli/src/modules/lint/lib/optionsParser.ts @@ -62,8 +62,8 @@ export function createScriptOptionsParser( // Triggers the writing of options to the result object cmd.parseOptions(argsStr.split(' ')); - (cmd as any)._storeOptionsAsProperties = currentOpts; - (cmd as any)._optionValues = currentStore; + (cmd as any)._optionValues = currentOpts; + (cmd as any)._storeOptionsAsProperties = currentStore; return result; }; From bd2c923d8e379d5e87f637609559d9ad137eb521 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 17:11:07 +0100 Subject: [PATCH 3/4] Throw error for removed migrate package-exports command Signed-off-by: Patrik Oldsberg --- packages/cli/src/modules/migrate/commands/packageExports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/modules/migrate/commands/packageExports.ts b/packages/cli/src/modules/migrate/commands/packageExports.ts index 0d02782509..ea42638e58 100644 --- a/packages/cli/src/modules/migrate/commands/packageExports.ts +++ b/packages/cli/src/modules/migrate/commands/packageExports.ts @@ -15,7 +15,7 @@ */ export async function command() { - console.log( + throw new Error( 'The `migrate package-exports` command has been removed, use `repo fix` instead.', ); } From 4868ff07b6ddf644bfafe59fb6234c004f8fcf76 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 17:31:30 +0100 Subject: [PATCH 4/4] Fix typeDistProject import path in pack.ts The file was moved from maintenance to build on master, so the cross-module import is no longer needed. Signed-off-by: Patrik Oldsberg --- packages/cli/src/modules/build/commands/package/pack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/modules/build/commands/package/pack.ts b/packages/cli/src/modules/build/commands/package/pack.ts index 67ef7bca8b..0265c07093 100644 --- a/packages/cli/src/modules/build/commands/package/pack.ts +++ b/packages/cli/src/modules/build/commands/package/pack.ts @@ -22,7 +22,7 @@ import { targetPaths } from '@backstage/cli-common'; import fs from 'fs-extra'; import { publishPreflightCheck } from '../../lib/publishing'; -import { createTypeDistProject } from '../../../build/lib/typeDistProject'; +import { createTypeDistProject } from '../../lib/typeDistProject'; export const pre = async () => { publishPreflightCheck({