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
10 changes: 5 additions & 5 deletions packages/app/src/cli/commands/app/init.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Init from './init.js'
import initPrompt from '../../prompts/init/init.js'
import initService from '../../services/init/init.js'
import {selectDeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {defaultDeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {selectOrg} from '../../services/context.js'
import {fetchOrgFromId, NoOrgError} from '../../services/dev/fetch.js'
import {appNamePrompt, createAsNewAppPrompt, selectAppPrompt} from '../../prompts/dev.js'
Expand Down Expand Up @@ -44,7 +44,7 @@ describe('Init command', () => {
vi.mocked(validateTemplateValue).mockReturnValue(undefined)
vi.mocked(validateFlavorValue).mockReturnValue(undefined)
vi.mocked(inferPackageManager).mockReturnValue('npm')
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
vi.mocked(selectOrg).mockResolvedValue(mockOrganization)

// Mock the orgAndApps method on the developer platform client
Expand Down Expand Up @@ -87,7 +87,7 @@ describe('Init command', () => {
vi.mocked(validateTemplateValue).mockReturnValue(undefined)
vi.mocked(validateFlavorValue).mockReturnValue(undefined)
vi.mocked(inferPackageManager).mockReturnValue('npm')
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)

// Mock fetchOrgFromId to return the organization
vi.mocked(fetchOrgFromId).mockResolvedValue(mockOrganization)
Expand Down Expand Up @@ -149,7 +149,7 @@ describe('Init command', () => {
vi.mocked(validateTemplateValue).mockReturnValue(undefined)
vi.mocked(validateFlavorValue).mockReturnValue(undefined)
vi.mocked(inferPackageManager).mockReturnValue('npm')
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)

// Mock fetchOrgFromId to throw NoOrgError for invalid organization
vi.mocked(fetchOrgFromId).mockRejectedValue(
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('Init command', () => {
vi.mocked(validateTemplateValue).mockReturnValue(undefined)
vi.mocked(validateFlavorValue).mockReturnValue(undefined)
vi.mocked(inferPackageManager).mockReturnValue('npm')
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)

// Mock fetchOrgFromId to return the organization
vi.mocked(fetchOrgFromId).mockResolvedValue(mockOrganization)
Expand Down
6 changes: 2 additions & 4 deletions packages/app/src/cli/commands/app/init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import initPrompt, {visibleTemplates} from '../../prompts/init/init.js'
import initService from '../../services/init/init.js'
import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {defaultDeveloperPlatformClient, DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {appFromIdentifiers, selectOrg} from '../../services/context.js'
import {fetchOrgFromId} from '../../services/dev/fetch.js'
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
Expand Down Expand Up @@ -91,7 +91,7 @@ export default class Init extends AppLinkedCommand {
const name = flags.name ?? (await getAppName(flags.path))

// Force user authentication before prompting.
let developerPlatformClient = selectDeveloperPlatformClient()
const developerPlatformClient = defaultDeveloperPlatformClient()
await developerPlatformClient.session()

const promptAnswers = await initPrompt({
Expand All @@ -105,7 +105,6 @@ export default class Init extends AppLinkedCommand {
// If a client-id is provided we don't need to prompt the user and can link directly to that app.
const selectedApp = await appFromIdentifiers({apiKey: flags['client-id']})
appName = selectedApp.title
developerPlatformClient = selectedApp.developerPlatformClient ?? developerPlatformClient
selectAppResult = {result: 'existing', app: selectedApp}
} else {
let org: Organization
Expand All @@ -115,7 +114,6 @@ export default class Init extends AppLinkedCommand {
} else {
org = await selectOrg()
}
developerPlatformClient = selectDeveloperPlatformClient({organization: org})
const {organization, apps, hasMorePages} = await developerPlatformClient.orgAndApps(org.id)
selectAppResult = await selectAppOrNewAppName(
flags.name !== undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import link from './link.js'
import {testOrganizationApp, testDeveloperPlatformClient} from '../../../models/app/app.test-data.js'
import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {defaultDeveloperPlatformClient, DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {OrganizationApp, OrganizationSource} from '../../../models/organization.js'
import {appNamePrompt, createAsNewAppPrompt} from '../../../prompts/dev.js'
import {selectConfigName} from '../../../prompts/config.js'
Expand Down Expand Up @@ -83,7 +83,7 @@ api_version = "2024-01"
writeFileSync(joinPath(tmp, 'package.json'), '{}')

const developerPlatformClient = buildDeveloperPlatformClient()
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(createAsNewAppPrompt).mockResolvedValue(true)
vi.mocked(appNamePrompt).mockResolvedValue('A user provided name')
vi.mocked(selectOrganizationPrompt).mockResolvedValue({
Expand Down Expand Up @@ -176,7 +176,7 @@ api_version = "2025-07"
writeFileSync(joinPath(tmp, 'package.json'), '{}')

const developerPlatformClient = buildDeveloperPlatformClient()
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(createAsNewAppPrompt).mockResolvedValue(true)
vi.mocked(appNamePrompt).mockResolvedValue('My App')
vi.mocked(selectOrganizationPrompt).mockResolvedValue({
Expand Down Expand Up @@ -224,7 +224,7 @@ required = true
writeFileSync(joinPath(tmp, 'package.json'), '{}')

const developerPlatformClient = buildDeveloperPlatformClient()
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(developerPlatformClient)
vi.mocked(createAsNewAppPrompt).mockResolvedValue(true)
vi.mocked(appNamePrompt).mockResolvedValue('My App')
vi.mocked(selectOrganizationPrompt).mockResolvedValue({
Expand Down
12 changes: 4 additions & 8 deletions packages/app/src/cli/services/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ import {
import {getAppConfigurationFileName, isWebType} from '../models/app/loader.js'
import {AppLinkedInterface} from '../models/app/app.js'
import * as loadSpecifications from '../models/extensions/load-specifications.js'
import {
allDeveloperPlatformClients,
DeveloperPlatformClient,
selectDeveloperPlatformClient,
} from '../utilities/developer-platform-client.js'
import {defaultDeveloperPlatformClient, DeveloperPlatformClient} from '../utilities/developer-platform-client.js'
import {RemoteAwareExtensionSpecification} from '../models/extensions/specification.js'
import {selectOrganizationPrompt} from '@shopify/organizations'
import {TomlFile} from '@shopify/cli-kit/node/toml/toml-file'
Expand Down Expand Up @@ -187,7 +183,7 @@ describe('ensureDeployContext', () => {
...deployOptions(app, false, true),
developerPlatformClient: buildDeveloperPlatformClient({}),
}
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(options.developerPlatformClient)
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(options.developerPlatformClient)

// When/Then
await expect(ensureDeployContext(options)).resolves.toBeDefined()
Expand Down Expand Up @@ -381,7 +377,7 @@ describe('appFromIdentifiers', () => {
}),
})

vi.mocked(allDeveloperPlatformClients).mockReturnValue([developerPlatformClient])
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(developerPlatformClient)

await expect(
appFromIdentifiers({
Expand All @@ -407,7 +403,7 @@ describe('appFromIdentifiers', () => {
}),
})

vi.mocked(allDeveloperPlatformClients).mockReturnValue([developerPlatformClient])
vi.mocked(defaultDeveloperPlatformClient).mockReturnValue(developerPlatformClient)

await expect(
appFromIdentifiers({
Expand Down
31 changes: 6 additions & 25 deletions packages/app/src/cli/services/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import {Organization, OrganizationApp, OrganizationSource, OrganizationStore} fr
import metadata from '../metadata.js'
import {getAppConfigurationFileName} from '../models/app/loader.js'

import {
allDeveloperPlatformClients,
CreateAppOptions,
selectDeveloperPlatformClient,
} from '../utilities/developer-platform-client.js'
import {CreateAppOptions, defaultDeveloperPlatformClient} from '../utilities/developer-platform-client.js'
import {selectOrganizationPrompt} from '@shopify/organizations'
import {TomlFile} from '@shopify/cli-kit/node/toml/toml-file'
import {isServiceAccount, isUserAccount} from '@shopify/cli-kit/node/session'
Expand Down Expand Up @@ -62,26 +58,11 @@ interface AppFromIdOptions {
}

export const appFromIdentifiers = async (options: AppFromIdOptions): Promise<OrganizationApp> => {
const allClients = allDeveloperPlatformClients()

let app: OrganizationApp | undefined
for (const client of allClients) {
try {
// eslint-disable-next-line no-await-in-loop
app = await client.appFromIdentifiers(options.apiKey)
if (app) break
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if ('statusCode' in error && error.statusCode === 404) {
// We don't want to throw an error if the first client can't find the app. Try the next client.
continue
}
throw error
}
}
const client = defaultDeveloperPlatformClient()
const app = await client.appFromIdentifiers(options.apiKey)

if (!app) {
const accountInfo = (await allClients[0]?.accountInfo()) ?? {type: 'UnknownAccount'}
const accountInfo = (await client.accountInfo()) ?? {type: 'UnknownAccount'}
let identifier = 'Unknown account'
let isOrg = false

Expand Down Expand Up @@ -194,10 +175,10 @@ function renderWarningAboutIncludeConfigOnDeploy() {
export async function fetchOrCreateOrganizationApp(
options: CreateAppOptions & {organizationId?: string},
): Promise<OrganizationApp> {
const developerPlatformClient = defaultDeveloperPlatformClient()
const org = options.organizationId
? await fetchOrgFromId(options.organizationId, selectDeveloperPlatformClient())
? await fetchOrgFromId(options.organizationId, developerPlatformClient)
: await selectOrg()
const developerPlatformClient = selectDeveloperPlatformClient({organization: org})
const {organization, apps, hasMorePages} = await developerPlatformClient.orgAndApps(org.id)
const remoteApp = await selectOrCreateApp(apps, hasMorePages, organization, developerPlatformClient, options)

Expand Down
30 changes: 1 addition & 29 deletions packages/app/src/cli/services/dev/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import {
testDeveloperPlatformClient,
} from '../../models/app/app.test-data.js'
import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {PartnersClient} from '../../utilities/developer-platform-client/partners-client.js'
import {AppManagementClient} from '../../utilities/developer-platform-client/app-management-client.js'
import {afterEach, describe, expect, test, vi} from 'vitest'
import {renderFatalError} from '@shopify/cli-kit/node/ui'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
import {AbortError} from '@shopify/cli-kit/node/error'
import {blockPartnersAccess} from '@shopify/cli-kit/node/environment'

const ORG1: Organization = {
id: '1',
Expand All @@ -35,40 +33,16 @@ const STORE1: OrganizationStore = {
}

vi.mock('@shopify/cli-kit/node/api/partners')
vi.mock('../../utilities/developer-platform-client/partners-client.js')
vi.mock('../../utilities/developer-platform-client/app-management-client.js')
vi.mock('@shopify/cli-kit/node/environment')

afterEach(() => {
mockAndCaptureOutput().clear()
vi.unstubAllEnvs()
})

describe('fetchOrganizations', async () => {
test('returns fetched organizations from available developer platform clients', async () => {
test('returns fetched organizations from App Management', async () => {
// Given
vi.mocked(blockPartnersAccess).mockReturnValue(false)
const partnersClient: PartnersClient = testDeveloperPlatformClient({
organizations: () => Promise.resolve([ORG1]),
}) as PartnersClient
const appManagementClient: AppManagementClient = testDeveloperPlatformClient({
organizations: () => Promise.resolve([ORG2]),
}) as AppManagementClient
vi.mocked(PartnersClient.getInstance).mockReturnValue(partnersClient)
vi.mocked(AppManagementClient.getInstance).mockReturnValue(appManagementClient)

// When
const got = await fetchOrganizations()

// Then
expect(got).toEqual([ORG2, ORG1])
expect(partnersClient.organizations).toHaveBeenCalled()
expect(appManagementClient.organizations).toHaveBeenCalled()
})

test('returns fetched organizations from App Management for 3P development', async () => {
// Given
vi.mocked(blockPartnersAccess).mockReturnValue(true)
const appManagementClient: AppManagementClient = testDeveloperPlatformClient({
organizations: () => Promise.resolve([ORG2]),
}) as AppManagementClient
Expand All @@ -79,13 +53,11 @@ describe('fetchOrganizations', async () => {

// Then
expect(got).toEqual([ORG2])
expect(PartnersClient.getInstance).not.toHaveBeenCalled()
expect(appManagementClient.organizations).toHaveBeenCalled()
})

test('throws if there are no organizations', async () => {
// Given
vi.mocked(blockPartnersAccess).mockReturnValue(true)
const appManagementClient: AppManagementClient = testDeveloperPlatformClient({
organizations: () => Promise.resolve([]),
}) as AppManagementClient
Expand Down
14 changes: 4 additions & 10 deletions packages/app/src/cli/services/dev/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import {fetchCurrentAccountInformation} from '../context/partner-account-info.js
import {
DeveloperPlatformClient,
Store,
allDeveloperPlatformClients,
selectDeveloperPlatformClient,
defaultDeveloperPlatformClient,
} from '../../utilities/developer-platform-client.js'
import {AccountInfo, isServiceAccount, isUserAccount} from '@shopify/cli-kit/node/session'
import {AbortError} from '@shopify/cli-kit/node/error'
Expand Down Expand Up @@ -76,16 +75,11 @@ export class NoOrgError extends AbortError {
* @returns List of organizations
*/
export async function fetchOrganizations(): Promise<Organization[]> {
const organizations: Organization[] = []
for (const client of allDeveloperPlatformClients()) {
// We don't want to run this in parallel because there could be port conflicts
// eslint-disable-next-line no-await-in-loop
const clientOrganizations = await client.organizations()
organizations.push(...clientOrganizations)
}
const client = defaultDeveloperPlatformClient()
const organizations: Organization[] = await client.organizations()

if (organizations.length === 0) {
const developerPlatformClient = selectDeveloperPlatformClient()
const developerPlatformClient = defaultDeveloperPlatformClient()
const session = await developerPlatformClient.session()
const accountInfo = await fetchCurrentAccountInformation(developerPlatformClient, session.userId)
throw new NoOrgError(accountInfo)
Expand Down
32 changes: 1 addition & 31 deletions packages/app/src/cli/utilities/developer-platform-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {PartnersClient} from './developer-platform-client/partners-client.js'
import {AppManagementClient} from './developer-platform-client/app-management-client.js'
import {
MinimalAppIdentifiers,
Expand Down Expand Up @@ -49,7 +48,6 @@ import {
import {Store} from '../api/graphql/business-platform-organizations/generated/types.js'
import {Session} from '@shopify/cli-kit/node/session'
import {TokenItem} from '@shopify/cli-kit/node/ui'
import {blockPartnersAccess} from '@shopify/cli-kit/node/environment'
import {UnauthorizedHandler} from '@shopify/cli-kit/node/api/graphql'
import {JsonMapType} from '@shopify/cli-kit/node/toml'

Expand All @@ -64,40 +62,12 @@ export type Paginateable<T> = T & {
hasMorePages: boolean
}

interface SelectDeveloperPlatformClientOptions {
organization?: Organization
}

export interface AppVersionIdentifiers {
appVersionId: number
versionId: string
}

export function allDeveloperPlatformClients(): DeveloperPlatformClient[] {
const clients: DeveloperPlatformClient[] = []

clients.push(AppManagementClient.getInstance())

if (!blockPartnersAccess()) {
clients.push(PartnersClient.getInstance())
}

return clients
}

export function selectDeveloperPlatformClient({
organization,
}: SelectDeveloperPlatformClientOptions = {}): DeveloperPlatformClient {
if (organization) return selectDeveloperPlatformClientByOrg(organization)
return defaultDeveloperPlatformClient()
}

function selectDeveloperPlatformClientByOrg(organization: Organization): DeveloperPlatformClient {
if (organization.source === OrganizationSource.BusinessPlatform) return AppManagementClient.getInstance()
return PartnersClient.getInstance()
}

function defaultDeveloperPlatformClient(): DeveloperPlatformClient {
export function defaultDeveloperPlatformClient(): DeveloperPlatformClient {
return AppManagementClient.getInstance()
}

Expand Down
1 change: 0 additions & 1 deletion packages/cli-kit/src/private/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const environmentVariables = {
otelURL: 'SHOPIFY_CLI_OTEL_EXPORTER_OTLP_ENDPOINT',
themeKitAccessDomain: 'SHOPIFY_CLI_THEME_KIT_ACCESS_DOMAIN',
json: 'SHOPIFY_FLAG_JSON',
neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',
skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',
maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',
disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING',
Expand Down
Loading
Loading