Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/short-cooks-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/cli-kit': minor
'@shopify/app': minor
---

Added CLI support for extensions.supported_features in toml
3 changes: 3 additions & 0 deletions packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ export async function testUIExtension(
sources: [],
},
},
supported_features: {
offline_mode: false,
},
extension_points: [
{
target: 'target1',
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/cli/models/app/loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,9 @@ redirect_urls = [ "https://example.com/api/auth" ]
[extensions.capabilities.iframe]
sources = ["https://my-iframe.com"]

[extensions.supported_features]
offline_mode = true

[extensions.settings]
[[extensions.settings.fields]]
key = "field_key"
Expand Down Expand Up @@ -1560,6 +1563,9 @@ redirect_urls = [ "https://example.com/api/auth" ]
sources: ['https://my-iframe.com'],
},
},
supported_features: {
offline_mode: true,
},
settings: {
fields: [
{
Expand Down
5 changes: 5 additions & 0 deletions packages/app/src/cli/models/extensions/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const CapabilitiesSchema = zod.object({
iframe: IframeCapabilitySchema.optional(),
})

const SupportedFeaturesSchema = zod.object({
offline_mode: zod.boolean().optional(),
})

export const ExtensionsArraySchema = zod.object({
type: zod.string().optional(),
extensions: zod.array(zod.any()).optional(),
Expand Down Expand Up @@ -108,6 +112,7 @@ export const BaseSchema = zod.object({
api_version: ApiVersionSchema.optional(),
extension_points: zod.any().optional(),
capabilities: CapabilitiesSchema.optional(),
supported_features: SupportedFeaturesSchema.optional(),
settings: SettingsSchema.optional(),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const checkoutSpec = createExtensionSpecification({
return {
extension_points: config.extension_points,
capabilities: config.capabilities,
supported_features: config.supported_features,
metafields: config.metafields ?? [],
name: config.name,
settings: config.settings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,13 +978,126 @@ Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`
...uiExtension.configuration.capabilities.iframe,
},
},
supported_features: undefined,
name: uiExtension.configuration.name,
description: uiExtension.configuration.description,
api_version: uiExtension.configuration.api_version,
settings: uiExtension.configuration.settings,
})
})
})

test('returns supported_features with offline_mode true when configured', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
vi.spyOn(loadLocales, 'loadLocalesConfig').mockResolvedValue({})
const configurationPath = joinPath(tmpDir, 'shopify.extension.toml')
const allSpecs = await loadLocalExtensionsSpecifications()
const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')!
const uiExtension = new ExtensionInstance({
configuration: {
extension_points: [],
api_version: '2023-01' as const,
name: 'UI Extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
supported_features: {
offline_mode: true,
},
settings: {},
},
directory: tmpDir,
specification,
configurationPath,
entryPath: '',
})

// When
const deployConfig = await uiExtension.deployConfig({
apiKey: 'apiKey',
appConfiguration: placeholderAppConfiguration,
})

// Then
expect(deployConfig?.supported_features).toStrictEqual({
offline_mode: true,
})
})
})

test('returns supported_features with offline_mode false when configured', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
vi.spyOn(loadLocales, 'loadLocalesConfig').mockResolvedValue({})
const configurationPath = joinPath(tmpDir, 'shopify.extension.toml')
const allSpecs = await loadLocalExtensionsSpecifications()
const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')!
const uiExtension = new ExtensionInstance({
configuration: {
extension_points: [],
api_version: '2023-01' as const,
name: 'UI Extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
supported_features: {
offline_mode: false,
},
settings: {},
},
directory: tmpDir,
specification,
configurationPath,
entryPath: '',
})

// When
const deployConfig = await uiExtension.deployConfig({
apiKey: 'apiKey',
appConfiguration: placeholderAppConfiguration,
})

// Then
expect(deployConfig?.supported_features).toStrictEqual({
offline_mode: false,
})
})
})

test('returns supported_features as undefined when not configured', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
vi.spyOn(loadLocales, 'loadLocalesConfig').mockResolvedValue({})
const configurationPath = joinPath(tmpDir, 'shopify.extension.toml')
const allSpecs = await loadLocalExtensionsSpecifications()
const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')!
const uiExtension = new ExtensionInstance({
configuration: {
extension_points: [],
api_version: '2023-01' as const,
name: 'UI Extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
settings: {},
},
directory: tmpDir,
specification,
configurationPath,
entryPath: '',
})

// When
const deployConfig = await uiExtension.deployConfig({
apiKey: 'apiKey',
appConfiguration: placeholderAppConfiguration,
})

// Then
expect(deployConfig?.supported_features).toBeUndefined()
})
})
})

describe('getBundleExtensionStdinContent()', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const uiExtensionSpec = createExtensionSpecification({
api_version: config.api_version,
extension_points: transformedExtensionPoints,
capabilities: config.capabilities,
supported_features: config.supported_features,
name: config.name,
description: config.description,
settings: config.settings,
Expand Down
92 changes: 92 additions & 0 deletions packages/app/src/cli/services/dev/extension/payload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,98 @@ describe('getUIExtensionPayload', () => {
})
})

describe('supportedFeatures', () => {
test('returns supportedFeatures with offlineMode true when offline_mode is enabled', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const uiExtension = await testUIExtension({
directory: tmpDir,
configuration: {
name: 'test-extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
supported_features: {
offline_mode: true,
},
extension_points: [],
},
})
const options: ExtensionsPayloadStoreOptions = {} as ExtensionsPayloadStoreOptions

// When
const got = await getUIExtensionPayload(uiExtension, 'mock-bundle-path', {
...options,
currentDevelopmentPayload: {},
})

// Then
expect(got.supportedFeatures).toStrictEqual({
offlineMode: true,
})
})
})

test('returns supportedFeatures with offlineMode false when offline_mode is disabled', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const uiExtension = await testUIExtension({
directory: tmpDir,
configuration: {
name: 'test-extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
supported_features: {
offline_mode: false,
},
extension_points: [],
},
})
const options: ExtensionsPayloadStoreOptions = {} as ExtensionsPayloadStoreOptions

// When
const got = await getUIExtensionPayload(uiExtension, 'mock-bundle-path', {
...options,
currentDevelopmentPayload: {},
})

// Then
expect(got.supportedFeatures).toStrictEqual({
offlineMode: false,
})
})
})

test('returns supportedFeatures with offlineMode false when supported_features is not configured', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const uiExtension = await testUIExtension({
directory: tmpDir,
configuration: {
name: 'test-extension',
type: 'ui_extension',
metafields: [],
capabilities: {},
extension_points: [],
},
})
const options: ExtensionsPayloadStoreOptions = {} as ExtensionsPayloadStoreOptions

// When
const got = await getUIExtensionPayload(uiExtension, 'mock-bundle-path', {
...options,
currentDevelopmentPayload: {},
})

// Then
expect(got.supportedFeatures).toStrictEqual({
offlineMode: false,
})
})
})
})

test('adds root.url, resource.url and surface to extensionPoints[n] when extensionPoints[n] is an object', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/cli/services/dev/extension/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export async function getUIExtensionPayload(
lastUpdated: (await fileLastUpdatedTimestamp(extensionOutputPath)) ?? 0,
},
},
supportedFeatures: {
offlineMode: extension.configuration.supported_features?.offline_mode ?? false,
},
capabilities: {
blockProgress: extension.configuration.capabilities?.block_progress ?? false,
networkAccess: extension.configuration.capabilities?.network_access ?? false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ export interface DevNewExtensionPointSchema extends NewExtensionPointSchemaType
}
}

interface SupportedFeatures {
offlineMode: boolean
}

export interface UIExtensionPayload {
assets: {
main: Asset
}
supportedFeatures?: SupportedFeatures
capabilities?: Capabilities
development: {
resource: {
Expand Down
Loading