Skip to content

Commit 5b7c919

Browse files
authored
Merge pull request #6812 from Shopify/02-02-ignore_the_identifier_attribute_for_event_subscriptions_when_fetching_configs_from_the_remote_server
Ignore the identifier attribute for event subscriptions when fetching configs from the remote server
2 parents cc56db6 + 66ef065 commit 5b7c919

File tree

6 files changed

+152
-2
lines changed

6 files changed

+152
-2
lines changed

packages/app/src/cli/models/app/loader.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,7 +2090,7 @@ redirect_urls = [ "https://example.com/api/auth" ]
20902090
const app = await loadTestingApp()
20912091

20922092
// Then
2093-
expect(app.allExtensions).toHaveLength(5)
2093+
expect(app.allExtensions).toHaveLength(6)
20942094
const extensionsConfig = app.allExtensions.map((ext) => ext.configuration)
20952095
expect(extensionsConfig).toEqual([
20962096
expect.objectContaining({
@@ -2106,6 +2106,9 @@ redirect_urls = [ "https://example.com/api/auth" ]
21062106
api_version: '2023-07',
21072107
},
21082108
}),
2109+
expect.objectContaining({
2110+
name: 'for-testing',
2111+
}),
21092112
expect.objectContaining({
21102113
pos: {
21112114
embedded: true,
@@ -2145,7 +2148,7 @@ redirect_urls = [ "https://example.com/api/auth" ]
21452148
const app = await loadTestingApp({remoteFlags: []})
21462149

21472150
// Then
2148-
expect(app.allExtensions).toHaveLength(6)
2151+
expect(app.allExtensions).toHaveLength(7)
21492152
const extensionsConfig = app.allExtensions.map((ext) => ext.configuration)
21502153
expect(extensionsConfig).toEqual([
21512154
{
@@ -2168,6 +2171,9 @@ redirect_urls = [ "https://example.com/api/auth" ]
21682171
},
21692172
name: 'for-testing-webhooks',
21702173
},
2174+
{
2175+
name: 'for-testing-webhooks',
2176+
},
21712177
{
21722178
application_url: 'https://example.com/lala',
21732179
embedded: true,

packages/app/src/cli/models/extensions/extension-instance.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {PosSpecIdentifier} from './specifications/app_config_point_of_sale.js'
1212
import {PrivacyComplianceWebhooksSpecIdentifier} from './specifications/app_config_privacy_compliance_webhooks.js'
1313
import {WebhooksSpecIdentifier} from './specifications/app_config_webhook.js'
1414
import {WebhookSubscriptionSpecIdentifier} from './specifications/app_config_webhook_subscription.js'
15+
import {EventsSpecIdentifier} from './specifications/app_config_events.js'
1516
import {
1617
ExtensionBuildOptions,
1718
buildFunctionExtension,
@@ -44,6 +45,7 @@ export const CONFIG_EXTENSION_IDS: string[] = [
4445
PrivacyComplianceWebhooksSpecIdentifier,
4546
WebhookSubscriptionSpecIdentifier,
4647
WebhooksSpecIdentifier,
48+
EventsSpecIdentifier,
4749
]
4850

4951
/**

packages/app/src/cli/models/extensions/load-specifications.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import appWebhooksSpec, {WebhooksSpecIdentifier} from './specifications/app_conf
66
import appWebhookSubscriptionSpec, {
77
WebhookSubscriptionSpecIdentifier,
88
} from './specifications/app_config_webhook_subscription.js'
9+
import appEventsSpec, {EventsSpecIdentifier} from './specifications/app_config_events.js'
910
import appBrandingSpec, {BrandingSpecIdentifier} from './specifications/app_config_branding.js'
1011
import appAccessSpec, {AppAccessSpecIdentifier} from './specifications/app_config_app_access.js'
1112
import appPrivacyComplienceSpec, {
@@ -32,6 +33,7 @@ const SORTED_CONFIGURATION_SPEC_IDENTIFIERS = [
3233
AppAccessSpecIdentifier,
3334
WebhooksSpecIdentifier,
3435
WebhookSubscriptionSpecIdentifier,
36+
EventsSpecIdentifier,
3537
PrivacyComplianceWebhooksSpecIdentifier,
3638
AppProxySpecIdentifier,
3739
PosSpecIdentifier,
@@ -58,6 +60,7 @@ function loadSpecifications() {
5860
appPrivacyComplienceSpec,
5961
appWebhooksSpec,
6062
appWebhookSubscriptionSpec,
63+
appEventsSpec,
6164
]
6265
const moduleSpecs = [
6366
checkoutPostPurchaseSpec,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {transformToEventsConfig, transformFromEventsConfig} from './transform/app_config_events.js'
2+
import {CustomTransformationConfig, createConfigExtensionSpecification} from '../specification.js'
3+
import {BaseSchemaWithoutHandle} from '../schemas.js'
4+
import {zod} from '@shopify/cli-kit/node/schema'
5+
6+
export const EventsSpecIdentifier = 'events'
7+
8+
const EventsTransformConfig: CustomTransformationConfig = {
9+
forward: transformFromEventsConfig,
10+
reverse: (content: object) => transformToEventsConfig(content),
11+
}
12+
13+
const EventsSchema = BaseSchemaWithoutHandle.extend({
14+
events: zod.any().optional(),
15+
})
16+
17+
const appEventsSpec = createConfigExtensionSpecification({
18+
identifier: EventsSpecIdentifier,
19+
schema: EventsSchema,
20+
transformConfig: EventsTransformConfig,
21+
})
22+
23+
export default appEventsSpec
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {transformToEventsConfig, transformFromEventsConfig} from './app_config_events.js'
2+
import {describe, expect, test} from 'vitest'
3+
4+
describe('transformFromEventsConfig', () => {
5+
test('returns content as-is without transformation', () => {
6+
const content = {
7+
events: {
8+
api_version: '2024-01',
9+
subscription: [{topic: 'orders/create', uri: 'https://example.com', actions: ['create']}],
10+
},
11+
}
12+
13+
const result = transformFromEventsConfig(content)
14+
15+
expect(result).toEqual(content)
16+
})
17+
})
18+
19+
describe('transformToEventsConfig', () => {
20+
test('strips server-managed identifier field from subscriptions while preserving all other fields', () => {
21+
const remoteContent = {
22+
events: {
23+
api_version: '2024-01',
24+
subscription: [
25+
{
26+
topic: 'orders/create',
27+
uri: 'https://example.com/webhook',
28+
actions: ['create'],
29+
identifier: 'id-1',
30+
},
31+
{
32+
topic: 'products/update',
33+
uri: 'https://example.com/webhook',
34+
actions: ['update'],
35+
handle: 'my-subscription',
36+
triggers: ['product_updated'],
37+
query: 'query { id }',
38+
query_filter: 'status:active',
39+
identifier: 'id-2',
40+
},
41+
],
42+
},
43+
}
44+
45+
const result = transformToEventsConfig(remoteContent)
46+
47+
expect(result).toEqual({
48+
events: {
49+
api_version: '2024-01',
50+
subscription: [
51+
{
52+
topic: 'orders/create',
53+
uri: 'https://example.com/webhook',
54+
actions: ['create'],
55+
},
56+
{
57+
topic: 'products/update',
58+
uri: 'https://example.com/webhook',
59+
actions: ['update'],
60+
handle: 'my-subscription',
61+
triggers: ['product_updated'],
62+
query: 'query { id }',
63+
query_filter: 'status:active',
64+
},
65+
],
66+
},
67+
})
68+
})
69+
70+
test('handles missing subscription field', () => {
71+
const remoteContent = {
72+
events: {
73+
api_version: '2024-01',
74+
},
75+
}
76+
77+
const result = transformToEventsConfig(remoteContent)
78+
79+
expect(result).toEqual({
80+
events: {
81+
api_version: '2024-01',
82+
subscription: undefined,
83+
},
84+
})
85+
})
86+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {getPathValue} from '@shopify/cli-kit/common/object'
2+
3+
/**
4+
* Transforms the events config from local to remote format.
5+
* No transformation needed - local config is already in the correct format for the server.
6+
* The 'identifier' field is server-managed and should not exist in local configs.
7+
*/
8+
export function transformFromEventsConfig(content: object) {
9+
return content
10+
}
11+
12+
/**
13+
* Transforms the events config from remote to local format.
14+
* Strips the server-managed 'identifier' field from subscriptions.
15+
*/
16+
export function transformToEventsConfig(content: object) {
17+
const eventsConfig = getPathValue(content, 'events') as {api_version: string; subscription: object[]}
18+
const apiVersion = getPathValue(eventsConfig, 'api_version')
19+
const subscription = getPathValue(eventsConfig, 'subscription') as {identifier: string}[]
20+
21+
// Server always includes identifier - strip it for local TOML
22+
const cleanedSubscriptions = subscription?.map((sub) => {
23+
const {identifier, ...rest} = sub
24+
return rest
25+
})
26+
27+
const events = apiVersion || cleanedSubscriptions ? {api_version: apiVersion, subscription: cleanedSubscriptions} : {}
28+
29+
return {events}
30+
}

0 commit comments

Comments
 (0)