Skip to content
Merged
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
31 changes: 31 additions & 0 deletions packages/store/src/cli/services/store/info/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,42 @@ describe('getStoreInfo', () => {
subdomain: SHOP,
accessUrl: 'https://app.shopify.com/auth/preview-store?token=fresh-access-token',
saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token',
authScopes: [],
})
// The admin URL doesn't resolve for an unclaimed preview store, so it's deliberately omitted.
expect(result.adminUrl).toBeUndefined()
})

test('surfaces cached Admin API scopes for locally stored preview stores', async () => {
vi.mocked(getCurrentStoredStoreAppSession).mockReturnValueOnce({
store: SHOP,
clientId: STORE_AUTH_APP_CLIENT_ID,
userId: 'preview:placeholder-uuid',
accessToken: 'shpat_preview_token',
scopes: ['read_themes', 'write_themes'],
acquiredAt: '2026-06-08T12:00:00.000Z',
kind: 'preview',
preview: {
placeholderAccountUuid: 'placeholder-uuid',
shopId: '123',
name: 'Lavender Candles',
createdAt: '2026-06-08T12:00:00.000Z',
accessUrl: 'https://app.shopify.com/auth/preview-store?token=stale-access-token',
},
})
vi.mocked(claimPreviewStore).mockResolvedValueOnce({
claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token',
})
vi.mocked(getPreviewStore).mockResolvedValueOnce({
shop: {id: '123', name: 'Lavender Candles', domain: SHOP},
accessUrl: 'https://app.shopify.com/auth/preview-store?token=fresh-access-token',
})

const result = await getStoreInfo({store: SHOP})

expect(result.authScopes).toEqual(['read_themes', 'write_themes'])
})

test('prefers BP when store auth exists and BP can resolve the store', async () => {
mockStoredStoreAuth()

Expand Down
4 changes: 3 additions & 1 deletion packages/store/src/cli/services/store/info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ function buildPreviewStoreResult(args: {
saveUrl: previewStoreUrls.saveUrl,
}

return {...compact(fields), subdomain: store} as StoreInfoResult
// `authScopes` is always present for preview stores (even when empty) so consumers can rely on the
// key to learn which Admin API scopes are preapproved. There's no way to grant more scopes later.
return {...compact(fields), subdomain: store, authScopes: previewSession.scopes} as StoreInfoResult
}

// The BP `ShopifyShopID` scalar is the bare numeric id; the admin GID is derived locally.
Expand Down
7 changes: 7 additions & 0 deletions packages/store/src/cli/services/store/info/result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ describe('renderStoreInfoResult', () => {
expect(renderInfo).not.toHaveBeenCalled()
})

test('includes authScopes in the JSON output when present', () => {
renderStoreInfoResult(baseResult({authScopes: ['read_themes', 'write_themes']}), 'json')

const payload = vi.mocked(outputResult).mock.calls[0]?.[0] as string
expect(JSON.parse(payload).authScopes).toEqual(['read_themes', 'write_themes'])
})

test('renders a Store details section in text format', () => {
renderStoreInfoResult(baseResult(), 'text')

Expand Down
4 changes: 4 additions & 0 deletions packages/store/src/cli/services/store/info/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface StoreInfoResult {
adminUrl?: string
accessUrl?: string
saveUrl?: string
// Preapproved Admin API access scopes for the store (currently only preview stores, which
// cache the scopes granted at creation time). Preview stores aren't a logged-in experience, so
// there's no way to grant additional scopes later.
authScopes?: string[]
}

/**
Expand Down
Loading