From 0feadff45f1a101166df9c17597ae3155e112976 Mon Sep 17 00:00:00 2001 From: Ariel Caplan Date: Tue, 24 Dec 2024 12:21:01 +0200 Subject: [PATCH] Enable search for dev stores --- .../generated/list_app_dev_stores.ts | 28 ++++++++- .../queries/list_app_dev_stores.graphql | 31 +++++----- .../app/src/cli/models/app/app.test-data.ts | 2 +- packages/app/src/cli/prompts/dev.test.ts | 12 ++-- packages/app/src/cli/prompts/dev.ts | 38 +++++++++--- .../src/cli/services/dev/select-store.test.ts | 62 ++++++++++++++----- .../app/src/cli/services/dev/select-store.ts | 18 +++--- .../src/cli/services/store-context.test.ts | 18 ++++-- .../utilities/developer-platform-client.ts | 2 +- .../app-management-client.ts | 8 ++- .../partners-client.ts | 7 ++- 11 files changed, 168 insertions(+), 58 deletions(-) diff --git a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/list_app_dev_stores.ts b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/list_app_dev_stores.ts index 860309bd4e8..84de2af4e59 100644 --- a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/list_app_dev_stores.ts +++ b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/list_app_dev_stores.ts @@ -3,7 +3,9 @@ import * as Types from './types.js' import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core' -export type ListAppDevStoresQueryVariables = Types.Exact<{[key: string]: never}> +export type ListAppDevStoresQueryVariables = Types.Exact<{ + searchTerm?: Types.InputMaybe +}> export type ListAppDevStoresQuery = { organization?: { @@ -20,6 +22,7 @@ export type ListAppDevStoresQuery = { shortName?: string | null } }[] + pageInfo: {hasNextPage: boolean} } | null } | null } @@ -31,6 +34,13 @@ export const ListAppDevStores = { kind: 'OperationDefinition', operation: 'query', name: {kind: 'Name', value: 'ListAppDevStores'}, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: {kind: 'Variable', name: {kind: 'Name', value: 'searchTerm'}}, + type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}, + }, + ], selectionSet: { kind: 'SelectionSet', selections: [ @@ -70,6 +80,11 @@ export const ListAppDevStores = { ], }, }, + { + kind: 'Argument', + name: {kind: 'Name', value: 'search'}, + value: {kind: 'Variable', name: {kind: 'Name', value: 'searchTerm'}}, + }, ], selectionSet: { kind: 'SelectionSet', @@ -100,6 +115,17 @@ export const ListAppDevStores = { ], }, }, + { + kind: 'Field', + name: {kind: 'Name', value: 'pageInfo'}, + selectionSet: { + kind: 'SelectionSet', + selections: [ + {kind: 'Field', name: {kind: 'Name', value: 'hasNextPage'}}, + {kind: 'Field', name: {kind: 'Name', value: '__typename'}}, + ], + }, + }, {kind: 'Field', name: {kind: 'Name', value: '__typename'}}, ], }, diff --git a/packages/app/src/cli/api/graphql/business-platform-organizations/queries/list_app_dev_stores.graphql b/packages/app/src/cli/api/graphql/business-platform-organizations/queries/list_app_dev_stores.graphql index c667c18b191..82368b4b3ce 100644 --- a/packages/app/src/cli/api/graphql/business-platform-organizations/queries/list_app_dev_stores.graphql +++ b/packages/app/src/cli/api/graphql/business-platform-organizations/queries/list_app_dev_stores.graphql @@ -1,18 +1,21 @@ - query ListAppDevStores { - organization { - id - name - accessibleShops(filters: {field: STORE_TYPE, operator: EQUALS, value: "app_development"}) { - edges { - node { - id - externalId - name - storeType - primaryDomain - shortName - } +query ListAppDevStores($searchTerm: String) { + organization { + id + name + accessibleShops(filters: {field: STORE_TYPE, operator: EQUALS, value: "app_development"}, search: $searchTerm) { + edges { + node { + id + externalId + name + storeType + primaryDomain + shortName } } + pageInfo { + hasNextPage + } } } +} diff --git a/packages/app/src/cli/models/app/app.test-data.ts b/packages/app/src/cli/models/app/app.test-data.ts index 2ea50a349d8..69bd8024c8a 100644 --- a/packages/app/src/cli/models/app/app.test-data.ts +++ b/packages/app/src/cli/models/app/app.test-data.ts @@ -1329,7 +1329,7 @@ export function testDeveloperPlatformClient(stubs: Partial Promise.resolve(testOrganizationApp()), - devStoresForOrg: (_organizationId: string) => Promise.resolve([]), + devStoresForOrg: (_organizationId: string) => Promise.resolve({stores: [], hasMorePages: false}), storeByDomain: (_orgId: string, _shopDomain: string) => Promise.resolve({organizations: {nodes: []}}), appExtensionRegistrations: (_app: MinimalAppIdentifiers) => Promise.resolve(emptyAppExtensionRegistrations), appVersions: (_app: MinimalAppIdentifiers) => Promise.resolve(emptyAppVersions), diff --git a/packages/app/src/cli/prompts/dev.test.ts b/packages/app/src/cli/prompts/dev.test.ts index 8eb2dcf3b48..117c8740be0 100644 --- a/packages/app/src/cli/prompts/dev.test.ts +++ b/packages/app/src/cli/prompts/dev.test.ts @@ -141,7 +141,7 @@ describe('selectStore', () => { const stores: OrganizationStore[] = [] // When - const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt) + const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt}) // Then expect(got).toEqual(undefined) @@ -154,7 +154,7 @@ describe('selectStore', () => { const outputMock = mockAndCaptureOutput() // When - const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt) + const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt}) // Then expect(got).toEqual(STORE1) @@ -168,7 +168,7 @@ describe('selectStore', () => { vi.mocked(renderAutocompletePrompt).mockResolvedValue('2') // When - const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt) + const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt}) // Then expect(got).toEqual(STORE2) @@ -178,6 +178,8 @@ describe('selectStore', () => { {label: 'store1', value: '1'}, {label: 'store2', value: '2'}, ], + hasMorePages: false, + search: expect.any(Function), }) }) @@ -187,7 +189,7 @@ describe('selectStore', () => { vi.mocked(renderAutocompletePrompt).mockResolvedValue('2') // When - const got = await selectStorePrompt(stores, true) + const got = await selectStorePrompt({stores, showDomainOnPrompt: true}) // Then expect(got).toEqual(STORE2) @@ -197,6 +199,8 @@ describe('selectStore', () => { {label: 'store1 (domain1)', value: '1'}, {label: 'store2 (domain2)', value: '2'}, ], + hasMorePages: false, + search: expect.any(Function), }) }) }) diff --git a/packages/app/src/cli/prompts/dev.ts b/packages/app/src/cli/prompts/dev.ts index 2d1ae644fe3..51d354588f2 100644 --- a/packages/app/src/cli/prompts/dev.ts +++ b/packages/app/src/cli/prompts/dev.ts @@ -2,6 +2,7 @@ import {Organization, MinimalOrganizationApp, OrganizationStore, MinimalAppIdentifiers} from '../models/organization.js' import {getTomls} from '../utilities/app/config/getTomls.js' import {setCachedCommandTomlMap} from '../services/local-storage.js' +import {Paginateable} from '../utilities/developer-platform-client.js' import {renderAutocompletePrompt, renderConfirmationPrompt, renderTextPrompt} from '@shopify/cli-kit/node/ui' import {outputCompleted} from '@shopify/cli-kit/node/output' @@ -58,27 +59,50 @@ export async function selectAppPrompt( return currentAppChoices.find((app) => app.apiKey === apiKey)! } -export async function selectStorePrompt( - stores: OrganizationStore[], - showDomainOnPrompt: boolean, -): Promise { +interface SelectStorePromptOptions { + onSearchForStoresByName?: (term: string) => Promise> + stores: OrganizationStore[] + hasMorePages?: boolean + showDomainOnPrompt: boolean +} + +export async function selectStorePrompt({ + stores, + hasMorePages = false, + onSearchForStoresByName = (_term: string) => Promise.resolve({stores, hasMorePages}), + showDomainOnPrompt = true, +}: SelectStorePromptOptions): Promise { if (stores.length === 0) return undefined if (stores.length === 1) { outputCompleted(`Using your default dev store, ${stores[0]!.shopName}, to preview your project.`) return stores[0] } - const storeList = stores.map((store) => { + const toAnswer = (store: OrganizationStore) => { let label = store.shopName if (showDomainOnPrompt && store.shopDomain) { label = `${store.shopName} (${store.shopDomain})` } return {label, value: store.shopId} - }) + } + + let currentStoreChoices = stores const id = await renderAutocompletePrompt({ message: 'Which store would you like to use to view your project?', - choices: storeList, + choices: currentStoreChoices.map(toAnswer), + hasMorePages, + search: async (term) => { + const result = await onSearchForStoresByName(term) + currentStoreChoices = result.stores + + return { + data: currentStoreChoices.map(toAnswer), + meta: { + hasNextPage: result.hasMorePages, + }, + } + }, }) return stores.find((store) => store.shopId === id) } diff --git a/packages/app/src/cli/services/dev/select-store.test.ts b/packages/app/src/cli/services/dev/select-store.test.ts index 3befa69d7f4..b1fdbf467e8 100644 --- a/packages/app/src/cli/services/dev/select-store.test.ts +++ b/packages/app/src/cli/services/dev/select-store.test.ts @@ -60,11 +60,16 @@ describe('selectStore', async () => { vi.mocked(selectStorePrompt).mockResolvedValueOnce(STORE1) // When - const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient()) + const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient()) // Then expect(got).toEqual(STORE1) - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: defaultShowDomainOnPrompt, + }), + ) }) test('selectStorePrompt is called with showDomainOnPrompt = true if clientName is app-management', async () => { @@ -73,11 +78,16 @@ describe('selectStore', async () => { const developerPlatformClient = testDeveloperPlatformClient({clientName: ClientName.AppManagement}) // When - const got = await selectStore([STORE1, STORE2], ORG1, developerPlatformClient) + const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, developerPlatformClient) // Then expect(got).toEqual(STORE1) - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], true) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: true, + }), + ) }) test('prompts user to convert store to non-transferable if selection is invalid', async () => { @@ -86,11 +96,16 @@ describe('selectStore', async () => { vi.mocked(confirmConversionToTransferDisabledStorePrompt).mockResolvedValueOnce(true) // When - const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient()) + const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient()) // Then expect(got).toEqual(STORE2) - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: defaultShowDomainOnPrompt, + }), + ) expect(confirmConversionToTransferDisabledStorePrompt).toHaveBeenCalled() }) @@ -101,11 +116,16 @@ describe('selectStore', async () => { vi.mocked(confirmConversionToTransferDisabledStorePrompt).mockResolvedValueOnce(false) // When - const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient()) + const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient()) // Then expect(got).toEqual(STORE1) - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: defaultShowDomainOnPrompt, + }), + ) expect(confirmConversionToTransferDisabledStorePrompt).toHaveBeenCalled() }) @@ -117,12 +137,17 @@ describe('selectStore', async () => { const developerPlatformClient = testDeveloperPlatformClient() // When - const got = await selectStore([STORE1, STORE2], ORG1, developerPlatformClient) + const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, developerPlatformClient) // Then expect(got).toEqual(STORE2) expect(developerPlatformClient.convertToTransferDisabledStore).not.toHaveBeenCalled() - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: defaultShowDomainOnPrompt, + }), + ) }) test('throws if store is non convertible', async () => { @@ -130,7 +155,11 @@ describe('selectStore', async () => { vi.mocked(selectStorePrompt).mockResolvedValueOnce(STORE3) // When - const got = selectStore([STORE1, STORE2, STORE3], ORG1, testDeveloperPlatformClient()) + const got = selectStore( + {stores: [STORE1, STORE2, STORE3], hasMorePages: false}, + ORG1, + testDeveloperPlatformClient(), + ) // Then await expect(got).rejects.toThrow('The store you specified (domain3) is not a dev store') @@ -142,11 +171,16 @@ describe('selectStore', async () => { vi.mocked(reloadStoreListPrompt).mockResolvedValue(false) // When - const got = () => selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient()) + const got = () => selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient()) // Then await expect(got).rejects.toThrowError() - expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt) + expect(selectStorePrompt).toHaveBeenCalledWith( + expect.objectContaining({ + stores: [STORE1, STORE2], + showDomainOnPrompt: defaultShowDomainOnPrompt, + }), + ) }) test('prompts user to create & reload, fetches 10 times and tries again if reload is true', async () => { @@ -157,7 +191,7 @@ describe('selectStore', async () => { const developerPlatformClient = testDeveloperPlatformClient() // When - const got = selectStore([], ORG1, developerPlatformClient) + const got = selectStore({stores: [], hasMorePages: false}, ORG1, developerPlatformClient) // Then await expect(got).rejects.toThrow() diff --git a/packages/app/src/cli/services/dev/select-store.ts b/packages/app/src/cli/services/dev/select-store.ts index 067c2935ebc..2654bf88450 100644 --- a/packages/app/src/cli/services/dev/select-store.ts +++ b/packages/app/src/cli/services/dev/select-store.ts @@ -8,7 +8,7 @@ import { ConvertDevToTransferDisabledSchema, ConvertDevToTransferDisabledStoreVariables, } from '../../api/graphql/convert_dev_to_transfer_disabled_store.js' -import {ClientName, DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' +import {ClientName, DeveloperPlatformClient, Paginateable} from '../../utilities/developer-platform-client.js' import {sleep} from '@shopify/cli-kit/node/system' import {renderInfo, renderTasks} from '@shopify/cli-kit/node/ui' import {firstPartyDev} from '@shopify/cli-kit/node/context/local' @@ -26,14 +26,18 @@ import {outputSuccess} from '@shopify/cli-kit/node/output' * @returns The selected store */ export async function selectStore( - stores: OrganizationStore[], + storesSearch: Paginateable<{stores: OrganizationStore[]}>, org: Organization, developerPlatformClient: DeveloperPlatformClient, ): Promise { const showDomainOnPrompt = developerPlatformClient.clientName === ClientName.AppManagement // If no stores, guide the developer through creating one // Then, with a store selected, make sure its transfer-disabled, prompting to convert if needed - let store = await selectStorePrompt(stores, showDomainOnPrompt) + let store = await selectStorePrompt({ + onSearchForStoresByName: async (term: string) => developerPlatformClient.devStoresForOrg(org.id, term), + ...storesSearch, + showDomainOnPrompt, + }) if (!store) { renderInfo({ body: await developerPlatformClient.getCreateDevStoreLink(org.id), @@ -45,8 +49,8 @@ export async function selectStore( throw new CancelExecution() } - const data = await waitForCreatedStore(org.id, developerPlatformClient) - store = await selectStore(data, org, developerPlatformClient) + const stores = await waitForCreatedStore(org.id, developerPlatformClient) + store = await selectStore({stores, hasMorePages: false}, org, developerPlatformClient) } let storeIsValid = await convertToTransferDisabledStoreIfNeeded( @@ -57,7 +61,7 @@ export async function selectStore( ) while (!storeIsValid) { // eslint-disable-next-line no-await-in-loop - store = await selectStorePrompt(stores, showDomainOnPrompt) + store = await selectStorePrompt({stores: [store], hasMorePages: false, showDomainOnPrompt}) if (!store) { throw new CancelExecution() } @@ -88,7 +92,7 @@ async function waitForCreatedStore( task: async () => { for (let i = 0; i < retries; i++) { // eslint-disable-next-line no-await-in-loop - const stores = await developerPlatformClient.devStoresForOrg(orgId) + const {stores} = await developerPlatformClient.devStoresForOrg(orgId) if (stores.length > 0) { data = stores return diff --git a/packages/app/src/cli/services/store-context.test.ts b/packages/app/src/cli/services/store-context.test.ts index 85cce5c96ce..4061288d746 100644 --- a/packages/app/src/cli/services/store-context.test.ts +++ b/packages/app/src/cli/services/store-context.test.ts @@ -76,7 +76,7 @@ describe('storeContext', () => { const appWithoutCachedStore = testAppLinked() const allStores = [mockStore, {...mockStore, shopId: 'store2', shopDomain: 'another-store.myshopify.com'}] - vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue(allStores) + vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue({stores: allStores, hasMorePages: false}) vi.mocked(selectStore).mockResolvedValue(mockStore) const updatedAppContextResult = {...appContextResult, app: appWithoutCachedStore} @@ -86,14 +86,18 @@ describe('storeContext', () => { }) expect(mockDeveloperPlatformClient.devStoresForOrg).toHaveBeenCalledWith(mockOrganization.id) - expect(selectStore).toHaveBeenCalledWith(allStores, mockOrganization, mockDeveloperPlatformClient) + expect(selectStore).toHaveBeenCalledWith( + {stores: allStores, hasMorePages: false}, + mockOrganization, + mockDeveloperPlatformClient, + ) expect(result).toEqual(mockStore) }) test('fetches and selects store when forceReselectStore is true', async () => { const allStores = [mockStore, {...mockStore, shopId: 'store2', shopDomain: 'another-store.myshopify.com'}] - vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue(allStores) + vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue({stores: allStores, hasMorePages: false}) vi.mocked(selectStore).mockResolvedValue(mockStore) const result = await storeContext({ @@ -102,7 +106,11 @@ describe('storeContext', () => { }) expect(mockDeveloperPlatformClient.devStoresForOrg).toHaveBeenCalledWith(mockOrganization.id) - expect(selectStore).toHaveBeenCalledWith(allStores, mockOrganization, mockDeveloperPlatformClient) + expect(selectStore).toHaveBeenCalledWith( + {stores: allStores, hasMorePages: false}, + mockOrganization, + mockDeveloperPlatformClient, + ) expect(result).toEqual(mockStore) }) @@ -115,7 +123,7 @@ describe('storeContext', () => { test('throws an error when selectStore fails', async () => { const appWithoutCachedStore = testAppLinked() - vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue([]) + vi.mocked(mockDeveloperPlatformClient.devStoresForOrg).mockResolvedValue({stores: [], hasMorePages: false}) vi.mocked(selectStore).mockRejectedValue(new Error('No stores available')) const updatedAppContextResult = {...appContextResult, app: appWithoutCachedStore} diff --git a/packages/app/src/cli/utilities/developer-platform-client.ts b/packages/app/src/cli/utilities/developer-platform-client.ts index 7a8673be6c4..c81a49547f8 100644 --- a/packages/app/src/cli/utilities/developer-platform-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client.ts @@ -222,7 +222,7 @@ export interface DeveloperPlatformClient { specifications: (app: MinimalAppIdentifiers) => Promise templateSpecifications: (app: MinimalAppIdentifiers) => Promise createApp: (org: Organization, name: string, options?: CreateAppOptions) => Promise - devStoresForOrg: (orgId: string) => Promise + devStoresForOrg: (orgId: string, searchTerm?: string) => Promise> storeByDomain: (orgId: string, shopDomain: string) => Promise appExtensionRegistrations: ( app: MinimalAppIdentifiers, diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index d3d782ba301..98e77e8493c 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -397,11 +397,12 @@ export class AppManagementClient implements DeveloperPlatformClient { // we are returning OrganizationStore type here because we want to keep types consistent btwn // partners-client and app-management-client. Since we need transferDisabled and convertableToPartnerTest values // from the Partners OrganizationStore schema, we will return this type for now - async devStoresForOrg(orgId: string): Promise { + async devStoresForOrg(orgId: string, searchTerm?: string): Promise> { const storesResult = await businessPlatformOrganizationsRequestDoc( ListAppDevStores, await this.businessPlatformToken(), orgId, + {searchTerm}, ) const organization = storesResult.organization @@ -410,7 +411,10 @@ export class AppManagementClient implements DeveloperPlatformClient { } const shopArray = organization.accessibleShops?.edges.map((value) => value.node) ?? [] - return mapBusinessPlatformStoresToOrganizationStores(shopArray) + return { + stores: mapBusinessPlatformStoresToOrganizationStores(shopArray), + hasMorePages: storesResult.organization?.accessibleShops?.pageInfo.hasNextPage ?? false, + } } async appExtensionRegistrations( diff --git a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts index 4255903a0d7..33041bdc435 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts @@ -358,10 +358,13 @@ export class PartnersClient implements DeveloperPlatformClient { return {...result.appCreate.app, organizationId: org.id, newApp: true, flags, developerPlatformClient: this} } - async devStoresForOrg(orgId: string): Promise { + async devStoresForOrg(orgId: string): Promise> { const variables: DevStoresByOrgQueryVariables = {id: orgId} const result: DevStoresByOrgQuery = await this.requestDoc(DevStoresByOrg, variables) - return result.organizations.nodes![0]!.stores.nodes as OrganizationStore[] + return { + stores: result.organizations.nodes![0]!.stores.nodes as OrganizationStore[], + hasMorePages: false, + } } async appExtensionRegistrations(