Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/app/src/cli/models/extensions/specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export type ExtensionFeature =

export type TransformationConfig = Record<string, string>

export interface TransformRemoteToLocalOptions {
readonly flags?: Flag[]
readonly module?: {readonly handle: string}
}

export interface CustomTransformationConfig {
forward?: (obj: object, appConfiguration: AppConfiguration, options?: {flags?: Flag[]}) => object
reverse?: (obj: object, options?: {flags?: Flag[]}) => object
reverse?: (obj: object, options?: TransformRemoteToLocalOptions) => object
}

type ExtensionExperience = 'extension' | 'configuration'
Expand Down Expand Up @@ -119,7 +124,7 @@ export interface ExtensionSpecification<TConfiguration extends BaseConfigType =
* @param options - Additional options to be used in the transformation
* @returns Transformed configuration to use in place of the platform provided content
*/
transformRemoteToLocal?: (remoteContent: object, options?: {flags?: Flag[]}) => object
transformRemoteToLocal?: (remoteContent: object, options?: TransformRemoteToLocalOptions) => object

uidStrategy: UidStrategy

Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/cli/services/app/select-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export function remoteAppConfigurationExtensionContent(
const config = module.config
if (!config) return

remoteAppConfig = deepMergeObjects(remoteAppConfig, configSpec.transformRemoteToLocal?.(config, {flags}) ?? config)
remoteAppConfig = deepMergeObjects(
remoteAppConfig,
configSpec.transformRemoteToLocal?.(config, {flags, module: {handle: module.registrationTitle}}) ?? config,
)
})

return {...remoteAppConfig}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../../models/app/app.test-data.js'
import {OrganizationApp} from '../../models/organization.js'
import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
import {BaseConfigType} from '../../models/extensions/schemas.js'
import {BaseConfigType, BaseSchemaWithoutHandle} from '../../models/extensions/schemas.js'
import {createConfigExtensionSpecification} from '../../models/extensions/specification.js'
import {AppModuleVersion, DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {deployOrReleaseConfirmationPrompt} from '../../prompts/deploy-release.js'
Expand Down Expand Up @@ -623,6 +623,63 @@ describe('classifyDeployExtensionChanges', () => {
})

describe('ensureDeployIdentifiersFromAppVersion', () => {
test('passes each module handle to a config reverse transform without changing its output', async () => {
const configuration: BaseConfigType & {synthetic_config: {enabled: boolean}} = {synthetic_config: {enabled: true}}
const reverse = vi.fn((content: object) => content)
const specification = createConfigExtensionSpecification<BaseConfigType>({
identifier: 'synthetic_config',
schema: BaseSchemaWithoutHandle.extend({synthetic_config: zod.object({enabled: zod.boolean()})}),
transformConfig: {forward: (content) => content, reverse},
})
const extension = new ExtensionInstance<BaseConfigType>({
configuration,
configurationPath: 'shopify.app.toml',
directory: '/app',
specification,
})
extension.handle = 'Local_Module_Handle'
const app = testApp({
...APP,
allExtensions: [extension],
specifications: [specification],
})
const remote: AppModuleVersion = {
registrationId: extension.uid,
registrationUuid: 'synthetic-uuid',
registrationTitle: 'Remote_Module_Handle',
type: specification.identifier,
config: configuration,
specification: {
identifier: specification.identifier,
name: 'Synthetic config',
experience: 'configuration',
options: {managementExperience: 'cli'},
},
}

await ensureDeployIdentifiersFromAppVersion(deployOptions({app, activeAppVersion: {appModuleVersions: [remote]}}))

expect(reverse).toHaveBeenCalledTimes(2)
expect(reverse).toHaveBeenNthCalledWith(1, configuration, {
flags: app.remoteFlags,
module: {handle: extension.handle},
})
expect(reverse).toHaveBeenNthCalledWith(2, configuration, {
flags: app.remoteFlags,
module: {handle: remote.registrationTitle},
})
expect(deployOrReleaseConfirmationPrompt).toHaveBeenLastCalledWith(
expect.objectContaining({
configExtensionIdentifiersBreakdown: {
existingFieldNames: ['synthetic_config'],
existingUpdatedFieldNames: [],
newFieldNames: [],
deletedFieldNames: [],
},
}),
)
})

test('prompts with the existing UI breakdown shape and returns deploy identifiers', async () => {
const identifiers = await ensureDeployIdentifiersFromAppVersion(
deployOptions({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,10 @@ async function localAppConfigurationExtensionContent(app: AppInterface, apiKey:
// eslint-disable-next-line no-await-in-loop
const deployConfig = await extension.deployConfig({apiKey, appConfiguration: app.configuration})
const localConfig =
extension.specification.transformRemoteToLocal?.(deployConfig ?? {}, {flags: app.remoteFlags}) ??
extension.configuration
extension.specification.transformRemoteToLocal?.(deployConfig ?? {}, {
flags: app.remoteFlags,
module: {handle: extension.handle},
}) ?? extension.configuration
appConfig = deepMergeObjects(appConfig, localConfig)
}

Expand Down
Loading