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
5 changes: 3 additions & 2 deletions packages/app/src/cli/models/app/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ describe('manifest', () => {
assets: appHome.uid,
target: appHome.contextValue,
config: expect.objectContaining({
app_url: 'https://new-url.io',
application_url: 'https://new-url.io',
}),
},
{
Expand Down Expand Up @@ -942,8 +942,9 @@ describe('manifest', () => {
assets: appHome.uid,
target: appHome.contextValue,
config: {
app_url: 'https://example.com',
application_url: 'https://example.com',
embedded: true,
metafields: [],
},
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import spec from './app_config_app_home.js'
import {placeholderAppConfiguration} from '../../app/app.test-data.js'
import {describe, expect, test} from 'vitest'

describe('app_home', () => {
describe('transform', () => {
test('should return the transformed object', () => {
// Given
const object = {
application_url: 'https://my-app-url.dev',
embedded: true,
app_preferences: {
url: 'https://my-preferences-url.dev',
},
}

test('transformLocalToRemote should be undefined', () => {
const appConfigSpec = spec

// When
const result = appConfigSpec.transformLocalToRemote!(object, placeholderAppConfiguration)

// Then
expect(result).toMatchObject({
app_url: 'https://my-app-url.dev',
embedded: true,
preferences_url: 'https://my-preferences-url.dev',
})
expect(appConfigSpec.transformLocalToRemote).toBeUndefined()
})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {validateUrl} from '../../app/validation/common.js'
import {BaseSchemaWithoutHandle} from '../schemas.js'
import {TransformationConfig, createConfigExtensionSpecification} from '../specification.js'
import {configWithoutFirstClassFields, createConfigExtensionSpecification} from '../specification.js'
import {getPathValue, setPathValue} from '@shopify/cli-kit/common/object'
import {zod} from '@shopify/cli-kit/node/schema'

const AppHomeSchema = BaseSchemaWithoutHandle.extend({
Expand All @@ -13,18 +14,25 @@ const AppHomeSchema = BaseSchemaWithoutHandle.extend({
.optional(),
})

const AppHomeTransformConfig: TransformationConfig = {
app_url: 'application_url',
embedded: 'embedded',
preferences_url: 'app_preferences.url',
}

export const AppHomeSpecIdentifier = 'app_home'

const appHomeSpec = createConfigExtensionSpecification({
identifier: AppHomeSpecIdentifier,
schema: AppHomeSchema,
transformConfig: AppHomeTransformConfig,
deployConfig: async (config) => {
const {name, ...rest} = configWithoutFirstClassFields(config)
return rest
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing forward mapping in deployConfig after removing transformLocalToRemote

The PR removes transformConfig (which previously generated transformLocalToRemote) and tests that transformLocalToRemote is now undefined. However, deployConfig currently strips first-class fields and returns the remaining config as-is.

Local schema uses: application_url, embedded, app_preferences.url. Platform expects: app_url, embedded, preferences_url. With the current deployConfig implementation returning the rest object, the deploy payload will likely include application_url and app_preferences rather than app_url and preferences_url.

Evidence: createConfigExtensionSpecification only builds transformLocalToRemote from transformConfig; this PR removes transformConfig so no forward transform exists. Nothing in deployConfig performs required key translation. Impact: app home config may not apply, server-side validation may fail, deploys can be blocked, or production routing can break for all users deploying/updating app_home via this CLI path.

transformRemoteToLocal: (remoteContent: object) => {
const result: {[key: string]: unknown} = {}
const appUrl = getPathValue(remoteContent, 'app_url')
if (appUrl !== undefined) result.application_url = appUrl
const embedded = getPathValue(remoteContent, 'embedded')
if (embedded !== undefined) result.embedded = embedded
const preferencesUrl = getPathValue(remoteContent, 'preferences_url')
if (preferencesUrl !== undefined) setPathValue(result, 'app_preferences.url', preferencesUrl)
return result
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformRemoteToLocal can write invalid types into local config (no type validation)

The new transformRemoteToLocal copies values from remoteContent into result without verifying types. For example, remoteContent.app_url is assigned to result.application_url even if it’s not a string, and preferences_url is written under app_preferences.url even if it’s not a string. This bypasses the previous pattern where transforms often operated on known-good shapes or via schema-backed reverse transforms.

Evidence:

const appUrl = getPathValue(remoteContent, 'app_url')
if (appUrl !== undefined) result.application_url = appUrl
...
const preferencesUrl = getPathValue(remoteContent, 'preferences_url')
if (preferencesUrl !== undefined) setPathValue(result, 'app_preferences.url', preferencesUrl)

getPathValue returns unknown, and no typeof === 'string' checks exist.

Impact:

  • User impact: CLI may write invalid shopify.app.toml or in-memory config; later validation may fail with confusing errors, or invalid values may propagate to other code paths.
  • Backend impact: If invalid local config is later deployed, could lead to rejected deploys or inconsistent settings.
  • Scale: Affects any app where the remote API returns unexpected types (bad data, forward-compat changes, or partial responses).

patchWithAppDevURLs: (config, urls) => {
config.application_url = urls.applicationUrl
},
Expand Down
Loading