From 39273da9f14048ef2e917ccd0a06320f7d160df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Thu, 16 Jul 2026 15:27:48 +0200 Subject: [PATCH 1/2] Extract MigrationDeveloperPlatformClient interface First step of the PartnersClient cleanup. The legacy extension migrations (Flow, app module, UI extension) are the only production use of PartnersClient. Extract a narrow MigrationDeveloperPlatformClient interface and retype the migration call sites to it, so PartnersClient no longer needs the full DeveloperPlatformClient surface. AppManagementClient drops its three "Not implemented" migrate stubs. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../app/src/cli/models/app/app.test-data.ts | 18 +++++++++++----- .../context/deploy-app-version-migrations.ts | 4 ++-- .../app/src/cli/services/dev/fetch.test.ts | 4 ++-- .../cli/services/dev/migrate-app-module.ts | 6 +++--- .../services/dev/migrate-flow-extension.ts | 6 +++--- .../services/dev/migrate-to-ui-extension.ts | 6 +++--- .../utilities/developer-platform-client.ts | 15 ++++++++++--- .../app-management-client.ts | 21 ------------------- 8 files changed, 38 insertions(+), 42 deletions(-) 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 0ac71e5c4c9..ce01625a337 100644 --- a/packages/app/src/cli/models/app/app.test-data.ts +++ b/packages/app/src/cli/models/app/app.test-data.ts @@ -34,6 +34,7 @@ import { ClientName, CreateAppOptions, DeveloperPlatformClient, + MigrationDeveloperPlatformClient, DevSessionCreateOptions, DevSessionDeleteOptions, DevSessionUpdateOptions, @@ -1300,8 +1301,12 @@ const appLogsSubscribeResponse: AppLogsSubscribeResponse = { }, } -export function testDeveloperPlatformClient(stubs: Partial = {}): DeveloperPlatformClient { - const clientStub: DeveloperPlatformClient = { +type TestDeveloperPlatformClient = DeveloperPlatformClient & MigrationDeveloperPlatformClient + +export function testDeveloperPlatformClient( + stubs: Partial = {}, +): TestDeveloperPlatformClient { + const clientStub: TestDeveloperPlatformClient = { clientName: ClientName.AppManagement, webUiName: 'Test Dashboard', organizationSource: OrganizationSource.BusinessPlatform, @@ -1374,15 +1379,18 @@ export function testDeveloperPlatformClient(stubs: Partial = clientStub + const retVal: Partial = clientStub for (const [key, value] of Object.entries(clientStub)) { if (typeof value === 'function') { retVal[ - key as keyof Omit + key as keyof Omit< + TestDeveloperPlatformClient, + 'clientName' | 'webUiName' | 'organizationSource' | 'bundleFormat' + > ] = vi.fn().mockImplementation(value) } } - return retVal as DeveloperPlatformClient + return retVal as TestDeveloperPlatformClient } export const testPartnersServiceSession: Session = { diff --git a/packages/app/src/cli/services/context/deploy-app-version-migrations.ts b/packages/app/src/cli/services/context/deploy-app-version-migrations.ts index 9e862120cd4..42f2cadfab2 100644 --- a/packages/app/src/cli/services/context/deploy-app-version-migrations.ts +++ b/packages/app/src/cli/services/context/deploy-app-version-migrations.ts @@ -10,7 +10,7 @@ import { migrateAppModules, UIModulesMap, } from '../dev/migrate-app-module.js' -import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' +import {MigrationDeveloperPlatformClient} from '../../utilities/developer-platform-client.js' import {PartnersClient} from '../../utilities/developer-platform-client/partners-client.js' import {AbortSilentError} from '@shopify/cli-kit/node/error' @@ -94,7 +94,7 @@ function migrationGroup(typesMap: {[key: string]: string[]}, type: string) { extensionsToMigrate: Parameters[0]['extensionsToMigrate'] appId: string remoteExtensions: RemoteSource[] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) => migrateAppModules({...options, type}), } } diff --git a/packages/app/src/cli/services/dev/fetch.test.ts b/packages/app/src/cli/services/dev/fetch.test.ts index dee6c3f227a..29d8cbc81f4 100644 --- a/packages/app/src/cli/services/dev/fetch.test.ts +++ b/packages/app/src/cli/services/dev/fetch.test.ts @@ -45,7 +45,7 @@ describe('fetchOrganizations', async () => { // Given const appManagementClient: AppManagementClient = testDeveloperPlatformClient({ organizations: () => Promise.resolve([ORG2]), - }) as AppManagementClient + }) as unknown as AppManagementClient vi.mocked(AppManagementClient.getInstance).mockReturnValue(appManagementClient) // When @@ -60,7 +60,7 @@ describe('fetchOrganizations', async () => { // Given const appManagementClient: AppManagementClient = testDeveloperPlatformClient({ organizations: () => Promise.resolve([]), - }) as AppManagementClient + }) as unknown as AppManagementClient vi.mocked(AppManagementClient.getInstance).mockReturnValue(appManagementClient) // When diff --git a/packages/app/src/cli/services/dev/migrate-app-module.ts b/packages/app/src/cli/services/dev/migrate-app-module.ts index c626040a0c7..da9b36decda 100644 --- a/packages/app/src/cli/services/dev/migrate-app-module.ts +++ b/packages/app/src/cli/services/dev/migrate-app-module.ts @@ -1,6 +1,6 @@ import {LocalSource, RemoteSource} from '../context/identifiers.js' import {ExtensionUuidsByLocalIdentifier} from '../../models/app/identifiers.js' -import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' +import {MigrationDeveloperPlatformClient} from '../../utilities/developer-platform-client.js' import {MigrateAppModuleSchema, MigrateAppModuleVariables} from '../../api/graphql/extension_migrate_app_module.js' import {MAX_EXTENSION_HANDLE_LENGTH} from '../../models/extensions/schemas.js' import {AbortError} from '@shopify/cli-kit/node/error' @@ -91,7 +91,7 @@ export async function migrateAppModules(options: { appId: string type: string remoteExtensions: RemoteSource[] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {extensionsToMigrate, appId, type, remoteExtensions, migrationClient} = options @@ -123,7 +123,7 @@ async function migrateAppModule(options: { registrationId: MigrateAppModuleVariables['registrationId'] registrationUuid: MigrateAppModuleVariables['registrationUuid'] type: MigrateAppModuleVariables['type'] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {apiKey, registrationId, registrationUuid, type, migrationClient} = options diff --git a/packages/app/src/cli/services/dev/migrate-flow-extension.ts b/packages/app/src/cli/services/dev/migrate-flow-extension.ts index b5241e8f48f..34f30634750 100644 --- a/packages/app/src/cli/services/dev/migrate-flow-extension.ts +++ b/packages/app/src/cli/services/dev/migrate-flow-extension.ts @@ -4,14 +4,14 @@ import { MigrateFlowExtensionSchema, MigrateFlowExtensionVariables, } from '../../api/graphql/extension_migrate_flow_extension.js' -import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' +import {MigrationDeveloperPlatformClient} from '../../utilities/developer-platform-client.js' import {AbortError} from '@shopify/cli-kit/node/error' export async function migrateFlowExtensions(options: { extensionsToMigrate: LocalRemoteSource[] appId: string remoteExtensions: RemoteSource[] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {extensionsToMigrate, appId, remoteExtensions, migrationClient} = options @@ -46,7 +46,7 @@ async function migrateFlowExtension(options: { apiKey: MigrateFlowExtensionVariables['apiKey'] registrationId: MigrateFlowExtensionVariables['registrationId'] registrationUuid: MigrateFlowExtensionVariables['registrationUuid'] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {apiKey, registrationId, registrationUuid, migrationClient} = options diff --git a/packages/app/src/cli/services/dev/migrate-to-ui-extension.ts b/packages/app/src/cli/services/dev/migrate-to-ui-extension.ts index 92596967f88..f105684f71b 100644 --- a/packages/app/src/cli/services/dev/migrate-to-ui-extension.ts +++ b/packages/app/src/cli/services/dev/migrate-to-ui-extension.ts @@ -4,14 +4,14 @@ import { MigrateToUiExtensionVariables, } from '../../api/graphql/extension_migrate_to_ui_extension.js' import {RemoteSource} from '../context/identifiers.js' -import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' +import {MigrationDeveloperPlatformClient} from '../../utilities/developer-platform-client.js' import {AbortError} from '@shopify/cli-kit/node/error' export async function migrateExtensionsToUIExtension(options: { extensionsToMigrate: LocalRemoteSource[] appId: string remoteExtensions: RemoteSource[] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {extensionsToMigrate, appId, remoteExtensions, migrationClient} = options @@ -41,7 +41,7 @@ async function migrateExtensionToUIExtension(options: { apiKey: MigrateToUiExtensionVariables['apiKey'] registrationId: MigrateToUiExtensionVariables['registrationId'] registrationUuid: MigrateToUiExtensionVariables['registrationUuid'] - migrationClient: DeveloperPlatformClient + migrationClient: MigrationDeveloperPlatformClient }) { const {apiKey, registrationId, registrationUuid, migrationClient} = options diff --git a/packages/app/src/cli/utilities/developer-platform-client.ts b/packages/app/src/cli/utilities/developer-platform-client.ts index 674a2dfa904..f79054d3c9e 100644 --- a/packages/app/src/cli/utilities/developer-platform-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client.ts @@ -226,8 +226,6 @@ export interface DeveloperPlatformClient { sendSampleWebhook: (input: SendSampleWebhookVariables, organizationId: string) => Promise apiVersions: (organizationId: string) => Promise topics: (input: WebhookTopicsVariables, organizationId: string) => Promise - migrateFlowExtension: (input: MigrateFlowExtensionVariables) => Promise - migrateAppModule: (input: MigrateAppModuleVariables) => Promise updateURLs: (input: UpdateURLsVariables) => Promise currentAccountInfo: () => Promise targetSchemaDefinition: ( @@ -240,7 +238,6 @@ export interface DeveloperPlatformClient { apiKey: string, organizationId: string, ) => Promise - migrateToUiExtension: (input: MigrateToUiExtensionVariables) => Promise toExtensionGraphQLType: (input: string) => string subscribeToAppLogs: ( input: AppLogsSubscribeMutationVariables, @@ -254,6 +251,18 @@ export interface DeveloperPlatformClient { getCreateDevStoreLink: (org: Organization) => Promise } +/** + * The legacy extension migrations (Flow, app module, and UI extension) are only implemented by + * {@link PartnersClient}; the default {@link AppManagementClient} does not support them. Keeping + * these mutations on a dedicated interface lets the migration callers depend on just this surface + * rather than the full {@link DeveloperPlatformClient}. + */ +export interface MigrationDeveloperPlatformClient { + migrateFlowExtension: (input: MigrateFlowExtensionVariables) => Promise + migrateAppModule: (input: MigrateAppModuleVariables) => Promise + migrateToUiExtension: (input: MigrateToUiExtensionVariables) => Promise +} + const inProgressRefreshes = new WeakMap>() /** 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 fd006751360..737b73d7b91 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 @@ -53,18 +53,9 @@ import { } from '../../services/webhook/request-sample.js' import {PublicApiVersionsSchema} from '../../services/webhook/request-api-versions.js' import {WebhookTopicsSchema, WebhookTopicsVariables} from '../../services/webhook/request-topics.js' -import { - MigrateFlowExtensionSchema, - MigrateFlowExtensionVariables, -} from '../../api/graphql/extension_migrate_flow_extension.js' import {UpdateURLsSchema, UpdateURLsVariables} from '../../api/graphql/update_urls.js' import {CurrentAccountInfoQuery} from '../../api/graphql/partners/generated/current-account-info.js' import {ExtensionTemplate, ExtensionTemplatesResult} from '../../models/app/template.js' -import { - MigrateToUiExtensionVariables, - MigrateToUiExtensionSchema, -} from '../../api/graphql/extension_migrate_to_ui_extension.js' -import {MigrateAppModuleSchema, MigrateAppModuleVariables} from '../../api/graphql/extension_migrate_app_module.js' import {AppHomeSpecIdentifier} from '../../models/extensions/specifications/app_config_app_home.js' import {BrandingSpecIdentifier} from '../../models/extensions/specifications/app_config_branding.js' import {AppAccessSpecIdentifier} from '../../models/extensions/specifications/app_config_app_access.js' @@ -935,14 +926,6 @@ export class AppManagementClient implements DeveloperPlatformClient { } } - async migrateFlowExtension(_input: MigrateFlowExtensionVariables): Promise { - throw new BugError('Not implemented: migrateFlowExtension') - } - - async migrateAppModule(_input: MigrateAppModuleVariables): Promise { - throw new BugError('Not implemented: migrateAppModule') - } - async updateURLs(_input: UpdateURLsVariables): Promise { outputDebug('⚠️ updateURLs is not implemented') return {appUpdate: {userErrors: []}} @@ -997,10 +980,6 @@ export class AppManagementClient implements DeveloperPlatformClient { } } - async migrateToUiExtension(_input: MigrateToUiExtensionVariables): Promise { - throw new BugError('Not implemented: migrateToUiExtension') - } - toExtensionGraphQLType(input: string) { return input.toLowerCase() } From a30cffab3d82688b3197d0ac57eb7f80b38e8b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Thu, 16 Jul 2026 15:33:57 +0200 Subject: [PATCH 2/2] Narrow the token-refresh client dependency Second step of the PartnersClient cleanup. createUnauthorizedHandler only uses a small slice of a developer platform client (session and unsafeRefreshToken), but was typed against the whole DeveloperPlatformClient. Narrow it to the minimal Pick subset it consumes so a client that no longer implements the full interface can still be passed. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../app/src/cli/utilities/developer-platform-client.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app/src/cli/utilities/developer-platform-client.ts b/packages/app/src/cli/utilities/developer-platform-client.ts index f79054d3c9e..821bf4d2ff3 100644 --- a/packages/app/src/cli/utilities/developer-platform-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client.ts @@ -263,7 +263,10 @@ export interface MigrationDeveloperPlatformClient { migrateToUiExtension: (input: MigrateToUiExtensionVariables) => Promise } -const inProgressRefreshes = new WeakMap>() +/** The subset of a developer platform client needed to refresh an expired token. */ +type TokenRefreshableClient = Pick + +const inProgressRefreshes = new WeakMap>() /** * Creates an unauthorized handler for a developer platform client that will refresh the token @@ -275,7 +278,7 @@ const inProgressRefreshes = new WeakMap * @returns The unauthorized handler. */ export function createUnauthorizedHandler( - client: DeveloperPlatformClient, + client: TokenRefreshableClient, tokenType: 'default' | 'businessPlatform' = 'default', ): UnauthorizedHandler { return {