From 7b77d0676654a7dcc269b840bc5d92d1ebe5fee8 Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Thu, 19 Mar 2026 11:54:31 +0100 Subject: [PATCH 1/2] Generalize --force deprecation warning into BaseCommand Add a `deprecated()` helper to BaseCommand that attaches a deprecation warning to any flag definition, and a private `showDeprecatedFlagWarnings()` method that fires the warning automatically during `parse()` when the flag is used. Remove the manual `if (flags.force) { renderWarning(...) }` blocks from deploy.ts and release.ts in favour of wrapping the flag declarations with `deprecated()`. Co-Authored-By: Claude Sonnet 4.6 --- packages/app/src/cli/commands/app/deploy.ts | 42 +++++++++---------- packages/app/src/cli/commands/app/release.ts | 42 +++++++++---------- .../cli-kit/src/public/node/base-command.ts | 17 +++++++- 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/packages/app/src/cli/commands/app/deploy.ts b/packages/app/src/cli/commands/app/deploy.ts index c7780493566..648bd4dd886 100644 --- a/packages/app/src/cli/commands/app/deploy.ts +++ b/packages/app/src/cli/commands/app/deploy.ts @@ -4,11 +4,12 @@ import {validateVersion} from '../../validations/version-name.js' import {validateMessage} from '../../validations/message.js' import metadata from '../../metadata.js' import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js' +import {deprecated} from '@shopify/cli-kit/node/base-command' import {linkedAppContext} from '../../services/app-context.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' -import {renderWarning} from '@shopify/cli-kit/node/ui' + export default class Deploy extends AppLinkedCommand { static summary = 'Deploy your Shopify app.' @@ -25,13 +26,25 @@ export default class Deploy extends AppLinkedCommand { static flags = { ...globalFlags, ...appFlags, - force: Flags.boolean({ - hidden: false, - description: - '[Deprecated] Deploy without asking for confirmation. Equivalent to --allow-updates --allow-deletes. Use --allow-updates for CI/CD environments instead.', - env: 'SHOPIFY_FLAG_FORCE', - char: 'f', - }), + force: deprecated( + Flags.boolean({ + hidden: false, + description: + '[Deprecated] Deploy without asking for confirmation. Equivalent to --allow-updates --allow-deletes. Use --allow-updates for CI/CD environments instead.', + env: 'SHOPIFY_FLAG_FORCE', + char: 'f', + }), + { + headline: ['The', {command: '--force'}, 'flag is deprecated and will be removed in the next major release.'], + body: [ + 'Use', + {command: '--allow-updates'}, + 'for CI/CD environments, or', + {command: '--allow-updates --allow-deletes'}, + 'if you also want to allow removals.', + ], + }, + ), 'allow-updates': Flags.boolean({ hidden: false, description: @@ -80,19 +93,6 @@ export default class Deploy extends AppLinkedCommand { async run(): Promise { const {flags} = await this.parse(Deploy) - if (flags.force) { - renderWarning({ - headline: ['The', {command: '--force'}, 'flag is deprecated and will be removed in the next major release.'], - body: [ - 'Use', - {command: '--allow-updates'}, - 'for CI/CD environments, or', - {command: '--allow-updates --allow-deletes'}, - 'if you also want to allow removals.', - ], - }) - } - await metadata.addPublicMetadata(() => ({ cmd_deploy_flag_message_used: Boolean(flags.message), cmd_deploy_flag_version_used: Boolean(flags.version), diff --git a/packages/app/src/cli/commands/app/release.ts b/packages/app/src/cli/commands/app/release.ts index c2da906e0f1..fa5ab49f9ca 100644 --- a/packages/app/src/cli/commands/app/release.ts +++ b/packages/app/src/cli/commands/app/release.ts @@ -1,11 +1,12 @@ import {appFlags} from '../../flags.js' import {release} from '../../services/release.js' import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js' +import {deprecated} from '@shopify/cli-kit/node/base-command' import {linkedAppContext} from '../../services/app-context.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' -import {renderWarning} from '@shopify/cli-kit/node/ui' + export default class Release extends AppLinkedCommand { static summary = 'Release an app version.' @@ -19,13 +20,25 @@ export default class Release extends AppLinkedCommand { static flags = { ...globalFlags, ...appFlags, - force: Flags.boolean({ - hidden: false, - description: - '[Deprecated] Release without asking for confirmation. Equivalent to --allow-updates --allow-deletes. Use --allow-updates for CI/CD environments instead.', - env: 'SHOPIFY_FLAG_FORCE', - char: 'f', - }), + force: deprecated( + Flags.boolean({ + hidden: false, + description: + '[Deprecated] Release without asking for confirmation. Equivalent to --allow-updates --allow-deletes. Use --allow-updates for CI/CD environments instead.', + env: 'SHOPIFY_FLAG_FORCE', + char: 'f', + }), + { + headline: ['The', {command: '--force'}, 'flag is deprecated and will be removed in the next major release.'], + body: [ + 'Use', + {command: '--allow-updates'}, + 'for CI/CD environments, or', + {command: '--allow-updates --allow-deletes'}, + 'if you also want to allow removals.', + ], + }, + ), 'allow-updates': Flags.boolean({ hidden: false, description: @@ -50,19 +63,6 @@ export default class Release extends AppLinkedCommand { const {flags} = await this.parse(Release) const clientId = flags['client-id'] - if (flags.force) { - renderWarning({ - headline: ['The', {command: '--force'}, 'flag is deprecated and will be removed in the next major release.'], - body: [ - 'Use', - {command: '--allow-updates'}, - 'for CI/CD environments, or', - {command: '--allow-updates --allow-deletes'}, - 'if you also want to allow removals.', - ], - }) - } - await addPublicMetadata(() => ({ cmd_app_reset_used: flags.reset, })) diff --git a/packages/cli-kit/src/public/node/base-command.ts b/packages/cli-kit/src/public/node/base-command.ts index e5b5f8398c8..8c761ac299b 100644 --- a/packages/cli-kit/src/public/node/base-command.ts +++ b/packages/cli-kit/src/public/node/base-command.ts @@ -3,7 +3,7 @@ import {loadEnvironment, environmentFilePath} from './environments.js' import {isDevelopment} from './context/local.js' import {addPublicMetadata} from './metadata.js' import {AbortError} from './error.js' -import {renderInfo, renderWarning} from './ui.js' +import {renderInfo, renderWarning, RenderAlertOptions} from './ui.js' import {outputContent, outputResult, outputToken} from './output.js' import {terminalSupportsPrompting} from './system.js' import {hashString} from './crypto.js' @@ -26,6 +26,10 @@ interface EnvironmentFlags { path?: string } +export function deprecated(flag: T, warning: RenderAlertOptions): T { + return Object.assign(flag as object, {deprecationWarning: warning}) as T +} + abstract class BaseCommand extends Command { static baseFlags: FlagInput<{}> = {} @@ -82,6 +86,16 @@ abstract class BaseCommand extends Command { } } + private showDeprecatedFlagWarnings(parsedFlags: FlagOutput): void { + const commandVariables = this.constructor as unknown as {flags: JsonMap} + for (const [name, def] of Object.entries(commandVariables.flags ?? {})) { + const warning = (def as {deprecationWarning?: RenderAlertOptions}).deprecationWarning + if (warning && parsedFlags[name]) { + renderWarning(warning) + } + } + } + protected exitWithTimestampWhenEnvVariablePresent() { if (isTruthy(process.env.SHOPIFY_CLI_ENV_STARTUP_PERFORMANCE_RUN)) { outputResult(` @@ -104,6 +118,7 @@ abstract class BaseCommand extends Command { let result = await super.parse(options, argv) result = await this.resultWithEnvironment(result, options, argv) await addFromParsedFlags(result.flags) + this.showDeprecatedFlagWarnings(result.flags) return {...result, ...{argv: result.argv as string[]}} } From 64fd4d9f6cf80e21d2f8342bda7109912f314b04 Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Fri, 20 Mar 2026 09:45:55 +0100 Subject: [PATCH 2/2] Fix member-ordering lint error in BaseCommand Move showDeprecatedFlagWarnings to after all protected methods, alongside the other private methods. Co-Authored-By: Claude Sonnet 4.6 --- .../cli-kit/src/public/node/base-command.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cli-kit/src/public/node/base-command.ts b/packages/cli-kit/src/public/node/base-command.ts index 8c761ac299b..9da70470bec 100644 --- a/packages/cli-kit/src/public/node/base-command.ts +++ b/packages/cli-kit/src/public/node/base-command.ts @@ -86,16 +86,6 @@ abstract class BaseCommand extends Command { } } - private showDeprecatedFlagWarnings(parsedFlags: FlagOutput): void { - const commandVariables = this.constructor as unknown as {flags: JsonMap} - for (const [name, def] of Object.entries(commandVariables.flags ?? {})) { - const warning = (def as {deprecationWarning?: RenderAlertOptions}).deprecationWarning - if (warning && parsedFlags[name]) { - renderWarning(warning) - } - } - } - protected exitWithTimestampWhenEnvVariablePresent() { if (isTruthy(process.env.SHOPIFY_CLI_ENV_STARTUP_PERFORMANCE_RUN)) { outputResult(` @@ -144,6 +134,16 @@ This flag is required in non-interactive terminal environments, such as a CI env }) } + private showDeprecatedFlagWarnings(parsedFlags: FlagOutput): void { + const commandVariables = this.constructor as unknown as {flags: JsonMap} + for (const [name, def] of Object.entries(commandVariables.flags ?? {})) { + const warning = (def as {deprecationWarning?: RenderAlertOptions}).deprecationWarning + if (warning && parsedFlags[name]) { + renderWarning(warning) + } + } + } + private async resultWithEnvironment< TFlags extends FlagOutput & {path?: string; verbose?: boolean}, TGlobalFlags extends FlagOutput,