Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@ import {
import {UIExtensionPayload, ExtensionsEndpointPayload} from './models.js'
import * as payload from '../payload.js'
import {ExtensionInstance} from '../../../../models/extensions/extension-instance.js'
import {normalizeStoreFqdn, storeAdminUrl} from '@shopify/cli-kit/node/context/fqdn'
import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/node/context/fqdn', async (importOriginal) => {
const original = await importOriginal<typeof import('@shopify/cli-kit/node/context/fqdn')>()
return {
...original,
normalizeStoreFqdn: vi.fn(original.normalizeStoreFqdn),
storeAdminUrl: vi.fn(original.storeAdminUrl),
}
})

describe('getExtensionsPayloadStoreRawPayload()', () => {
test('returns the raw payload', async () => {
// Given
Expand All @@ -34,7 +44,7 @@ describe('getExtensionsPayloadStoreRawPayload()', () => {
app: {
title: 'mock-app-name',
apiKey: 'mock-api-key',
url: 'https://mock-store-fqdn.myshopify.com/admin/oauth/redirect_from_cli?client_id=mock-api-key',
url: 'https://admin.shopify.com/store/mock-store-fqdn/extensions-dev/preview?client_id=mock-api-key',
mobileUrl:
'https://mock-store-fqdn.myshopify.com/admin/apps/mock-api-key?shop=mock-store-fqdn.myshopify.com&host=bW9jay1zdG9yZS1mcWRuLm15c2hvcGlmeS5jb20vYWRtaW4vYXBwcy9tb2NrLWFwaS1rZXk',
},
Expand All @@ -52,6 +62,30 @@ describe('getExtensionsPayloadStoreRawPayload()', () => {
extensions: [{mock: 'extension-payload'}, {mock: 'extension-payload'}, {mock: 'extension-payload'}],
})
})

test('uses the admin-web preflight URL for local development stores', async () => {
vi.spyOn(payload, 'getUIExtensionPayload').mockResolvedValue({
mock: 'extension-payload',
} as unknown as UIExtensionPayload)
vi.mocked(normalizeStoreFqdn).mockReturnValue('mock-store-fqdn.my.shop.dev')
vi.mocked(storeAdminUrl).mockReturnValue('admin.shop.dev/store/mock-store-fqdn')

const options = {
apiKey: 'mock-api-key',
appName: 'mock-app-name',
url: 'https://mock-url.com',
websocketURL: 'wss://mock-websocket-url.com',
extensions: [{}],
storeFqdn: 'mock-store-fqdn.my.shop.dev',
manifestVersion: '3',
} as unknown as ExtensionsPayloadStoreOptions

const rawPayload = await getExtensionsPayloadStoreRawPayload(options, 'mock-bundle-path')

expect(rawPayload.app.url).toBe(
'https://admin.shop.dev/store/mock-store-fqdn/extensions-dev/preview?client_id=mock-api-key',
)
})
})

describe('ExtensionsPayloadStore()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment'
import {isStorefrontPasswordProtected} from '@shopify/theme'
import {fetchTheme} from '@shopify/cli-kit/node/themes/api'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {adminFqdn} from '@shopify/cli-kit/node/context/fqdn'
import {adminFqdn, normalizeStoreFqdn, storeAdminUrl} from '@shopify/cli-kit/node/context/fqdn'

vi.mock('../../context/identifiers.js')
vi.mock('@shopify/cli-kit/node/session.js')
Expand All @@ -50,6 +50,8 @@ vi.mock('@shopify/cli-kit/node/context/fqdn', async (importOriginal) => {
return {
...original,
adminFqdn: vi.fn(),
normalizeStoreFqdn: vi.fn(original.normalizeStoreFqdn),
storeAdminUrl: vi.fn(original.storeAdminUrl),
}
})

Expand Down Expand Up @@ -312,6 +314,69 @@ describe('setup-dev-processes', () => {
})
})

test('uses the admin-web preflight URL for local development stores', async () => {
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient()
const storeFqdn = 'test.my.shop.dev'
const storeId = '123456789'
const remoteAppUpdated = true
const graphiqlPort = 1234
const commandOptions: DevConfig['commandOptions'] = {
...appContextResult,
directory: '',
update: false,
commandConfig: new Config({root: ''}),
skipDependenciesInstallation: false,
tunnel: {mode: 'auto'},
}
const network: DevConfig['network'] = {
proxyUrl: 'https://example.com/proxy',
proxyPort: 444,
backendPort: 111,
frontendPort: 222,
currentUrls: {
applicationUrl: 'https://example.com/application',
redirectUrlWhitelist: ['https://example.com/redirect'],
},
}
const localApp = testAppWithConfig()
vi.spyOn(loader, 'reloadApp').mockResolvedValue(localApp)
vi.mocked(normalizeStoreFqdn).mockReturnValue('test.my.shop.dev')
vi.mocked(storeAdminUrl).mockReturnValue('admin.shop.dev/store/test')

const remoteApp: DevConfig['remoteApp'] = {
apiKey: 'api-key',
apiSecretKeys: [{secret: 'api-secret'}],
id: '1234',
title: 'App',
organizationId: '5678',
grantedScopes: [],
flags: [],
developerPlatformClient,
}

const res = await setupDevProcesses({
localApp,
commandOptions,
network,
remoteApp,
remoteAppUpdated,
storeFqdn,
storeId,
developerPlatformClient,
partnerUrlsUpdated: true,
graphiqlPort,
graphiqlKey: 'somekey',
})

expect(res.previewUrl).toBe('https://admin.shop.dev/store/test/extensions-dev/preview?client_id=api-key')
expect(res.processes[1]).toMatchObject({
type: 'graphiql',
options: {
appUrl: 'https://admin.shop.dev/store/test/extensions-dev/preview?client_id=api-key',
},
})
})

test('process list includes dev-session when useDevSession is true', async () => {
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({supportsDevSessions: true})
const storeFqdn = 'store.myshopify.io'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {isTruthy} from '@shopify/cli-kit/node/context/utilities'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment'
import {outputInfo} from '@shopify/cli-kit/node/output'
import {adminFqdn} from '@shopify/cli-kit/node/context/fqdn'
import {adminFqdn, normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'

interface ProxyServerProcess extends BaseProcess<{
port: number
Expand Down Expand Up @@ -108,8 +108,10 @@ export async function setupDevProcesses({

// appPreviewUrl is the direct app URL (used by GraphiQL and dev session fallback)
// previewURL is what's shown to the user (may be dev console for 1P devs)
const isLocalStore = normalizeStoreFqdn(storeFqdn).endsWith('.my.shop.dev')

let appPreviewUrl: string
if (is1PDev) {
if (is1PDev || isLocalStore) {
appPreviewUrl = buildAppURLForWeb(storeFqdn, apiKey)
} else {
const adminDomain = await adminFqdn()
Expand Down
29 changes: 29 additions & 0 deletions packages/app/src/cli/utilities/app/app-url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {buildAppURLForWeb} from './app-url.js'
import {describe, expect, test, vi} from 'vitest'
import {normalizeStoreFqdn, storeAdminUrl} from '@shopify/cli-kit/node/context/fqdn'

vi.mock('@shopify/cli-kit/node/context/fqdn', async (importOriginal) => {
const original = await importOriginal<typeof import('@shopify/cli-kit/node/context/fqdn')>()
return {
...original,
normalizeStoreFqdn: vi.fn(original.normalizeStoreFqdn),
storeAdminUrl: vi.fn(original.storeAdminUrl),
}
})

describe('buildAppURLForWeb', () => {
test('builds the admin-web preflight preview URL for production stores', () => {
const url = buildAppURLForWeb('my-store.myshopify.com', 'api-key')

expect(url).toBe('https://admin.shopify.com/store/my-store/extensions-dev/preview?client_id=api-key')
})

test('uses the same admin-web path in local development with the admin.shop.dev host', () => {
vi.mocked(normalizeStoreFqdn).mockReturnValue('my-store.my.shop.dev')
vi.mocked(storeAdminUrl).mockReturnValue('admin.shop.dev/store/my-store')

const url = buildAppURLForWeb('my-store.my.shop.dev', 'api-key')

expect(url).toBe('https://admin.shop.dev/store/my-store/extensions-dev/preview?client_id=api-key')
})
})
8 changes: 6 additions & 2 deletions packages/app/src/cli/utilities/app/app-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import {normalizeStoreFqdn, storeAdminUrl} from '@shopify/cli-kit/node/context/f

export function buildAppURLForWeb(storeFqdn: string, apiKey: string) {
const normalizedFQDN = normalizeStoreFqdn(storeFqdn)
const adminUrl = storeAdminUrl(normalizedFQDN)
return `https://${adminUrl}/admin/oauth/redirect_from_cli?client_id=${apiKey}`
const storeName = normalizedFQDN.split('.')[0]
const localAdminUrl = storeAdminUrl(normalizedFQDN)
const adminDomain = localAdminUrl === normalizedFQDN ? 'admin.shopify.com' : localAdminUrl.split('/')[0]
const searchParams = new URLSearchParams({client_id: apiKey})

return `https://${adminDomain}/store/${storeName}/extensions-dev/preview?${searchParams.toString()}`
}

export function buildAppURLForAdmin(storeFqdn: string, apiKey: string, adminDomain: string) {
Expand Down
Loading