diff --git a/packages/app/src/cli/services/context/breakdown-extensions.ts b/packages/app/src/cli/services/context/breakdown-extensions.ts index dfb5ea8c9f5..090cb94a3f7 100644 --- a/packages/app/src/cli/services/context/breakdown-extensions.ts +++ b/packages/app/src/cli/services/context/breakdown-extensions.ts @@ -15,7 +15,7 @@ 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 {deepCompare, deepDifference} from '@shopify/cli-kit/common/object' +import {deepCompare, deepCompareWithOrderInsensitiveArrays, deepDifference} from '@shopify/cli-kit/common/object' import {zod} from '@shopify/cli-kit/node/schema' export interface ConfigExtensionIdentifiersBreakdown { @@ -419,3 +419,58 @@ function loadDashboardIdentifiersBreakdown(currentRegistrations: RemoteSource[], unchanged: toUpdate, } } + +export function configExtensionsIdentifiersReleaseBreakdown({ + localApp, + versionAppModules, + activeAppVersion, +}: { + localApp: AppInterface + versionAppModules: AppModuleVersion[] + activeAppVersion?: AppVersion +}) { + if (localApp.allExtensions.filter((extension) => extension.isAppConfigExtension).length === 0) return + const versionConfig = remoteAppConfigurationExtensionContent( + versionAppModules, + localApp.specifications ?? [], + localApp.remoteFlags, + ) + const activeConfig = remoteAppConfigurationExtensionContent( + activeAppVersion?.appModuleVersions ?? [], + localApp.specifications ?? [], + localApp.remoteFlags, + ) + return buildConfigExtensionIdentifiersBreakdown(versionConfig, activeConfig) +} + +function buildConfigExtensionIdentifiersBreakdown( + localConfig: {[key: string]: unknown}, + remoteConfig: {[key: string]: unknown}, +): ConfigExtensionIdentifiersBreakdown | undefined { + const fieldNames = new Set([...Object.keys(localConfig), ...Object.keys(remoteConfig)]) + if (fieldNames.size === 0) return undefined + + const breakdown: ConfigExtensionIdentifiersBreakdown = { + existingFieldNames: [], + existingUpdatedFieldNames: [], + newFieldNames: [], + deletedFieldNames: [], + } + + for (const fieldName of fieldNames) { + const localValue = localConfig[fieldName] + const remoteValue = remoteConfig[fieldName] + + if (localValue === undefined) { + breakdown.deletedFieldNames.push(fieldName) + } else if (remoteValue === undefined) { + breakdown.newFieldNames.push(fieldName) + } else if (deepCompareWithOrderInsensitiveArrays(localValue, remoteValue)) { + breakdown.existingFieldNames.push(fieldName) + } else { + breakdown.existingUpdatedFieldNames.push(fieldName) + } + } + + return breakdown +} diff --git a/packages/app/src/cli/services/release.test.ts b/packages/app/src/cli/services/release.test.ts index 3f77c0c2dee..fb00a65db6d 100644 --- a/packages/app/src/cli/services/release.test.ts +++ b/packages/app/src/cli/services/release.test.ts @@ -1,6 +1,6 @@ import {release} from './release.js' import { - configExtensionsIdentifiersBreakdown, + configExtensionsIdentifiersReleaseBreakdown, extensionsIdentifiersReleaseBreakdown, } from './context/breakdown-extensions.js' import {testAppLinked, testDeveloperPlatformClient} from '../models/app/app.test-data.js' @@ -148,7 +148,7 @@ async function testRelease( ) { // Given vi.mocked(extensionsIdentifiersReleaseBreakdown).mockResolvedValue(buildExtensionsBreakdown()) - vi.mocked(configExtensionsIdentifiersBreakdown).mockResolvedValue(buildConfigExtensionsBreakdown()) + vi.mocked(configExtensionsIdentifiersReleaseBreakdown).mockReturnValue(buildConfigExtensionsBreakdown()) await release({ app, diff --git a/packages/app/src/cli/services/release.ts b/packages/app/src/cli/services/release.ts index 4d3101fe85e..9a5d7f38e23 100644 --- a/packages/app/src/cli/services/release.ts +++ b/packages/app/src/cli/services/release.ts @@ -1,5 +1,5 @@ import { - configExtensionsIdentifiersBreakdown, + configExtensionsIdentifiersReleaseBreakdown, extensionsIdentifiersReleaseBreakdown, } from './context/breakdown-extensions.js' import {AppLinkedInterface} from '../models/app/app.js' @@ -41,15 +41,11 @@ export async function release(options: ReleaseOptions) { remoteApp, options.version, ) - const configExtensionIdentifiersBreakdown = await configExtensionsIdentifiersBreakdown({ - developerPlatformClient, - apiKey: remoteApp.apiKey, + + const configExtensionIdentifiersBreakdown = configExtensionsIdentifiersReleaseBreakdown({ localApp: app, - remoteApp, - versionAppModules: versionDetails.appModuleVersions.map((appModuleVersion) => ({ - ...appModuleVersion, - })), - release: true, + versionAppModules: versionDetails.appModuleVersions, + activeAppVersion: await developerPlatformClient.activeAppVersion(remoteApp), }) const confirmed = await deployOrReleaseConfirmationPrompt({ configExtensionIdentifiersBreakdown, @@ -59,6 +55,7 @@ export async function release(options: ReleaseOptions) { allowUpdates: options.force || options.allowUpdates, allowDeletes: options.force || options.allowDeletes, }) + if (!confirmed) throw new AbortSilentError() interface Context { appRelease: AppReleaseSchema