Add deprecation warnings for old camelCase flag names

Instead of a hard breaking change, the old camelCase flag names
(--baseVersion, --successCache, --successCacheDir, --alwaysPack) still
work via type-flag's built-in camelCase/kebab-case mapping, but now
print a deprecation warning pointing to the new kebab-case spelling.

Downgrades the changeset from minor (breaking) to patch.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-01 16:54:31 +01:00
parent b45ee80104
commit 629e9f5558
6 changed files with 160 additions and 119 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
---
'@backstage/cli': minor
'@backstage/cli': patch
---
**BREAKING**: Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. The following CLI flags have been renamed from camelCase to kebab-case to match standard CLI conventions:
Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. The following CLI flags have been renamed from camelCase to kebab-case to match standard CLI conventions:
- `--baseVersion``--base-version`
- `--successCache``--success-cache`
@@ -10,4 +10,4 @@
- `--alwaysPack``--always-pack`
- `--alwaysYarnPack``--always-pack` (hidden legacy alias preserved)
If you have scripts or CI configurations that use any of the above flags, update them to the new kebab-case spelling.
The old camelCase flag names still work but will print a deprecation warning. Please update any scripts or CI configurations to use the new kebab-case spelling.
@@ -0,0 +1,38 @@
/*
* Copyright 2026 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.
*/
/**
* Scans args for deprecated camelCase flag names and logs a warning for each
* match. Since type-flag accepts both camelCase and kebab-case, the old names
* still work — this just nudges users toward the new spelling.
*/
export function warnDeprecatedFlags(
args: string[],
flags: Record<string, unknown>,
) {
for (const key of Object.keys(flags)) {
const kebab = key.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
if (kebab === key) {
continue;
}
const deprecated = `--${key}`;
if (args.some(a => a === deprecated || a.startsWith(`${deprecated}=`))) {
process.stderr.write(
`DEPRECATION WARNING: ${deprecated} has been renamed to --${kebab}\n`,
);
}
}
}
@@ -17,28 +17,33 @@
import fs from 'fs-extra';
import { cli } from 'cleye';
import { createDistWorkspace } from '../lib/packager';
import { warnDeprecatedFlags } from '../../../lib/warnDeprecatedFlags';
import type { CommandContext } from '../../../wiring/types';
export default async ({ args, info }: CommandContext) => {
// Support legacy --alwaysYarnPack alias
// Support legacy --alwaysYarnPack and --alwaysPack aliases
const normalizedArgs = args.map(a =>
a === '--alwaysYarnPack' ? '--always-pack' : a,
a === '--alwaysYarnPack' || a === '--alwaysPack' ? '--always-pack' : a,
);
const flagDefs = {
alwaysPack: {
type: Boolean,
description:
'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)',
},
};
warnDeprecatedFlags(args, flagDefs);
const {
flags: { alwaysPack },
_: positionals,
} = cli(
{
help: info,
help: { ...info, usage: `${info.usage} <workspace-dir> [packages...]` },
parameters: ['<workspace-dir>', '[packages...]'],
flags: {
alwaysPack: {
type: Boolean,
description:
'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)',
},
},
flags: flagDefs,
},
undefined,
normalizedArgs,
@@ -29,6 +29,7 @@ import {
import { targetPaths } from '@backstage/cli-common';
import { createScriptOptionsParser } from '../../lib/optionsParser';
import { warnDeprecatedFlags } from '../../../../lib/warnDeprecatedFlags';
import type { CommandContext } from '../../../../wiring/types';
function depCount(pkg: BackstagePackageJson) {
@@ -40,6 +41,43 @@ function depCount(pkg: BackstagePackageJson) {
}
export default async ({ args, info }: CommandContext) => {
const flagDefs = {
fix: {
type: Boolean,
description: 'Attempt to automatically fix violations',
},
format: {
type: String,
description: 'Lint report output format',
default: 'eslint-formatter-friendly',
},
outputFile: {
type: String,
description: 'Write the lint report to a file instead of stdout',
},
successCache: {
type: Boolean,
description:
'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run',
},
successCacheDir: {
type: String,
description:
'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
},
since: {
type: String,
description: 'Only lint packages that changed since the specified ref',
},
maxWarnings: {
type: String,
description:
'Fail if more than this number of warnings. -1 allows warnings. (default: -1)',
},
};
warnDeprecatedFlags(args, flagDefs);
const {
flags: {
fix,
@@ -50,48 +88,7 @@ export default async ({ args, info }: CommandContext) => {
since,
maxWarnings,
},
} = cli(
{
help: info,
flags: {
fix: {
type: Boolean,
description: 'Attempt to automatically fix violations',
},
format: {
type: String,
description: 'Lint report output format',
default: 'eslint-formatter-friendly',
},
outputFile: {
type: String,
description: 'Write the lint report to a file instead of stdout',
},
successCache: {
type: Boolean,
description:
'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run',
},
successCacheDir: {
type: String,
description:
'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
},
since: {
type: String,
description:
'Only lint packages that changed since the specified ref',
},
maxWarnings: {
type: String,
description:
'Fail if more than this number of warnings. -1 allows warnings. (default: -1)',
},
},
},
undefined,
args,
);
} = cli({ help: info, flags: flagDefs }, undefined, args);
let packages = await PackageGraph.listTargetPackages();
+42 -45
View File
@@ -16,9 +16,50 @@
import { cli } from 'cleye';
import { createNewPackage } from '../lib/createNewPackage';
import { warnDeprecatedFlags } from '../../../lib/warnDeprecatedFlags';
import type { CommandContext } from '../../../wiring/types';
export default async ({ args, info }: CommandContext) => {
const flagDefs = {
select: {
type: String,
description: 'Select the thing you want to be creating upfront',
},
option: {
type: [String] as const,
description: 'Pre-fill options for the creation process',
default: [] as string[],
},
skipInstall: {
type: Boolean,
description: `Skips running 'yarn install' and 'yarn lint --fix'`,
},
scope: {
type: String,
description: 'The scope to use for new packages',
},
npmRegistry: {
type: String,
description: 'The package registry to use for new packages',
},
baseVersion: {
type: String,
description: 'The version to use for any new packages (default: 0.1.0)',
},
license: {
type: String,
description:
'The license to use for any new packages (default: Apache-2.0)',
},
private: {
type: Boolean,
description: 'Mark new packages as private',
default: true,
},
};
warnDeprecatedFlags(args, flagDefs);
const {
flags: {
select,
@@ -30,51 +71,7 @@ export default async ({ args, info }: CommandContext) => {
license,
private: isPrivate,
},
} = cli(
{
help: info,
flags: {
select: {
type: String,
description: 'Select the thing you want to be creating upfront',
},
option: {
type: [String],
description: 'Pre-fill options for the creation process',
default: [],
},
skipInstall: {
type: Boolean,
description: `Skips running 'yarn install' and 'yarn lint --fix'`,
},
scope: {
type: String,
description: 'The scope to use for new packages',
},
npmRegistry: {
type: String,
description: 'The package registry to use for new packages',
},
baseVersion: {
type: String,
description:
'The version to use for any new packages (default: 0.1.0)',
},
license: {
type: String,
description:
'The license to use for any new packages (default: Apache-2.0)',
},
private: {
type: Boolean,
description: 'Mark new packages as private',
default: true,
},
},
},
undefined,
args,
);
} = cli({ help: info, flags: flagDefs }, undefined, args);
const prefilledParams = parseParams(rawArgOptions);
@@ -31,6 +31,7 @@ import {
findOwnPaths,
isChildPath,
} from '@backstage/cli-common';
import { warnDeprecatedFlags } from '../../../../lib/warnDeprecatedFlags';
import type { CommandContext } from '../../../../wiring/types';
type JestProject = {
@@ -136,28 +137,31 @@ export default async ({ args, info }: CommandContext) => {
// Parse Backstage-specific flags; unknown flags and arguments are left in
// args so they can be forwarded to Jest.
const flagDefs = {
since: {
type: String,
description: 'Only include test packages changed since the specified ref',
},
successCache: {
type: Boolean,
description: 'Cache and skip tests for unchanged packages',
},
successCacheDir: {
type: String,
description: 'Directory for the success cache',
},
jestHelp: {
type: Boolean,
description: "Show Jest's own help output",
},
};
warnDeprecatedFlags(args, flagDefs);
const { flags: opts } = cli(
{
help: info,
flags: {
since: {
type: String,
description:
'Only include test packages changed since the specified ref',
},
successCache: {
type: Boolean,
description: 'Cache and skip tests for unchanged packages',
},
successCacheDir: {
type: String,
description: 'Directory for the success cache',
},
jestHelp: {
type: Boolean,
description: "Show Jest's own help output",
},
},
flags: flagDefs,
ignoreArgv: type => type === 'unknown-flag' || type === 'argument',
},
undefined,