From 69868391b424333b0f667b55368d9fbaa29e20d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Wed, 8 Jul 2026 12:18:59 +0200 Subject: [PATCH] Remove dashboard managed extension support flag Assisted-By: devx/a2560e25-a597-4af7-92ea-24c6fa943415 --- packages/app/src/cli/commands/app/deploy.ts | 9 +- .../app/src/cli/models/app/app.test-data.ts | 6 +- .../src/cli/prompts/deploy-release.test.ts | 57 +--- .../app/src/cli/prompts/deploy-release.ts | 12 +- packages/app/src/cli/services/context.test.ts | 7 +- packages/app/src/cli/services/context.ts | 15 +- .../services/context/breakdown-extensions.ts | 9 - .../cli/services/context/identifiers.test.ts | 10 +- .../src/cli/services/context/identifiers.ts | 8 +- packages/app/src/cli/services/deploy.test.ts | 262 ++---------------- packages/app/src/cli/services/deploy.ts | 76 ++--- .../services/format-config-info-body.test.ts | 56 ---- .../cli/services/format-config-info-body.ts | 3 - packages/app/src/cli/services/release.ts | 5 +- .../utilities/developer-platform-client.ts | 1 - .../app-management-client.ts | 1 - .../partners-client.ts | 1 - 17 files changed, 60 insertions(+), 478 deletions(-) diff --git a/packages/app/src/cli/commands/app/deploy.ts b/packages/app/src/cli/commands/app/deploy.ts index b5b5a437db3..dd36957d820 100644 --- a/packages/app/src/cli/commands/app/deploy.ts +++ b/packages/app/src/cli/commands/app/deploy.ts @@ -87,11 +87,9 @@ export default class Deploy extends AppLinkedCommand { cmd_app_reset_used: flags.reset, })) - const force = flags['no-release'] - // When releasing, we require --no-release or --allow-updates or --allow-deletes for non-TTY. const requiredNonTTYFlags: string[] = [] - const hasAnyForceFlags = force || flags['allow-updates'] || flags['allow-deletes'] + const hasAnyForceFlags = flags['no-release'] || flags['allow-updates'] || flags['allow-deletes'] if (!hasAnyForceFlags) { requiredNonTTYFlags.push('allow-updates') } @@ -104,8 +102,8 @@ export default class Deploy extends AppLinkedCommand { userProvidedConfigName: flags.config, }) - const allowUpdates = force || flags['allow-updates'] - const allowDeletes = force || flags['allow-deletes'] + const allowUpdates = flags['no-release'] || flags['allow-updates'] + const allowDeletes = flags['no-release'] || flags['allow-deletes'] const result = await deploy({ app, @@ -114,7 +112,6 @@ export default class Deploy extends AppLinkedCommand { organization, developerPlatformClient, reset: flags.reset, - force, allowUpdates, allowDeletes, noRelease: flags['no-release'], diff --git a/packages/app/src/cli/models/app/app.test-data.ts b/packages/app/src/cli/models/app/app.test-data.ts index 01caa634e5b..b60f0b612c4 100644 --- a/packages/app/src/cli/models/app/app.test-data.ts +++ b/packages/app/src/cli/models/app/app.test-data.ts @@ -1314,7 +1314,6 @@ export function testDeveloperPlatformClient(stubs: Partial Promise.resolve(testPartnersUserSession), unsafeRefreshToken: () => Promise.resolve(testPartnersUserSession.token), accountInfo: () => Promise.resolve(testPartnersUserSession.accountInfo), @@ -1389,10 +1388,7 @@ export function testDeveloperPlatformClient(stubs: Partial + key as keyof Omit ] = vi.fn().mockImplementation(value) } } diff --git a/packages/app/src/cli/prompts/deploy-release.test.ts b/packages/app/src/cli/prompts/deploy-release.test.ts index a0121f6c176..435461319fe 100644 --- a/packages/app/src/cli/prompts/deploy-release.test.ts +++ b/packages/app/src/cli/prompts/deploy-release.test.ts @@ -18,7 +18,7 @@ beforeEach(() => { describe('deployOrReleaseConfirmationPrompt', () => { describe('when release', () => { - test('and force no prompt should be displayed and true returned', async () => { + test('and no release no prompt should be displayed and true returned', async () => { // Given const {extensionIdentifiersBreakdown, configExtensionIdentifiersBreakdown} = buildCompleteBreakdownInfo() const renderConfirmationPromptSpyOn = vi.spyOn(ui, 'renderConfirmationPrompt') @@ -30,8 +30,7 @@ describe('deployOrReleaseConfirmationPrompt', () => { extensionIdentifiersBreakdown, configExtensionIdentifiersBreakdown, appTitle, - release: true, - force: true, + release: false, }) // Then @@ -59,7 +58,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { configExtensionIdentifiersBreakdown, appTitle, release: true, - force: false, }) // Then @@ -104,7 +102,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: true, - force: false, }) // Then @@ -159,7 +156,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: true, - force: false, }) // Then @@ -220,7 +216,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: true, - force: false, installCount: 1243, }) @@ -247,7 +242,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: true, - force: false, installCount: 0, }) @@ -272,7 +266,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { const result = await deployOrReleaseConfirmationPrompt({ ...breakdownInfo, release: true, - force: false, }) // Then @@ -325,7 +318,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { const result = await deployOrReleaseConfirmationPrompt({ ...breakdownInfo, release: true, - force: false, showConfig: false, }) @@ -373,7 +365,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: true, - force: false, }) // Then @@ -412,10 +403,9 @@ describe('deployOrReleaseConfirmationPrompt', () => { }) describe('when no release', () => { - test('and no force without extensions deleted should display the complete confirmation prompt', async () => { + test('skips the confirmation prompt', async () => { // Given const breakdownInfo = buildCompleteBreakdownInfo() - breakdownInfo.extensionIdentifiersBreakdown.onlyRemote = [] const renderConfirmationPromptSpyOn = vi.spyOn(ui, 'renderConfirmationPrompt').mockResolvedValue(true) const metadataSpyOn = vi.spyOn(metadata, 'addPublicMetadata').mockImplementation(async () => {}) @@ -426,44 +416,15 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle, release: false, - force: false, }) // Then verifyMetada({ metadataSpyOn, - extensionIdentifiersBreakdown: breakdownInfo.extensionIdentifiersBreakdown, confirmed: result, configExtensionIdentifiersBreakdown: breakdownInfo.configExtensionIdentifiersBreakdown!, }) - expect(renderConfirmationPromptSpyOn).toHaveBeenCalledWith( - renderConfirmationPromptContent({ - appTitle, - infoTable: [ - { - header: 'Configuration:', - items: [ - {bullet: '+', item: ['new field name1', {subdued: '(new)'}], color: 'green'}, - {item: ['updating field name1', {subdued: '(updated)'}], color: '#FF8800'}, - 'existing field name1', - {bullet: '-', item: ['deleted field name1', {subdued: '(removed)'}], color: 'red'}, - ], - }, - { - header: 'Extensions:', - items: [ - {bullet: '+', item: ['to create extension (uid: uid-create)', {subdued: '(new)'}], color: 'green'}, - {item: ['to update extension', {subdued: '(updated)'}], color: '#FF8800'}, - 'unchanged extension', - ['from dashboard extension', {subdued: '(from Partner Dashboard)'}], - ], - }, - ], - dangerPrompt: false, - confirmationMessage: 'Yes, create this new version', - message: 'Create a new version of app title?', - }), - ) + expect(renderConfirmationPromptSpyOn).not.toHaveBeenCalled() expect(result).toBe(true) }) }) @@ -480,7 +441,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: true, allowDeletes: true, }) @@ -505,7 +465,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: true, allowDeletes: false, }) @@ -530,7 +489,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: true, allowDeletes: false, }) @@ -555,7 +513,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: false, allowDeletes: true, }) @@ -577,7 +534,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: true, allowDeletes: false, }), @@ -596,7 +552,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, allowUpdates: false, allowDeletes: true, }), @@ -619,7 +574,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, }), ).rejects.toMatchObject({ message: 'This deployment includes changes that require confirmation.', @@ -645,7 +599,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, }), ).rejects.toMatchObject({ message: 'This deployment includes changes that require confirmation.', @@ -666,7 +619,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, }), ).rejects.toMatchObject({ message: 'This deployment includes changes that require confirmation.', @@ -687,7 +639,6 @@ describe('deployOrReleaseConfirmationPrompt', () => { ...breakdownInfo, appTitle: 'app title', release: true, - force: false, }) // Then - should show the prompt normally since there are no changes requiring confirmation diff --git a/packages/app/src/cli/prompts/deploy-release.ts b/packages/app/src/cli/prompts/deploy-release.ts index 7d779fdde7b..b94728bb1fe 100644 --- a/packages/app/src/cli/prompts/deploy-release.ts +++ b/packages/app/src/cli/prompts/deploy-release.ts @@ -18,7 +18,6 @@ interface DeployOrReleaseConfirmationPromptOptions { configExtensionIdentifiersBreakdown?: ConfigExtensionIdentifiersBreakdown appTitle?: string release: boolean - force: boolean /** If true, allow adding and updating extensions and configuration without user confirmation */ allowUpdates?: boolean /** If true, allow removing extensions and configuration without user confirmation */ @@ -46,20 +45,20 @@ interface DeployConfirmationPromptOptions { * Throws an error if in non-TTY mode and there are changes that require confirmation. */ function shouldSkipConfirmationPrompt({ - force, + release, allowUpdates, allowDeletes, extensionIdentifiersBreakdown, configExtensionIdentifiersBreakdown, }: { - force: boolean + release: boolean allowUpdates?: boolean allowDeletes?: boolean extensionIdentifiersBreakdown: ExtensionIdentifiersBreakdown configExtensionIdentifiersBreakdown?: ConfigExtensionIdentifiersBreakdown }): boolean { - // --no-release (which sets force=true internally) is equivalent to --allow-updates --allow-deletes - if (force || (allowUpdates && allowDeletes)) return true + // not releasing is equivalent to --allow-updates --allow-deletes + if (!release || (allowUpdates && allowDeletes)) return true const hasDeletedExtensions = extensionIdentifiersBreakdown.onlyRemote.length > 0 const hasDeletedConfig = (configExtensionIdentifiersBreakdown?.deletedFieldNames.length ?? 0) > 0 @@ -92,7 +91,6 @@ function shouldSkipConfirmationPrompt({ } export async function deployOrReleaseConfirmationPrompt({ - force, allowUpdates, allowDeletes, extensionIdentifiersBreakdown, @@ -104,7 +102,7 @@ export async function deployOrReleaseConfirmationPrompt({ await metadata.addPublicMetadata(() => buildConfigurationBreakdownMetadata(configExtensionIdentifiersBreakdown)) const shouldSkip = shouldSkipConfirmationPrompt({ - force, + release, allowUpdates, allowDeletes, extensionIdentifiersBreakdown, diff --git a/packages/app/src/cli/services/context.test.ts b/packages/app/src/cli/services/context.test.ts index 07a1205dcc6..dd8bf8e6488 100644 --- a/packages/app/src/cli/services/context.test.ts +++ b/packages/app/src/cli/services/context.test.ts @@ -74,7 +74,8 @@ const deployOptions = (app: AppLinkedInterface, reset = false, force = false): D remoteApp: APP2, organization: ORG1, reset, - force, + allowUpdates: force, + allowDeletes: force, noRelease: false, developerPlatformClient: buildDeveloperPlatformClient(), skipBuild: false, @@ -212,7 +213,6 @@ describe('ensureDeployContext', () => { remoteApp: APP2, organization: ORG1, reset: false, - force: false, noRelease: false, developerPlatformClient: buildDeveloperPlatformClient({}), skipBuild: false, @@ -255,7 +255,6 @@ describe('ensureDeployContext', () => { remoteApp: APP2, organization: ORG1, reset: false, - force: false, noRelease: false, developerPlatformClient: buildDeveloperPlatformClient({}), skipBuild: false, @@ -314,7 +313,6 @@ describe('ensureDeployContext', () => { remoteApp: APP2, organization: ORG1, reset: false, - force: false, noRelease: false, developerPlatformClient, skipBuild: false, @@ -360,7 +358,6 @@ describe('ensureDeployContext', () => { remoteApp: APP2, organization: ORG1, reset: false, - force: false, noRelease: false, developerPlatformClient, skipBuild: false, diff --git a/packages/app/src/cli/services/context.ts b/packages/app/src/cli/services/context.ts index 17658ea9d66..d2d81730080 100644 --- a/packages/app/src/cli/services/context.ts +++ b/packages/app/src/cli/services/context.ts @@ -118,17 +118,16 @@ interface EnsureDeployContextResult { * @returns The selected org, app and dev store */ export async function ensureDeployContext(options: DeployOptions): Promise { - const {force, noRelease, app, remoteApp, developerPlatformClient, organization} = options + const {noRelease, app, remoteApp, developerPlatformClient, organization} = options const activeAppVersion = await developerPlatformClient.activeAppVersion(remoteApp) - const includeConfigOnDeploy = await checkIncludeConfigOnDeploy({app}) + await removeIncludeConfigOnDeployField(app) renderCurrentlyUsedConfigInfo({ org: organization.businessName, appName: remoteApp.title, appDotEnv: app.dotenv?.path, configFile: basename(app.configPath), - includeConfigOnDeploy, messages: [resetHelpMessage], }) @@ -136,7 +135,6 @@ export async function ensureDeployContext(options: DeployOptions): Promise { - await removeIncludeConfigOnDeployField(app) - return undefined -} - async function removeIncludeConfigOnDeployField(localApp: AppInterface) { const includeConfigOnDeploy = localApp.configuration.build?.include_config_on_deploy if (includeConfigOnDeploy === undefined) return @@ -273,7 +266,6 @@ interface CurrentlyUsedConfigInfoOptions { updateURLs?: string configFile?: string appDotEnv?: string - includeConfigOnDeploy?: boolean messages?: Token[][] } @@ -284,7 +276,6 @@ export function renderCurrentlyUsedConfigInfo({ updateURLs, configFile, appDotEnv, - includeConfigOnDeploy, messages, }: CurrentlyUsedConfigInfoOptions): void { const devStores = [] @@ -294,7 +285,7 @@ export function renderCurrentlyUsedConfigInfo({ renderInfo({ headline: configFile ? `Using ${fileName} for default values:` : 'Using these settings:', - body: formatConfigInfoBody({appName, org, devStores, updateURLs, includeConfigOnDeploy, messages}), + body: formatConfigInfoBody({appName, org, devStores, updateURLs, messages}), }) } diff --git a/packages/app/src/cli/services/context/breakdown-extensions.ts b/packages/app/src/cli/services/context/breakdown-extensions.ts index e8400fa2fb3..590a62944cf 100644 --- a/packages/app/src/cli/services/context/breakdown-extensions.ts +++ b/packages/app/src/cli/services/context/breakdown-extensions.ts @@ -15,7 +15,6 @@ import {ExtensionSpecification, isAppConfigSpecification} from '../../models/ext import {rewriteConfiguration} from '../app/write-app-configuration-file.js' import {AppConfigurationUsedByCli} from '../../models/extensions/specifications/types/app_config.js' import {removeTrailingSlash} from '../../models/extensions/specifications/validation/common.js' -import {throwUidMappingError} from '../../prompts/uid-mapping-error.js' import {deepCompare, deepDifference} from '@shopify/cli-kit/common/object' import {zod} from '@shopify/cli-kit/node/schema' @@ -58,14 +57,6 @@ export async function extensionsIdentifiersDeployBreakdown(options: EnsureDeploy }> { let remoteExtensionsRegistrations = await fetchRemoteExtensionsRegistrations(options) - if (options.force && !options.developerPlatformClient.supportsDashboardManagedExtensions) { - const unMigratedextensions = remoteExtensionsRegistrations.app.extensionRegistrations.filter((ext) => !ext.id) - - if (unMigratedextensions.length > 0) { - throwUidMappingError() - } - } - const extensionsToConfirm = await ensureExtensionsIds(options, remoteExtensionsRegistrations.app) if (extensionsToConfirm.didMigrateDashboardExtensions) { diff --git a/packages/app/src/cli/services/context/identifiers.test.ts b/packages/app/src/cli/services/context/identifiers.test.ts index b24fb50ccdf..ae5dab7e9e9 100644 --- a/packages/app/src/cli/services/context/identifiers.test.ts +++ b/packages/app/src/cli/services/context/identifiers.test.ts @@ -33,7 +33,6 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp: testOrganizationApp(), envIdentifiers: {}, - force: false, release: true, } await expect(ensureDeploymentIdsPresence(params)).rejects.toThrow(AbortSilentError) @@ -59,7 +58,6 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp, envIdentifiers: {}, - force: false, release: true, } @@ -74,7 +72,7 @@ describe('ensureDeploymentIdsPresence', () => { }) }) - test('when force is true, appInstallCount is not called even with remote-only extensions', async () => { + test('when release is false, appInstallCount is not called even with remote-only extensions', async () => { // Given const breakdown = buildExtensionsBreakdown() breakdown.extensionIdentifiersBreakdown.onlyRemote = [buildExtensionBreakdownInfo('removed', 'uuid-1')] @@ -98,8 +96,7 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp: testOrganizationApp(), envIdentifiers: {}, - force: true, - release: true, + release: false, } // When @@ -133,7 +130,6 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp: testOrganizationApp(), envIdentifiers: {}, - force: false, allowUpdates: true, allowDeletes: true, release: true, @@ -170,7 +166,6 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp: testOrganizationApp(), envIdentifiers: {}, - force: false, release: true, } @@ -206,7 +201,6 @@ describe('ensureDeploymentIdsPresence', () => { appName: 'appName', remoteApp: testOrganizationApp(), envIdentifiers: {}, - force: false, release: true, } const result = await ensureDeploymentIdsPresence(params) diff --git a/packages/app/src/cli/services/context/identifiers.ts b/packages/app/src/cli/services/context/identifiers.ts index d3f465823d4..1f2b7eeefcf 100644 --- a/packages/app/src/cli/services/context/identifiers.ts +++ b/packages/app/src/cli/services/context/identifiers.ts @@ -15,7 +15,6 @@ export interface EnsureDeploymentIdsPresenceOptions { appId: string appName: string envIdentifiers: Partial - force: boolean /** If true, allow adding and updating extensions and configuration without user confirmation */ allowUpdates?: boolean /** If true, allow removing extensions and configuration without user confirmation */ @@ -57,10 +56,8 @@ export async function ensureDeploymentIdsPresence(options: EnsureDeploymentIdsPr activeAppVersion: options.activeAppVersion, }) - const shouldFetchInstallCount = - extensionIdentifiersBreakdown.onlyRemote.length > 0 && - !options.force && - !(options.allowUpdates && options.allowDeletes) + const shouldWarn = options.release && !options.allowDeletes + const shouldFetchInstallCount = extensionIdentifiersBreakdown.onlyRemote.length > 0 && shouldWarn let installCount: number | undefined if (shouldFetchInstallCount) { @@ -81,7 +78,6 @@ export async function ensureDeploymentIdsPresence(options: EnsureDeploymentIdsPr configExtensionIdentifiersBreakdown, appTitle: options.remoteApp?.title, release: options.release, - force: options.force, allowUpdates: options.allowUpdates, allowDeletes: options.allowDeletes, installCount, diff --git a/packages/app/src/cli/services/deploy.test.ts b/packages/app/src/cli/services/deploy.test.ts index 4807b4ebdd2..0af16d422c3 100644 --- a/packages/app/src/cli/services/deploy.test.ts +++ b/packages/app/src/cli/services/deploy.test.ts @@ -690,7 +690,6 @@ interface TestDeployBundleInput { app: AppLinkedInterface remoteApp: OrganizationApp options?: { - force?: boolean noRelease?: boolean message?: string version?: string @@ -745,7 +744,6 @@ async function testDeployBundle({ remoteApp, organization: testOrganization(), reset: false, - force: Boolean(options?.force), noRelease: Boolean(options?.noRelease), message: options?.message, version: options?.version, @@ -756,33 +754,6 @@ async function testDeployBundle({ } describe('ImportExtensionsIfNeeded', () => { - test('skips extension import when force flag is true', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient() - - vi.mocked(getExtensions).mockResolvedValue([]) - - // When - const result = await importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: true, - }) - - // Then - expect(result).toBe(app) - expect(getExtensions).toHaveBeenCalledWith({ - developerPlatformClient, - apiKey: remoteApp.apiKey, - organizationId: remoteApp.organizationId, - extensionTypes: allExtensionTypes, - onlyDashboardManaged: true, - }) - }) - test('skips extension import when not in TTY environment', async () => { // Given const app = testAppLinked() @@ -797,7 +768,6 @@ describe('ImportExtensionsIfNeeded', () => { app, remoteApp, developerPlatformClient, - force: false, }) // Then @@ -811,7 +781,7 @@ describe('ImportExtensionsIfNeeded', () => { }) }) - test('prompts for extension import when in TTY environment and force is false', async () => { + test('prompts for extension import when in TTY environment', async () => { // Given const app = testAppLinked() const remoteApp = testOrganizationApp() @@ -830,7 +800,6 @@ describe('ImportExtensionsIfNeeded', () => { app, remoteApp, developerPlatformClient, - force: false, }) // Then @@ -843,14 +812,14 @@ describe('ImportExtensionsIfNeeded', () => { }) expect(renderConfirmationPrompt).toHaveBeenCalledWith({ message: [ - 'App includes legacy extensions that will be deprecated soon:\n', + "App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:\n", ' - Extension 1\n - Extension 2', '\n\nRun ', {command: 'shopify app import-extensions'}, - 'to add legacy extensions now?', + ' to add legacy extensions now?', ], confirmationMessage: 'Yes, add legacy extensions and deploy', - cancellationMessage: 'No, skip for now', + cancellationMessage: `No, don't add legacy extensions`, }) expect(importAllExtensions).toHaveBeenCalledWith({ app, @@ -876,7 +845,6 @@ describe('ImportExtensionsIfNeeded', () => { app, remoteApp, developerPlatformClient, - force: false, }) // Then @@ -884,7 +852,7 @@ describe('ImportExtensionsIfNeeded', () => { expect(renderConfirmationPrompt).not.toHaveBeenCalled() }) - test('returns original app when user declines import', async () => { + test('throws silent error when user declines import', async () => { // Given const app = testAppLinked() const remoteApp = testOrganizationApp() @@ -895,52 +863,23 @@ describe('ImportExtensionsIfNeeded', () => { vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) - // When - const result = await importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: false, - }) - - // Then - expect(result).toBe(app) - expect(importAllExtensions).not.toHaveBeenCalled() - expect(reloadApp).not.toHaveBeenCalled() - }) - - test('throws error when platform does not support dashboard managed extensions and force is true', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) - const mockExtensions = [{title: 'Extension 1'}] - - vi.mocked(isTTY).mockReturnValue(true) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - // When/Then await expect( importExtensionsIfNeeded({ app, remoteApp, developerPlatformClient, - force: true, }), - ).rejects.toThrow( - "App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:", - ) + ).rejects.toThrowError(AbortSilentError) + expect(importAllExtensions).not.toHaveBeenCalled() + expect(reloadApp).not.toHaveBeenCalled() }) - test('throws error when platform does not support dashboard managed extensions and not in TTY', async () => { + test('throws error when not TTY without force-equivalent flags', async () => { // Given const app = testAppLinked() const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) + const developerPlatformClient = testDeveloperPlatformClient() const mockExtensions = [{title: 'Extension 1'}] vi.mocked(isTTY).mockReturnValue(false) @@ -952,60 +891,24 @@ describe('ImportExtensionsIfNeeded', () => { app, remoteApp, developerPlatformClient, - force: false, }), ).rejects.toThrow( "App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:", ) - }) - - test('imports extensions when platform does not support dashboard managed extensions', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) - const mockExtensions = [{title: 'Extension 1'}] - const reloadedApp = {...app, name: 'reloaded'} - - vi.mocked(isTTY).mockReturnValue(true) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - vi.mocked(renderConfirmationPrompt).mockResolvedValue(true) - vi.mocked(reloadApp).mockResolvedValue(reloadedApp as any) - // When - const result = await importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: false, - }) - - // Then - expect(getExtensions).toHaveBeenCalledWith({ - developerPlatformClient, - apiKey: remoteApp.apiKey, - organizationId: remoteApp.organizationId, - extensionTypes: allExtensionTypes, - onlyDashboardManaged: true, - }) - expect(importAllExtensions).toHaveBeenCalledWith({ - app, - remoteApp, - developerPlatformClient, - extensions: mockExtensions, - }) - expect(result).toBe(reloadedApp) + expect(renderConfirmationPrompt).not.toHaveBeenCalled() + expect(importAllExtensions).not.toHaveBeenCalled() }) - test('throws error when platform does not support dashboard managed extensions and force is true', async () => { + test.each([ + {flag: 'allowUpdates', options: {allowUpdates: true}}, + {flag: 'allowDeletes', options: {allowDeletes: true}}, + {flag: 'noRelease', options: {noRelease: true}}, + ])('throws error without prompting when $flag is set', async ({options}) => { // Given const app = testAppLinked() const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) + const developerPlatformClient = testDeveloperPlatformClient() const mockExtensions = [{title: 'Extension 1'}] vi.mocked(isTTY).mockReturnValue(true) @@ -1017,140 +920,13 @@ describe('ImportExtensionsIfNeeded', () => { app, remoteApp, developerPlatformClient, - force: true, - }), - ).rejects.toThrow( - "App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:", - ) - }) - - test('throws error when platform does not support dashboard managed extensions and not TTY', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) - const mockExtensions = [{title: 'Extension 1'}] - - vi.mocked(isTTY).mockReturnValue(false) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - - // When/Then - await expect( - importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: false, + ...options, }), ).rejects.toThrow( "App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:", ) - }) - - test('throws silent error when platform does not support dashboard managed extensions and user cancels', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - }) - const mockExtensions = [{title: 'Extension 1'}] - - vi.mocked(isTTY).mockReturnValue(true) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) - - // When/Then - await expect( - importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: false, - }), - ).rejects.toThrowError(AbortSilentError) - }) - - test('returns app without prompting when platform supports dashboard managed extensions and force is true', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient() - const mockExtensions = [{title: 'Extension 1'}] - vi.mocked(isTTY).mockReturnValue(true) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - - // When - const result = await importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: true, - }) - - // Then expect(renderConfirmationPrompt).not.toHaveBeenCalled() expect(importAllExtensions).not.toHaveBeenCalled() - expect(result).toBe(app) - }) - - test('returns app without prompting when platform supports dashboard managed extensions and not TTY', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient() - const mockExtensions = [{title: 'Extension 1'}] - - vi.mocked(isTTY).mockReturnValue(false) - vi.mocked(getExtensions).mockResolvedValue(mockExtensions as any) - - // When - const result = await importExtensionsIfNeeded({ - app, - remoteApp, - developerPlatformClient, - force: false, - }) - - // Then - expect(renderConfirmationPrompt).not.toHaveBeenCalled() - expect(importAllExtensions).not.toHaveBeenCalled() - expect(result).toBe(app) - }) - - test('ensureDeploymentIdsPresence throws when forcing deploy and unassigned dashboard extensions exist on unsupported platform', async () => { - // Given - const app = testAppLinked() - const remoteApp = testOrganizationApp() - const developerPlatformClient = testDeveloperPlatformClient({ - supportsDashboardManagedExtensions: false, - appExtensionRegistrations: async () => - Promise.resolve({ - app: { - extensionRegistrations: [{id: '', title: 'Legacy extension'}], - configurationRegistrations: [], - }, - } as any), - }) - - const identifiersModule = - await vi.importActual('./context/identifiers.js') - - // When/Then - await expect( - identifiersModule.ensureDeploymentIdsPresence({ - app, - developerPlatformClient, - appId: remoteApp.apiKey, - appName: remoteApp.title, - envIdentifiers: {}, - force: true, - release: true, - remoteApp, - } as any), - ).rejects.toThrow('need to be assigned') }) }) diff --git a/packages/app/src/cli/services/deploy.ts b/packages/app/src/cli/services/deploy.ts index 33dffd2b727..83f9fd5734c 100644 --- a/packages/app/src/cli/services/deploy.ts +++ b/packages/app/src/cli/services/deploy.ts @@ -39,9 +39,6 @@ export interface DeployOptions { /** If true, ignore any cached appId or extensionId */ reset: boolean - /** If true, proceed with deploy without asking for confirmation (equivalent to allowUpdates && allowDeletes) */ - force: boolean - /** If true, allow adding and updating extensions and configuration without user confirmation */ allowUpdates?: boolean @@ -73,58 +70,24 @@ interface ImportExtensionsIfNeededOptions { app: AppLinkedInterface remoteApp: OrganizationApp developerPlatformClient: DeveloperPlatformClient - force: boolean -} - -async function handleSupportedDashboardExtensions( - options: ImportExtensionsIfNeededOptions & { - extensions: ExtensionRegistration[] - }, -): Promise { - const {app, remoteApp, developerPlatformClient, force, extensions} = options - - if (force || !isTTY()) { - return app - } - - const message = [ - `App includes legacy extensions that will be deprecated soon:\n`, - extensions.map((ext) => ` - ${ext.title}`).join('\n'), - '\n\nRun ', - {command: 'shopify app import-extensions'}, - 'to add legacy extensions now?', - ] - const shouldImportExtensions = await renderConfirmationPrompt({ - message, - confirmationMessage: 'Yes, add legacy extensions and deploy', - cancellationMessage: 'No, skip for now', - }) - - if (shouldImportExtensions) { - await importAllExtensions({ - app, - remoteApp, - developerPlatformClient, - extensions, - }) - return reloadApp(app) - } - - return app + noRelease?: boolean + allowUpdates?: boolean + allowDeletes?: boolean } -async function handleUnsupportedDashboardExtensions( +async function handleDashboardExtensions( options: ImportExtensionsIfNeededOptions & { extensions: ExtensionRegistration[] }, ): Promise { - const {app, remoteApp, developerPlatformClient, force, extensions} = options + const {app, remoteApp, developerPlatformClient, noRelease, allowUpdates, allowDeletes, extensions} = options + const force = [noRelease, allowUpdates, allowDeletes].some(Boolean) const message = [ `App can't be deployed until Partner Dashboard managed extensions are added to your version or removed from your app:\n`, extensions.map((ext) => ` - ${ext.title}`).join('\n'), ] - const nextSteps = ['\n\nRun ', {command: 'shopify app import-extensions'}, 'to add legacy extensions.'] + const nextSteps = ['\n\nRun ', {command: 'shopify app import-extensions'}, ' to add legacy extensions.'] if (force || !isTTY()) { throw new AbortError(message, nextSteps) @@ -145,9 +108,9 @@ async function handleUnsupportedDashboardExtensions( extensions, }) return reloadApp(app) - } else { - throw new AbortSilentError() } + + throw new AbortSilentError() } export async function importExtensionsIfNeeded(options: ImportExtensionsIfNeededOptions): Promise { @@ -167,27 +130,22 @@ export async function importExtensionsIfNeeded(options: ImportExtensionsIfNeeded return app } - if (developerPlatformClient.supportsDashboardManagedExtensions) { - return handleSupportedDashboardExtensions({ - ...options, - extensions: extensionsNotImportedYet, - }) - } else { - return handleUnsupportedDashboardExtensions({ - ...options, - extensions: extensionsNotImportedYet, - }) - } + return handleDashboardExtensions({ + ...options, + extensions: extensionsNotImportedYet, + }) } export async function deploy(options: DeployOptions) { - const {remoteApp, developerPlatformClient, noRelease, force, allowUpdates, allowDeletes} = options + const {remoteApp, developerPlatformClient, noRelease, allowUpdates, allowDeletes} = options const app = await importExtensionsIfNeeded({ app: options.app, remoteApp, developerPlatformClient, - force, + noRelease, + allowUpdates, + allowDeletes, }) const {identifiers, didMigrateExtensionsToDevDash} = await ensureDeployContext({ diff --git a/packages/app/src/cli/services/format-config-info-body.test.ts b/packages/app/src/cli/services/format-config-info-body.test.ts index 60384d04379..0b7a2dace5c 100644 --- a/packages/app/src/cli/services/format-config-info-body.test.ts +++ b/packages/app/src/cli/services/format-config-info-body.test.ts @@ -137,62 +137,6 @@ describe('formatConfigInfoBody', () => { }) }) - test('if includeConfigOnDeploy is true, it shows an item for includeConfigOnDeploy', () => { - // GIVEN - const options = { - appName: 'test-app', - includeConfigOnDeploy: true, - } - - // WHEN - const result = formatConfigInfoBody(options) - - // THEN - const body = result as Token[] - expect(body[0]).toMatchObject({ - list: { - items: expect.arrayContaining(['Include config: Yes']), - }, - }) - }) - - test('if includeConfigOnDeploy is false, it shows an item for includeConfigOnDeploy with No', () => { - // GIVEN - const options = { - appName: 'test-app', - includeConfigOnDeploy: false, - } - - // WHEN - const result = formatConfigInfoBody(options) - - // THEN - const body = result as Token[] - expect(body[0]).toMatchObject({ - list: { - items: expect.arrayContaining(['Include config: No']), - }, - }) - }) - - test('if includeConfigOnDeploy is undefined, it does not show an item for includeConfigOnDeploy', () => { - // GIVEN - const options = { - appName: 'test-app', - } - - // WHEN - const result = formatConfigInfoBody(options) - - // THEN - const body = result as Token[] - expect(body[0]).toMatchObject({ - list: { - items: expect.not.arrayContaining([expect.stringMatching(/^Include config:/)]), - }, - }) - }) - test('if there are messages, it shows each message as a distinct paragraph', () => { // GIVEN const message1: Token[] = ['First message'] diff --git a/packages/app/src/cli/services/format-config-info-body.ts b/packages/app/src/cli/services/format-config-info-body.ts index 61e15a1ad14..3cbd4546032 100644 --- a/packages/app/src/cli/services/format-config-info-body.ts +++ b/packages/app/src/cli/services/format-config-info-body.ts @@ -5,7 +5,6 @@ interface FormatConfigInfoBodyOptions { org?: string devStores?: string[] updateURLs?: string - includeConfigOnDeploy?: boolean messages?: Token[][] } @@ -36,7 +35,6 @@ export function formatConfigInfoBody({ org, devStores, updateURLs, - includeConfigOnDeploy, messages, }: FormatConfigInfoBodyOptions): TokenItem { const items = [`App: ${appName}`] @@ -45,7 +43,6 @@ export function formatConfigInfoBody({ devStores.forEach((storeUrl) => items.push(`Dev store: ${storeUrl}`)) } if (updateURLs) items.push(`Update URLs: ${updateURLs}`) - if (includeConfigOnDeploy !== undefined) items.push(`Include config: ${includeConfigOnDeploy ? 'Yes' : 'No'}`) let body: Token[] = [{list: {items}}] diff --git a/packages/app/src/cli/services/release.ts b/packages/app/src/cli/services/release.ts index cd0e50d4578..4d3101fe85e 100644 --- a/packages/app/src/cli/services/release.ts +++ b/packages/app/src/cli/services/release.ts @@ -56,9 +56,8 @@ export async function release(options: ReleaseOptions) { extensionIdentifiersBreakdown, appTitle: remoteApp.title, release: true, - force: options.force, - allowUpdates: options.allowUpdates, - allowDeletes: options.allowDeletes, + allowUpdates: options.force || options.allowUpdates, + allowDeletes: options.force || options.allowDeletes, }) if (!confirmed) throw new AbortSilentError() interface Context { diff --git a/packages/app/src/cli/utilities/developer-platform-client.ts b/packages/app/src/cli/utilities/developer-platform-client.ts index 4aee0f37f1f..f092e6d1d1b 100644 --- a/packages/app/src/cli/utilities/developer-platform-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client.ts @@ -223,7 +223,6 @@ export interface DeveloperPlatformClient { readonly webUiName: string readonly organizationSource: OrganizationSource readonly bundleFormat: 'zip' | 'br' - readonly supportsDashboardManagedExtensions: boolean session: () => Promise /** * This is an unsafe method that should only be used when the session is expired. diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index 9f1d09b032c..07b32c020bd 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -193,7 +193,6 @@ export class AppManagementClient implements DeveloperPlatformClient { public readonly webUiName = 'Developer Dashboard' public readonly organizationSource = OrganizationSource.BusinessPlatform public readonly bundleFormat = 'br' - public readonly supportsDashboardManagedExtensions = false private _session: Session | undefined private constructor(session?: Session) { diff --git a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts index 15c7a5952ea..fa2dafdc36e 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts @@ -195,7 +195,6 @@ export class PartnersClient implements DeveloperPlatformClient { public readonly webUiName = 'Partner Dashboard' public readonly organizationSource = OrganizationSource.Partners public readonly bundleFormat = 'zip' - public readonly supportsDashboardManagedExtensions = true private _session: Session | undefined private constructor(session?: Session) {