Skip to content

Commit 48c5296

Browse files
Remove legacy schema
1 parent d39d272 commit 48c5296

29 files changed

+664
-1009
lines changed

packages/app/src/cli/commands/app/release.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {appFlags} from '../../flags.js'
22
import {release} from '../../services/release.js'
33
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
44
import {linkedAppContext} from '../../services/app-context.js'
5-
import {getAppConfigurationState} from '../../models/app/loader.js'
65
import {Flags} from '@oclif/core'
76
import {globalFlags} from '@shopify/cli-kit/node/cli'
87
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'
@@ -60,10 +59,6 @@ export default class Release extends AppLinkedCommand {
6059
if (!hasAnyForceFlags) {
6160
requiredNonTTYFlags.push('allow-updates')
6261
}
63-
const configurationState = await getAppConfigurationState(flags.path, flags.config)
64-
if (configurationState.state === 'template-only' && !clientId) {
65-
requiredNonTTYFlags.push('client-id')
66-
}
6762
this.failMissingNonTTYFlags(flags, requiredNonTTYFlags)
6863

6964
const {app, remoteApp, developerPlatformClient} = await linkedAppContext({

packages/app/src/cli/models/app/app.test-data.ts

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import {
22
App,
3-
AppConfiguration,
4-
AppConfigurationSchema,
3+
AppSchema,
54
AppConfigurationWithoutPath,
65
AppInterface,
76
AppLinkedInterface,
87
CurrentAppConfiguration,
9-
LegacyAppConfiguration,
108
WebType,
119
getAppVersionedSchema,
1210
} from './app.js'
@@ -94,16 +92,13 @@ export const DEFAULT_CONFIG = {
9492
embedded: true,
9593
access_scopes: {
9694
scopes: 'read_products',
95+
use_legacy_install_flow: true,
9796
},
9897
}
9998

10099
export function testApp(app: Partial<AppInterface> = {}, schemaType: 'current' | 'legacy' = 'legacy'): AppInterface {
101100
const getConfig = () => {
102-
if (schemaType === 'legacy') {
103-
return {scopes: '', extension_directories: [], path: ''}
104-
} else {
105-
return DEFAULT_CONFIG as CurrentAppConfiguration
106-
}
101+
return DEFAULT_CONFIG as CurrentAppConfiguration
107102
}
108103

109104
const newApp = new App({
@@ -126,7 +121,7 @@ export function testApp(app: Partial<AppInterface> = {}, schemaType: 'current' |
126121
dotenv: app.dotenv,
127122
errors: app.errors,
128123
specifications: app.specifications ?? [],
129-
configSchema: (app.configSchema ?? AppConfigurationSchema) as any,
124+
configSchema: (app.configSchema ?? AppSchema) as any,
130125
remoteFlags: app.remoteFlags ?? [],
131126
hiddenConfig: app.hiddenConfig ?? {},
132127
devApplicationURLs: app.devApplicationURLs,
@@ -150,20 +145,6 @@ interface TestAppWithConfigOptions {
150145
config: object
151146
}
152147

153-
export function testAppWithLegacyConfig({
154-
app = {},
155-
config = {},
156-
}: TestAppWithConfigOptions): AppInterface<LegacyAppConfiguration> {
157-
const configuration: AppConfiguration = {
158-
path: '',
159-
scopes: '',
160-
name: 'name',
161-
extension_directories: [],
162-
...config,
163-
}
164-
return testApp({...app, configuration}) as AppInterface<LegacyAppConfiguration>
165-
}
166-
167148
export function testAppWithConfig(options?: TestAppWithConfigOptions): AppLinkedInterface {
168149
const app = testAppLinked(options?.app)
169150
app.configuration = {
@@ -207,7 +188,9 @@ export function testOrganizationApp(app: Partial<OrganizationApp> = {}): Organiz
207188
return {...defaultApp, ...app}
208189
}
209190

210-
export const placeholderAppConfiguration: AppConfigurationWithoutPath = {scopes: ''}
191+
export const placeholderAppConfiguration: AppConfigurationWithoutPath = {
192+
client_id: '',
193+
}
211194

212195
export async function testUIExtension(
213196
uiExtension: Omit<Partial<ExtensionInstance>, 'configuration'> & {

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

Lines changed: 24 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import {
22
AppSchema,
33
CurrentAppConfiguration,
4-
LegacyAppConfiguration,
5-
TemplateConfigSchema,
64
getAppScopes,
75
getAppScopesArray,
8-
getTemplateScopesArray,
96
getUIExtensionRendererVersion,
10-
isCurrentAppSchema,
11-
isLegacyAppSchema,
127
validateExtensionsHandlesInCollection,
138
validateFunctionExtensionsWithUiHandle,
149
} from './app.js'
@@ -63,69 +58,32 @@ const CORRECT_CURRENT_APP_SCHEMA: CurrentAppConfiguration = {
6358
},
6459
}
6560

66-
const CORRECT_LEGACY_APP_SCHEMA: LegacyAppConfiguration = {
67-
path: '',
68-
extension_directories: [],
69-
web_directories: [],
70-
scopes: 'write_products',
71-
}
72-
7361
describe('app schema validation', () => {
74-
describe('legacy schema validator', () => {
75-
test('checks whether legacy app schema is valid -- pass', () => {
76-
expect(isLegacyAppSchema(CORRECT_LEGACY_APP_SCHEMA)).toBe(true)
77-
})
78-
test('checks whether legacy app schema is valid -- fail', () => {
79-
const config = {
80-
...CORRECT_LEGACY_APP_SCHEMA,
81-
some_other_key: 'i am not valid, i will fail',
82-
}
83-
expect(isLegacyAppSchema(config)).toBe(false)
84-
})
62+
test('extension_directories should be transformed to double asterisks', () => {
63+
const config = {
64+
...CORRECT_CURRENT_APP_SCHEMA,
65+
extension_directories: ['extensions/*'],
66+
}
67+
const parsed = AppSchema.parse(config)
68+
expect(parsed.extension_directories).toEqual(['extensions/**'])
8569
})
8670

87-
describe('current schema validator', () => {
88-
test('checks whether current app schema is valid -- pass', () => {
89-
expect(isCurrentAppSchema(CORRECT_CURRENT_APP_SCHEMA)).toBe(true)
90-
})
91-
test('checks whether current app schema is valid -- fail', () => {
92-
const config = {
93-
...CORRECT_CURRENT_APP_SCHEMA,
94-
}
95-
96-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
97-
// @ts-ignore
98-
delete config.client_id
99-
100-
expect(isCurrentAppSchema(config)).toBe(false)
101-
})
102-
103-
test('extension_directories should be transformed to double asterisks', () => {
104-
const config = {
105-
...CORRECT_CURRENT_APP_SCHEMA,
106-
extension_directories: ['extensions/*'],
107-
}
108-
const parsed = AppSchema.parse(config)
109-
expect(parsed.extension_directories).toEqual(['extensions/**'])
110-
})
111-
112-
test('extension_directories is not transformed if it ends with double asterisks', () => {
113-
const config = {
114-
...CORRECT_CURRENT_APP_SCHEMA,
115-
extension_directories: ['extensions/**'],
116-
}
117-
const parsed = AppSchema.parse(config)
118-
expect(parsed.extension_directories).toEqual(['extensions/**'])
119-
})
71+
test('extension_directories is not transformed if it ends with double asterisks', () => {
72+
const config = {
73+
...CORRECT_CURRENT_APP_SCHEMA,
74+
extension_directories: ['extensions/**'],
75+
}
76+
const parsed = AppSchema.parse(config)
77+
expect(parsed.extension_directories).toEqual(['extensions/**'])
78+
})
12079

121-
test('extension_directories is not transformed if it doesnt end with a wildcard', () => {
122-
const config = {
123-
...CORRECT_CURRENT_APP_SCHEMA,
124-
extension_directories: ['extensions'],
125-
}
126-
const parsed = AppSchema.parse(config)
127-
expect(parsed.extension_directories).toEqual(['extensions'])
128-
})
80+
test('extension_directories is not transformed if it doesnt end with a wildcard', () => {
81+
const config = {
82+
...CORRECT_CURRENT_APP_SCHEMA,
83+
extension_directories: ['extensions'],
84+
}
85+
const parsed = AppSchema.parse(config)
86+
expect(parsed.extension_directories).toEqual(['extensions'])
12987
})
13088
})
13189

@@ -212,117 +170,19 @@ describe('getUIExtensionRendererVersion', () => {
212170
})
213171

214172
describe('getAppScopes', () => {
215-
test('returns the scopes key when schema is legacy', () => {
216-
const config = {path: '', scopes: 'read_themes,read_products'}
217-
expect(getAppScopes(config)).toEqual('read_themes,read_products')
218-
})
219-
220-
test('returns the access_scopes.scopes key when schema is current', () => {
173+
test('returns the access_scopes.scopes key', () => {
221174
const config = {...DEFAULT_CONFIG, access_scopes: {scopes: 'read_themes,read_themes'}}
222175
expect(getAppScopes(config)).toEqual('read_themes,read_themes')
223176
})
224177
})
225178

226179
describe('getAppScopesArray', () => {
227-
test('returns the scopes key when schema is legacy', () => {
228-
const config = {path: '', scopes: 'read_themes, read_order ,write_products'}
229-
expect(getAppScopesArray(config)).toEqual(['read_themes', 'read_order', 'write_products'])
230-
})
231-
232-
test('returns the access_scopes.scopes key when schema is current', () => {
180+
test('returns the access_scopes.scopes key', () => {
233181
const config = {...DEFAULT_CONFIG, access_scopes: {scopes: 'read_themes, read_order ,write_products'}}
234182
expect(getAppScopesArray(config)).toEqual(['read_themes', 'read_order', 'write_products'])
235183
})
236184
})
237185

238-
describe('TemplateConfigSchema', () => {
239-
test('parses config with legacy scopes format', () => {
240-
const config = {scopes: 'read_products,write_products'}
241-
const result = TemplateConfigSchema.parse(config)
242-
expect(result.scopes).toEqual('read_products,write_products')
243-
})
244-
245-
test('parses config with access_scopes format', () => {
246-
const config = {access_scopes: {scopes: 'read_products,write_products'}}
247-
const result = TemplateConfigSchema.parse(config)
248-
expect(result.access_scopes?.scopes).toEqual('read_products,write_products')
249-
})
250-
251-
test('preserves extra keys like metafields via passthrough', () => {
252-
const config = {
253-
scopes: 'write_products',
254-
product: {
255-
metafields: {
256-
app: {
257-
demo_info: {
258-
type: 'single_line_text_field',
259-
name: 'Demo Source Info',
260-
},
261-
},
262-
},
263-
},
264-
webhooks: {
265-
api_version: '2025-07',
266-
subscriptions: [{uri: '/webhooks', topics: ['app/uninstalled']}],
267-
},
268-
}
269-
const result = TemplateConfigSchema.parse(config)
270-
expect(result.product).toEqual(config.product)
271-
expect(result.webhooks).toEqual(config.webhooks)
272-
})
273-
274-
test('parses empty config', () => {
275-
const config = {}
276-
const result = TemplateConfigSchema.parse(config)
277-
expect(result).toEqual({})
278-
})
279-
})
280-
281-
describe('getTemplateScopesArray', () => {
282-
test('returns scopes from legacy format', () => {
283-
const config = {scopes: 'read_themes,write_products'}
284-
expect(getTemplateScopesArray(config)).toEqual(['read_themes', 'write_products'])
285-
})
286-
287-
test('returns scopes from access_scopes format', () => {
288-
const config = {access_scopes: {scopes: 'read_themes,write_products'}}
289-
expect(getTemplateScopesArray(config)).toEqual(['read_themes', 'write_products'])
290-
})
291-
292-
test('trims whitespace from scopes and sorts', () => {
293-
const config = {scopes: ' write_products , read_themes '}
294-
expect(getTemplateScopesArray(config)).toEqual(['read_themes', 'write_products'])
295-
})
296-
297-
test('includes empty strings from consecutive commas (caller should handle)', () => {
298-
const config = {scopes: 'read_themes,write_products'}
299-
expect(getTemplateScopesArray(config)).toEqual(['read_themes', 'write_products'])
300-
})
301-
302-
test('returns empty array when no scopes defined', () => {
303-
const config = {}
304-
expect(getTemplateScopesArray(config)).toEqual([])
305-
})
306-
307-
test('returns empty array when scopes is empty string', () => {
308-
const config = {scopes: ''}
309-
expect(getTemplateScopesArray(config)).toEqual([])
310-
})
311-
312-
test('returns empty array when access_scopes.scopes is empty', () => {
313-
const config = {access_scopes: {scopes: ''}}
314-
expect(getTemplateScopesArray(config)).toEqual([])
315-
})
316-
317-
test('prefers legacy scopes over access_scopes when both present', () => {
318-
const config = {
319-
scopes: 'read_themes',
320-
access_scopes: {scopes: 'write_products'},
321-
}
322-
expect(getTemplateScopesArray(config)).toEqual(['read_themes'])
323-
})
324-
})
325-
326186
describe('preDeployValidation', () => {
327187
test('throws an error when app-specific webhooks are used with legacy install flow', async () => {
328188
// Given
@@ -418,18 +278,6 @@ Learn more: https://shopify.dev/docs/apps/build/authentication-authorization/app
418278
await expect(app.preDeployValidation()).resolves.not.toThrow()
419279
})
420280

421-
test('does not throw an error for legacy schema apps', async () => {
422-
// Given
423-
const configuration: LegacyAppConfiguration = {
424-
...CORRECT_LEGACY_APP_SCHEMA,
425-
scopes: 'read_orders',
426-
}
427-
const app = testApp(configuration, 'legacy')
428-
429-
// When/Then
430-
await expect(app.preDeployValidation()).resolves.not.toThrow()
431-
})
432-
433281
test('handles null/undefined subscriptions safely', async () => {
434282
// Given
435283
const configuration: CurrentAppConfiguration = {

0 commit comments

Comments
 (0)