diff --git a/packages/app/src/cli/commands/app/deploy.ts b/packages/app/src/cli/commands/app/deploy.ts index b985047120..3f7489a7be 100644 --- a/packages/app/src/cli/commands/app/deploy.ts +++ b/packages/app/src/cli/commands/app/deploy.ts @@ -1,13 +1,12 @@ import {appFlags} from '../../flags.js' import {deploy} from '../../services/deploy.js' -import {AppInterface} from '../../models/app/app.js' -import {loadApp} from '../../models/app/loader.js' +import {getAppConfigurationState} from '../../models/app/loader.js' import {validateVersion} from '../../validations/version-name.js' import {showApiKeyDeprecationWarning} from '../../prompts/deprecation-warnings.js' import {validateMessage} from '../../validations/message.js' import metadata from '../../metadata.js' -import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' import AppCommand, {AppCommandOutput} from '../../utilities/app-command.js' +import {linkedAppContext} from '../../services/app-context.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' @@ -100,19 +99,25 @@ export default class Deploy extends AppCommand { cmd_app_reset_used: flags.reset, })) - const app: AppInterface = await loadApp({ + const requiredNonTTYFlags = ['force'] + const configurationState = await getAppConfigurationState(flags.path, flags.config) + if (configurationState.state === 'template-only' && !apiKey) { + requiredNonTTYFlags.push('client-id') + } + this.failMissingNonTTYFlags(flags, requiredNonTTYFlags) + + const {app, remoteApp, developerPlatformClient, organization} = await linkedAppContext({ directory: flags.path, + clientId: apiKey, + forceRelink: flags.reset, userProvidedConfigName: flags.config, - specifications: await loadLocalExtensionsSpecifications(), }) - const requiredNonTTYFlags = ['force'] - if (!apiKey && !app.configuration.client_id) requiredNonTTYFlags.push('client-id') - this.failMissingNonTTYFlags(flags, requiredNonTTYFlags) - const result = await deploy({ app, - apiKey, + remoteApp, + organization, + developerPlatformClient, reset: flags.reset, force: flags.force, noRelease: flags['no-release'], diff --git a/packages/app/src/cli/commands/app/versions/list.ts b/packages/app/src/cli/commands/app/versions/list.ts index 6d1b29b15a..700673819a 100644 --- a/packages/app/src/cli/commands/app/versions/list.ts +++ b/packages/app/src/cli/commands/app/versions/list.ts @@ -48,7 +48,7 @@ export default class VersionsList extends AppCommand { } const apiKey = flags['client-id'] || flags['api-key'] - const {app, remoteApp, developerPlatformClient} = await linkedAppContext({ + const {app, remoteApp, developerPlatformClient, organization} = await linkedAppContext({ directory: flags.path, clientId: apiKey, forceRelink: false, @@ -58,6 +58,7 @@ export default class VersionsList extends AppCommand { await versionList({ app, remoteApp, + organization, developerPlatformClient, json: flags.json, }) 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 6839f6ec98..03794830a6 100644 --- a/packages/app/src/cli/models/app/app.test-data.ts +++ b/packages/app/src/cli/models/app/app.test-data.ts @@ -146,14 +146,14 @@ export function testAppWithLegacyConfig({ return testApp({...app, configuration}) as AppInterface } -export function testAppWithConfig(options?: TestAppWithConfigOptions): AppInterface { - const app = testApp(options?.app, 'current') +export function testAppWithConfig(options?: TestAppWithConfigOptions): AppLinkedInterface { + const app = testAppLinked(options?.app) app.configuration = { ...DEFAULT_CONFIG, ...options?.config, } as CurrentAppConfiguration - return app as AppInterface + return app } export function getWebhookConfig(webhookConfigOverrides?: WebhooksConfig): CurrentAppConfiguration { @@ -166,7 +166,7 @@ export function getWebhookConfig(webhookConfigOverrides?: WebhooksConfig): Curre } } -function testOrganization(): Organization { +export function testOrganization(): Organization { return { id: '1', businessName: 'org1', diff --git a/packages/app/src/cli/services/app-context.test.ts b/packages/app/src/cli/services/app-context.test.ts index 1942472442..3837a0ee2f 100644 --- a/packages/app/src/cli/services/app-context.test.ts +++ b/packages/app/src/cli/services/app-context.test.ts @@ -4,7 +4,8 @@ import link from './app/config/link.js' import {appFromId} from './context.js' import * as localStorage from './local-storage.js' -import {testOrganizationApp, testDeveloperPlatformClient} from '../models/app/app.test-data.js' +import {fetchOrgFromId} from './dev/fetch.js' +import {testOrganizationApp, testDeveloperPlatformClient, testOrganization} from '../models/app/app.test-data.js' import metadata from '../metadata.js' import * as loader from '../models/app/loader.js' import {beforeEach, describe, expect, test, vi} from 'vitest' @@ -15,7 +16,7 @@ import {tryParseInt} from '@shopify/cli-kit/common/string' vi.mock('./generate/fetch-extension-specifications.js') vi.mock('./app/config/link.js') vi.mock('./context.js') - +vi.mock('./dev/fetch.js') async function writeAppConfig(tmp: string, content: string) { const appConfigPath = joinPath(tmp, 'shopify.app.toml') const packageJsonPath = joinPath(tmp, 'package.json') @@ -24,6 +25,7 @@ async function writeAppConfig(tmp: string, content: string) { } const mockDeveloperPlatformClient = testDeveloperPlatformClient() +const mockOrganization = testOrganization() const mockRemoteApp = testOrganizationApp({ apiKey: 'test-api-key', title: 'Test App', @@ -34,6 +36,7 @@ const mockRemoteApp = testOrganizationApp({ beforeEach(() => { vi.mocked(fetchSpecifications).mockResolvedValue([]) vi.mocked(appFromId).mockResolvedValue(mockRemoteApp) + vi.mocked(fetchOrgFromId).mockResolvedValue(mockOrganization) }) describe('linkedAppContext', () => { @@ -62,6 +65,7 @@ describe('linkedAppContext', () => { remoteApp: mockRemoteApp, developerPlatformClient: expect.any(Object), specifications: [], + organization: mockOrganization, }) expect(link).not.toHaveBeenCalled() }) @@ -105,6 +109,7 @@ describe('linkedAppContext', () => { remoteApp: mockRemoteApp, developerPlatformClient: expect.any(Object), specifications: [], + organization: mockOrganization, }) expect(link).toHaveBeenCalledWith({directory: tmp, apiKey: undefined, configName: undefined}) }) diff --git a/packages/app/src/cli/services/app-context.ts b/packages/app/src/cli/services/app-context.ts index 4d4a021bfa..e939038d13 100644 --- a/packages/app/src/cli/services/app-context.ts +++ b/packages/app/src/cli/services/app-context.ts @@ -2,7 +2,8 @@ import {appFromId} from './context.js' import {getCachedAppInfo, setCachedAppInfo} from './local-storage.js' import {fetchSpecifications} from './generate/fetch-extension-specifications.js' import link from './app/config/link.js' -import {OrganizationApp} from '../models/organization.js' +import {fetchOrgFromId} from './dev/fetch.js' +import {Organization, OrganizationApp} from '../models/organization.js' import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../utilities/developer-platform-client.js' import {getAppConfigurationState, loadAppUsingConfigurationState} from '../models/app/loader.js' import {RemoteAwareExtensionSpecification} from '../models/extensions/specification.js' @@ -14,6 +15,7 @@ interface LoadedAppContextOutput { app: AppLinkedInterface remoteApp: OrganizationApp developerPlatformClient: DeveloperPlatformClient + organization: Organization specifications: RemoteAwareExtensionSpecification[] } @@ -77,6 +79,8 @@ export async function linkedAppContext({ } developerPlatformClient = remoteApp.developerPlatformClient ?? developerPlatformClient + const organization = await fetchOrgFromId(remoteApp.organizationId, developerPlatformClient) + // Fetch the remote app's specifications const specifications = await fetchSpecifications({developerPlatformClient, app: remoteApp}) @@ -96,7 +100,7 @@ export async function linkedAppContext({ await logMetadata(remoteApp) - return {app: localApp, remoteApp, developerPlatformClient, specifications} + return {app: localApp, remoteApp, developerPlatformClient, specifications, organization} } async function logMetadata(app: {organizationId: string; apiKey: string}) { diff --git a/packages/app/src/cli/services/context.test.ts b/packages/app/src/cli/services/context.test.ts index 04369c6e82..76ec015783 100644 --- a/packages/app/src/cli/services/context.test.ts +++ b/packages/app/src/cli/services/context.test.ts @@ -2,18 +2,13 @@ import {fetchOrganizations, fetchOrgFromId, fetchStoreByDomain} from './dev/fetc import {selectOrCreateApp} from './dev/select-app.js' import {selectStore, convertToTransferDisabledStoreIfNeeded} from './dev/select-store.js' import {ensureDeploymentIdsPresence} from './context/identifiers.js' -import { - DevContextOptions, - ensureDevContext, - ensureDeployContext, - ensureThemeExtensionDevContext, - DeployContextOptions, -} from './context.js' +import {DevContextOptions, ensureDevContext, ensureDeployContext, ensureThemeExtensionDevContext} from './context.js' import {createExtension} from './dev/create-extension.js' import {CachedAppInfo, clearCachedAppInfo, getCachedAppInfo, setCachedAppInfo} from './local-storage.js' import link from './app/config/link.js' import {fetchSpecifications} from './generate/fetch-extension-specifications.js' import * as writeAppConfigurationFile from './app/write-app-configuration-file.js' +import {DeployOptions} from './deploy.js' import { MinimalAppIdentifiers, Organization, @@ -21,8 +16,8 @@ import { OrganizationSource, OrganizationStore, } from '../models/organization.js' -import {updateAppIdentifiers, getAppIdentifiers} from '../models/app/identifiers.js' -import {reuseDevConfigPrompt, selectOrganizationPrompt} from '../prompts/dev.js' +import {getAppIdentifiers} from '../models/app/identifiers.js' +import {selectOrganizationPrompt} from '../prompts/dev.js' import { DEFAULT_CONFIG, testDeveloperPlatformClient, @@ -30,9 +25,7 @@ import { testAppWithConfig, testOrganizationApp, testThemeExtensions, - testAppConfigExtensions, buildVersionedAppSchema, - testAppWithLegacyConfig, } from '../models/app/app.test-data.js' import metadata from '../metadata.js' import { @@ -42,14 +35,13 @@ import { loadApp, loadAppConfiguration, } from '../models/app/loader.js' -import {AppInterface, CurrentAppConfiguration} from '../models/app/app.js' +import {AppInterface, AppLinkedInterface, CurrentAppConfiguration} from '../models/app/app.js' import * as loadSpecifications from '../models/extensions/load-specifications.js' import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../utilities/developer-platform-client.js' import {RemoteAwareExtensionSpecification} from '../models/extensions/specification.js' import {afterEach, beforeAll, beforeEach, describe, expect, test, vi} from 'vitest' import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' import {getPackageManager} from '@shopify/cli-kit/node/node-package-manager' -import {AbortError} from '@shopify/cli-kit/node/error' import {inTemporaryDirectory, readFile, writeFileSync} from '@shopify/cli-kit/node/fs' import {joinPath} from '@shopify/cli-kit/node/path' import {renderConfirmationPrompt, renderInfo, renderTasks, Task} from '@shopify/cli-kit/node/ui' @@ -139,9 +131,11 @@ const state: AppConfigurationStateLinked = { const remoteApp: OrganizationApp = APP1 -const deployOptions = (app: AppInterface, reset = false, force = false): DeployContextOptions => { +const deployOptions = (app: AppLinkedInterface, reset = false, force = false): DeployOptions => { return { app, + remoteApp: APP2, + organization: ORG1, reset, force, noRelease: false, @@ -838,270 +832,6 @@ api_version = "2023-04" }) describe('ensureDeployContext', () => { - test("fetches the app from the partners' API and returns it alongside the id when identifiers are available locally and the app has no extensions", async () => { - // Given - const app = testAppWithConfig({config: {client_id: APP2.apiKey}}) - const identifiers = { - app: APP2.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - vi.mocked(getAppIdentifiers).mockReturnValue({app: APP2.apiKey}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(buildDeveloperPlatformClient()) - const writeAppConfigurationFileSpy = vi - .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') - .mockResolvedValue() - - // When - const got = await ensureDeployContext(deployOptions(app)) - - // Then - expect(selectOrCreateApp).not.toHaveBeenCalled() - expect(got.remoteApp.id).toEqual(APP2.id) - expect(got.remoteApp.title).toEqual(APP2.title) - expect(got.remoteApp.appType).toEqual(APP2.appType) - expect(got.identifiers).toEqual(identifiers) - expect(got.release).toEqual(true) - - expect(metadata.getAllPublicMetadata()).toMatchObject({api_key: APP2.apiKey, partner_id: 1}) - }) - - test("fetches the app from the partners' API and returns it alongside the id when there are no identifiers but user chooses to reuse dev store.cliKitStore()", async () => { - // Given - const app = testApp() - const identifiers = { - app: APP2.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) - vi.mocked(getCachedAppInfo).mockReturnValue({...CACHED1, appId: 'key2'}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(reuseDevConfigPrompt).mockResolvedValueOnce(true) - vi.mocked(loadApp).mockResolvedValue(app) - - // When - const got = await ensureDeployContext(deployOptions(app)) - - // Then - expect(selectOrCreateApp).not.toHaveBeenCalled() - expect(reuseDevConfigPrompt).toHaveBeenCalled() - expect(got.remoteApp.id).toEqual(APP2.id) - expect(got.remoteApp.title).toEqual(APP2.title) - expect(got.remoteApp.appType).toEqual(APP2.appType) - expect(got.identifiers).toEqual(identifiers) - expect(got.release).toEqual(true) - }) - - test("fetches the app from the partners' API and returns it alongside the id when config as code is enabled", async () => { - // Given - const app = testAppWithConfig({config: {client_id: APP2.apiKey}}) - const identifiers = { - app: APP2.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - - const writeAppConfigurationFileSpy = vi - .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') - .mockResolvedValue() - const opts = deployOptions(app) - - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(opts.developerPlatformClient) - - // When - const got = await ensureDeployContext(opts) - - // Then - expect(selectOrCreateApp).not.toHaveBeenCalled() - expect(reuseDevConfigPrompt).not.toHaveBeenCalled() - expect(opts.developerPlatformClient.appFromId).toHaveBeenCalledWith({ - id: 'no-id-available', - apiKey: APP2.apiKey, - organizationId: '0', - }) - expect(got.remoteApp.id).toEqual(APP2.id) - expect(got.remoteApp.title).toEqual(APP2.title) - expect(got.remoteApp.appType).toEqual(APP2.appType) - expect(got.identifiers).toEqual(identifiers) - expect(got.release).toEqual(true) - writeAppConfigurationFileSpy.mockRestore() - }) - - test('prompts the user to create or select an app and returns it with its id when the app has no extensions', async () => { - // Given - const legacyApp = testAppWithLegacyConfig({config: {}}) - const app = testAppWithConfig({config: {client_id: APP1.apiKey}}) - const identifiers = { - app: APP1.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(legacyApp) - const configuration = {...app.configuration, organization_id: ORG1.id} - vi.mocked(link).mockResolvedValue({configuration, remoteApp, state}) - vi.spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile').mockResolvedValue() - - const developerPlatformClient = buildDeveloperPlatformClient({ - async orgAndApps(_orgId: string) { - return { - organization: ORG1, - apps: [APP1, APP2], - hasMorePages: false, - } - }, - appFromId: () => Promise.resolve(APP2), - }) - const opts: DeployContextOptions = {...deployOptions(legacyApp), developerPlatformClient} - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient) - - // When - const got = await ensureDeployContext(opts) - - // Then - expect(link).toBeCalled() - - expect(updateAppIdentifiers).toBeCalledWith({ - app: legacyApp, - identifiers, - command: 'deploy', - developerPlatformClient, - }) - expect(got.remoteApp.id).toEqual(APP1.id) - expect(got.remoteApp.title).toEqual(APP1.title) - expect(got.remoteApp.appType).toEqual(APP1.appType) - expect(got.identifiers).toEqual({app: APP1.apiKey, extensions: {}, extensionIds: {}, extensionsNonUuidManaged: {}}) - expect(got.release).toEqual(true) - }) - - test("throws an app not found error if the app with the Client ID doesn't exist", async () => { - // Given - const app = testAppWithConfig() - vi.mocked(getAppIdentifiers).mockReturnValue({app: APP1.apiKey}) - vi.mocked(loadApp).mockResolvedValue(app) - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - - const developerPlatformClient = testDeveloperPlatformClient({ - appFromId: vi.fn().mockRejectedValue(new AbortError("Couldn't find the app with Client ID key1")), - }) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient) - - const opts = { - ...deployOptions(app), - developerPlatformClient, - } - - // When - await expect(ensureDeployContext(opts)).rejects.toThrow(/Couldn't find the app with Client ID key1/) - }) - - test('prompts the user to create or select an app if reset is true', async () => { - // Given - const app = testApp() - const identifiers = { - app: APP1.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - - // There is a cached app but it will be ignored - vi.mocked(getAppIdentifiers).mockReturnValue({app: APP2.apiKey}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(link).mockResolvedValue({configuration: (app as any).configuration, remoteApp, state}) - vi.mocked(loadApp).mockResolvedValue(app) - const writeAppConfigurationFileSpy = vi - .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') - .mockResolvedValue() - - const developerPlatformClient = buildDeveloperPlatformClient({ - async orgAndApps(_orgId: string) { - return { - organization: ORG1, - apps: [APP1, APP2], - hasMorePages: false, - } - }, - appFromId: () => Promise.resolve(APP2), - }) - const opts = {...deployOptions(app, true), developerPlatformClient} - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient) - - // When - const got = await ensureDeployContext(opts) - - // Then - expect(fetchOrganizations).toHaveBeenCalledWith() - expect(selectOrCreateApp).toHaveBeenCalledWith( - app.name, - [APP1, APP2], - false, - ORG1, - opts.developerPlatformClient, - DEFAULT_SELECT_APP_OPTIONS, - ) - expect(updateAppIdentifiers).toBeCalledWith({ - app, - identifiers, - command: 'deploy', - developerPlatformClient, - }) - expect(got.remoteApp.id).toEqual(APP1.id) - expect(got.remoteApp.title).toEqual(APP1.title) - expect(got.remoteApp.appType).toEqual(APP1.appType) - expect(got.identifiers).toEqual({app: APP1.apiKey, extensions: {}, extensionIds: {}, extensionsNonUuidManaged: {}}) - expect(got.release).toEqual(true) - writeAppConfigurationFileSpy.mockRestore() - }) - - test('load the app extension using the remote extensions specifications', async () => { - // Given - const app = testAppWithConfig({config: {client_id: APP2.apiKey}}) - const identifiers = { - app: APP2.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - - const appWithExtensions = testApp({ - allExtensions: [await testAppConfigExtensions()], - }) - vi.mocked(getAppIdentifiers).mockReturnValue({app: APP2.apiKey}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(appWithExtensions) - vi.mocked(updateAppIdentifiers).mockResolvedValue(appWithExtensions) - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(buildDeveloperPlatformClient()) - - // When - const got = await ensureDeployContext(deployOptions(app)) - - // Then - expect(selectOrCreateApp).not.toHaveBeenCalled() - expect(got.remoteApp.id).toEqual(APP2.id) - expect(got.remoteApp.title).toEqual(APP2.title) - expect(got.remoteApp.appType).toEqual(APP2.appType) - expect(got.identifiers).toEqual(identifiers) - expect(got.release).toEqual(true) - expect(got.app.allExtensions).toEqual(appWithExtensions.allExtensions) - - expect(metadata.getAllPublicMetadata()).toMatchObject({api_key: APP2.apiKey, partner_id: 1}) - }) - test('prompts the user to include the configuration and persist the flag if the flag is not present', async () => { // Given const app = testAppWithConfig({config: {client_id: APP2.apiKey}}) @@ -1113,11 +843,8 @@ describe('ensureDeployContext', () => { } vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) vi.mocked(renderConfirmationPrompt).mockResolvedValue(true) vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml') - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(buildDeveloperPlatformClient()) const writeAppConfigurationFileSpy = vi .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') @@ -1128,8 +855,8 @@ describe('ensureDeployContext', () => { await ensureDeployContext(deployOptions(app)) // Then - expect(metadataSpyOn).toHaveBeenNthCalledWith(2, expect.any(Function)) - expect(metadataSpyOn.mock.calls[1]![0]()).toEqual({cmd_deploy_confirm_include_config_used: true}) + expect(metadataSpyOn).toHaveBeenNthCalledWith(1, expect.any(Function)) + expect(metadataSpyOn.mock.calls[0]![0]()).toEqual({cmd_deploy_confirm_include_config_used: true}) expect(renderConfirmationPrompt).toHaveBeenCalled() expect(writeAppConfigurationFileSpy).toHaveBeenCalledWith( @@ -1164,13 +891,9 @@ describe('ensureDeployContext', () => { extensionIds: {}, extensionsNonUuidManaged: {}, } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml') - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(buildDeveloperPlatformClient()) const writeAppConfigurationFileSpy = vi .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') .mockResolvedValue() @@ -1230,10 +953,7 @@ describe('ensureDeployContext', () => { await ensureDeployContext(options) // Then - expect(metadataSpyOn).toHaveBeenNthCalledWith(2, expect.any(Function)) - expect(metadataSpyOn.mock.calls[1]![0]()).toEqual( - expect.not.objectContaining({cmd_deploy_confirm_include_config_used: expect.anything()}), - ) + expect(metadataSpyOn).not.toHaveBeenCalled() expect(renderConfirmationPrompt).not.toHaveBeenCalled() expect(writeAppConfigurationFileSpy).not.toHaveBeenCalled() @@ -1265,12 +985,9 @@ describe('ensureDeployContext', () => { extensionIds: {}, extensionsNonUuidManaged: {}, } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml') - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) const writeAppConfigurationFileSpy = vi .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') .mockResolvedValue() @@ -1282,8 +999,8 @@ describe('ensureDeployContext', () => { await ensureDeployContext(deployOptions(app, true)) // Then - expect(metadataSpyOn).toHaveBeenNthCalledWith(2, expect.any(Function)) - expect(metadataSpyOn.mock.calls[1]![0]()).toEqual({cmd_deploy_confirm_include_config_used: false}) + expect(metadataSpyOn).toHaveBeenNthCalledWith(1, expect.any(Function)) + expect(metadataSpyOn.mock.calls[0]![0]()).toEqual({cmd_deploy_confirm_include_config_used: false}) expect(renderConfirmationPrompt).toHaveBeenCalled() expect(writeAppConfigurationFileSpy).toHaveBeenCalledWith( @@ -1318,10 +1035,7 @@ describe('ensureDeployContext', () => { extensionIds: {}, extensionsNonUuidManaged: {}, } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml') const writeAppConfigurationFileSpy = vi @@ -1365,13 +1079,9 @@ describe('ensureDeployContext', () => { extensionIds: {}, extensionsNonUuidManaged: {}, } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(app) vi.mocked(renderConfirmationPrompt).mockResolvedValue(false) vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml') - vi.mocked(link).mockResolvedValue({configuration: app.configuration, remoteApp, state}) - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(buildDeveloperPlatformClient()) const writeAppConfigurationFileSpy = vi .spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile') .mockResolvedValue() @@ -1400,53 +1110,6 @@ describe('ensureDeployContext', () => { }) writeAppConfigurationFileSpy.mockRestore() }) - - test('uses the right developer platform client when it changes', async () => { - // Given - const legacyApp = testAppWithLegacyConfig({config: {}}) - const app = testAppWithConfig({config: {client_id: APP1.apiKey}}) - const identifiers = { - app: APP1.apiKey, - extensions: {}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - vi.mocked(getAppIdentifiers).mockReturnValue({app: undefined}) - vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers) - vi.mocked(loadApp).mockResolvedValue(legacyApp) - const configuration = {...app.configuration, organization_id: ORG1.id} - vi.mocked(link).mockResolvedValue({configuration, remoteApp, state}) - vi.spyOn(writeAppConfigurationFile, 'writeAppConfigurationFile').mockResolvedValue() - - const anotherDeveloperPlatformClient = buildDeveloperPlatformClient() - const appWithAnotherDeveloperPlatformClient = testOrganizationApp({ - id: '2', - title: 'app2', - apiKey: 'key2', - apiSecretKeys: [{secret: 'secret2'}], - developerPlatformClient: anotherDeveloperPlatformClient, - }) - - const developerPlatformClient = testDeveloperPlatformClient({ - orgAndApps: () => - Promise.resolve({ - organization: ORG1, - apps: [APP1, appWithAnotherDeveloperPlatformClient], - hasMorePages: false, - }), - appFromId: () => Promise.resolve(appWithAnotherDeveloperPlatformClient), - }) - - const opts: DeployContextOptions = {...deployOptions(legacyApp), developerPlatformClient} - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient) - - // When - await ensureDeployContext(opts) - - // Then - expect(developerPlatformClient.activeAppVersion).not.toHaveBeenCalled() - expect(anotherDeveloperPlatformClient.activeAppVersion).toHaveBeenCalledOnce() - }) }) describe('ensureThemeExtensionDevContext', () => { diff --git a/packages/app/src/cli/services/context.ts b/packages/app/src/cli/services/context.ts index e56791e74e..60b53e2a1c 100644 --- a/packages/app/src/cli/services/context.ts +++ b/packages/app/src/cli/services/context.ts @@ -8,6 +8,7 @@ import link from './app/config/link.js' import {writeAppConfigurationFile} from './app/write-app-configuration-file.js' import {fetchAppRemoteConfiguration} from './app/select-app.js' import {fetchSpecifications} from './generate/fetch-extension-specifications.js' +import {DeployOptions} from './deploy.js' import {reuseDevConfigPrompt, selectOrganizationPrompt} from '../prompts/dev.js' import { AppConfiguration, @@ -261,13 +262,6 @@ function buildOutput( } } -interface DeployContextOutput { - app: AppInterface - remoteApp: Omit - identifiers: Identifiers - release: boolean -} - /** * If there is a cached ApiKey used for dev, retrieve that and ask the user if they want to reuse it * @param app - The local app object @@ -322,16 +316,6 @@ export async function ensureThemeExtensionDevContext( return registration } -export interface DeployContextOptions { - app: AppInterface - apiKey?: string - reset: boolean - force: boolean - noRelease: boolean - commitReference?: string - developerPlatformClient: DeveloperPlatformClient -} - /** * Make sure there is a valid context to execute `deploy` * That means we have a valid session, organization and app. @@ -344,25 +328,11 @@ export interface DeployContextOptions { * @param developerPlatformClient - The client to access the platform API * @returns The selected org, app and dev store */ -export async function ensureDeployContext(options: DeployContextOptions): Promise { - const {reset, force, noRelease} = options - let developerPlatformClient = options.developerPlatformClient - const enableLinkingPrompt = !options.apiKey && !isCurrentAppSchema(options.app.configuration) - const [remoteApp] = await fetchAppAndIdentifiers(options, developerPlatformClient, true, enableLinkingPrompt) - developerPlatformClient = remoteApp.developerPlatformClient ?? developerPlatformClient +export async function ensureDeployContext(options: DeployOptions): Promise { + const {reset, force, noRelease, app, remoteApp, developerPlatformClient, organization} = options const activeAppVersion = await developerPlatformClient.activeAppVersion(remoteApp) - const specifications = await fetchSpecifications({developerPlatformClient, app: remoteApp}) - const app: AppInterface = await loadApp({ - specifications, - directory: options.app.directory, - userProvidedConfigName: getAppConfigurationShorthand(options.app.configuration.path), - remoteFlags: remoteApp.flags, - }) - - const org = await fetchOrgFromId(remoteApp.organizationId, developerPlatformClient) - - await ensureIncludeConfigOnDeploy({org, app, remoteApp, reset, force}) + await ensureIncludeConfigOnDeploy({org: organization, app, remoteApp, reset, force}) const identifiers = await ensureDeploymentIdsPresence({ app, @@ -376,34 +346,11 @@ export async function ensureDeployContext(options: DeployContextOptions): Promis activeAppVersion, }) - // eslint-disable-next-line no-param-reassign - options = { - ...options, - app: await updateAppIdentifiers({app, identifiers, command: 'deploy', developerPlatformClient}), - } - - const result: DeployContextOutput = { - app: options.app, - remoteApp: { - id: remoteApp.id, - apiKey: remoteApp.apiKey, - title: remoteApp.title, - appType: remoteApp.appType, - organizationId: remoteApp.organizationId, - grantedScopes: remoteApp.grantedScopes, - flags: remoteApp.flags, - developerPlatformClient, - }, - identifiers, - release: !noRelease, - } + await updateAppIdentifiers({app, identifiers, command: 'deploy', developerPlatformClient}) - await logMetadataForLoadedContext({ - organizationId: result.remoteApp.organizationId, - apiKey: result.identifiers.app, - }) - return result + return identifiers } + interface ShouldOrPromptIncludeConfigDeployOptions { appDirectory: string localApp: AppInterface diff --git a/packages/app/src/cli/services/deploy.test.ts b/packages/app/src/cli/services/deploy.test.ts index 122cc9cb94..5f734fb606 100644 --- a/packages/app/src/cli/services/deploy.test.ts +++ b/packages/app/src/cli/services/deploy.test.ts @@ -3,7 +3,6 @@ import {deploy} from './deploy.js' import {uploadExtensionsBundle} from './deploy/upload.js' import {bundleAndBuildExtensions} from './deploy/bundle.js' import { - testApp, testFunctionExtension, testThemeExtensions, testUIExtension, @@ -11,9 +10,11 @@ import { testAppConfigExtensions, DEFAULT_CONFIG, testDeveloperPlatformClient, + testAppLinked, + testOrganization, } from '../models/app/app.test-data.js' import {updateAppIdentifiers} from '../models/app/identifiers.js' -import {AppInterface} from '../models/app/app.js' +import {AppInterface, AppLinkedInterface} from '../models/app/app.js' import {OrganizationApp} from '../models/organization.js' import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js' import {beforeEach, describe, expect, vi, test} from 'vitest' @@ -24,6 +25,10 @@ import {randomUUID} from '@shopify/cli-kit/node/crypto' const versionTag = 'unique-version-tag' const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient() +const remoteApp = testOrganizationApp({ + id: 'app-id', + organizationId: 'org-id', +}) vi.mock('../utilities/app/config/webhooks.js', async () => ({ ...((await vi.importActual('../utilities/app/config/webhooks.js')) as any), @@ -55,20 +60,13 @@ beforeEach(() => { describe('deploy', () => { test('passes release to uploadExtensionsBundle()', async () => { // Given - const app = testApp({allExtensions: []}) + const app = testAppLinked({allExtensions: []}) vi.mocked(renderTextPrompt).mockResolvedValue('Deployed from CLI') // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { noRelease: false, }, @@ -78,7 +76,7 @@ describe('deploy', () => { // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', appModules: [], @@ -90,19 +88,12 @@ describe('deploy', () => { test('passes a message to uploadExtensionsBundle() when a message arg is present', async () => { // Given - const app = testApp() + const app = testAppLinked() // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { message: 'Deployed from CLI with flag', }, @@ -119,19 +110,12 @@ describe('deploy', () => { test('passes a version to uploadExtensionsBundle() when a version arg is present', async () => { // Given - const app = testApp() + const app = testAppLinked() // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { version: '1.1.0', }, @@ -147,27 +131,20 @@ describe('deploy', () => { }) test('deploys the app with no extensions', async () => { - const app = testApp({allExtensions: []}) + const app = testAppLinked({allExtensions: []}) vi.mocked(renderTextPrompt).mockResolvedValueOnce('') // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, developerPlatformClient, }) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', appModules: [], @@ -182,15 +159,15 @@ describe('deploy', () => { test('uploads the extension bundle with 1 UI extension', async () => { // Given const uiExtension = await testUIExtension({type: 'web_pixel_extension'}) - const app = testApp({allExtensions: [uiExtension]}) + const app = testAppLinked({allExtensions: [uiExtension]}) // When - await testDeployBundle({app, developerPlatformClient}) + await testDeployBundle({app, remoteApp, developerPlatformClient}) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', bundlePath: expect.stringMatching(/bundle.zip$/), @@ -215,15 +192,15 @@ describe('deploy', () => { test('uploads the extension bundle with 1 theme extension', async () => { // Given const themeExtension = await testThemeExtensions() - const app = testApp({allExtensions: [themeExtension]}) + const app = testAppLinked({allExtensions: [themeExtension]}) // When - await testDeployBundle({app, developerPlatformClient}) + await testDeployBundle({app, remoteApp, developerPlatformClient}) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', bundlePath: expect.stringMatching(/bundle.zip$/), @@ -252,12 +229,12 @@ describe('deploy', () => { vi.spyOn(functionExtension, 'preDeployValidation').mockImplementation(async () => {}) vi.mocked(randomUUID).mockReturnValue(moduleId) - const app = testApp({allExtensions: [functionExtension]}) + const app = testAppLinked({allExtensions: [functionExtension]}) const mockedFunctionConfiguration = { title: functionExtension.configuration.name, module_id: moduleId, description: functionExtension.configuration.description, - app_key: 'app-id', + app_key: 'api-key', api_type: functionExtension.configuration.type, api_version: functionExtension.configuration.api_version, enable_creation_ui: true, @@ -267,17 +244,14 @@ describe('deploy', () => { // When await testDeployBundle({ app, - remoteApp: testOrganizationApp({ - id: 'app-id', - organizationId: 'org-id', - }), + remoteApp, developerPlatformClient, }) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', appModules: [ @@ -303,16 +277,16 @@ describe('deploy', () => { // Given const uiExtension = await testUIExtension({type: 'web_pixel_extension'}) const themeExtension = await testThemeExtensions() - const app = testApp({allExtensions: [uiExtension, themeExtension]}) + const app = testAppLinked({allExtensions: [uiExtension, themeExtension]}) const commitReference = 'https://github.com/deploytest/repo/commit/d4e5ce7999242b200acde378654d62c14b211bcc' // When - await testDeployBundle({app, released: false, commitReference, developerPlatformClient}) + await testDeployBundle({app, remoteApp, released: false, commitReference, developerPlatformClient}) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', bundlePath: expect.stringMatching(/bundle.zip$/), @@ -350,16 +324,16 @@ describe('deploy', () => { allExtensions: [extensionNonUuidManaged], configuration: {...DEFAULT_CONFIG, build: {include_config_on_deploy: true}}, } - const app = testApp(localApp) + const app = testAppLinked(localApp) const commitReference = 'https://github.com/deploytest/repo/commit/d4e5ce7999242b200acde378654d62c14b211bcc' // When - await testDeployBundle({app, released: false, commitReference, developerPlatformClient}) + await testDeployBundle({app, remoteApp, released: false, commitReference, developerPlatformClient}) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', appModules: [ @@ -384,16 +358,16 @@ describe('deploy', () => { test('doesnt push the configuration extension if include config on deploy is disabled', async () => { // Given const extensionNonUuidManaged = await testAppConfigExtensions() - const app = testApp({allExtensions: [extensionNonUuidManaged]}) + const app = testAppLinked({allExtensions: [extensionNonUuidManaged]}) const commitReference = 'https://github.com/deploytest/repo/commit/d4e5ce7999242b200acde378654d62c14b211bcc' // When - await testDeployBundle({app, released: false, commitReference, developerPlatformClient}) + await testDeployBundle({app, remoteApp, released: false, commitReference, developerPlatformClient}) // Then expect(uploadExtensionsBundle).toHaveBeenCalledWith({ appId: 'app-id', - apiKey: 'app-id', + apiKey: 'api-key', name: app.name, organizationId: 'org-id', appModules: [], @@ -409,20 +383,13 @@ describe('deploy', () => { test('shows a success message', async () => { // Given const uiExtension = await testUIExtension({type: 'web_pixel_extension'}) - const app = testApp({allExtensions: [uiExtension]}) + const app = testAppLinked({allExtensions: [uiExtension]}) vi.mocked(renderTextPrompt).mockResolvedValue('Deployed from CLI') // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { noRelease: false, }, @@ -448,20 +415,13 @@ describe('deploy', () => { test('shows a specific success message when there is an error with the release', async () => { // Given const uiExtension = await testUIExtension({type: 'web_pixel_extension'}) - const app = testApp({allExtensions: [uiExtension]}) + const app = testAppLinked({allExtensions: [uiExtension]}) vi.mocked(renderTextPrompt).mockResolvedValue('Deployed from CLI') // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id2', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { noRelease: false, message: 'version message', @@ -489,20 +449,13 @@ describe('deploy', () => { test('shows a specific success message when deploying --no-release', async () => { // Given const uiExtension = await testUIExtension({type: 'web_pixel_extension'}) - const app = testApp({allExtensions: [uiExtension]}) + const app = testAppLinked({allExtensions: [uiExtension]}) vi.mocked(renderTextPrompt).mockResolvedValue('Deployed from CLI') // When await testDeployBundle({ app, - remoteApp: { - id: 'app-id', - apiKey: 'api-key', - organizationId: 'org-id', - title: 'app-title', - grantedScopes: [], - flags: [], - }, + remoteApp, options: { noRelease: true, message: 'version message', @@ -534,8 +487,8 @@ describe('deploy', () => { }) interface TestDeployBundleInput { - app: AppInterface - remoteApp?: Omit + app: AppLinkedInterface + remoteApp: OrganizationApp options?: { force?: boolean noRelease?: boolean @@ -573,17 +526,7 @@ async function testDeployBundle({ extensionsNonUuidManaged: extensionsNonUuidPayload, } - vi.mocked(ensureDeployContext).mockResolvedValue({ - app: appToDeploy ?? app, - identifiers, - remoteApp: - remoteApp ?? - testOrganizationApp({ - id: 'app-id', - organizationId: 'org-id', - }), - release: !options?.noRelease, - }) + vi.mocked(ensureDeployContext).mockResolvedValue(identifiers) vi.mocked(useThemebundling).mockReturnValue(true) vi.mocked(uploadExtensionsBundle).mockResolvedValue({ @@ -597,6 +540,8 @@ async function testDeployBundle({ await deploy({ app, + remoteApp, + organization: testOrganization(), reset: false, force: Boolean(options?.force), noRelease: Boolean(options?.noRelease), diff --git a/packages/app/src/cli/services/deploy.ts b/packages/app/src/cli/services/deploy.ts index 56e6accdc1..326d842c4c 100644 --- a/packages/app/src/cli/services/deploy.ts +++ b/packages/app/src/cli/services/deploy.ts @@ -1,11 +1,11 @@ -/* eslint-disable require-atomic-updates */ import {uploadThemeExtensions, uploadExtensionsBundle, UploadExtensionsBundleOutput} from './deploy/upload.js' import {ensureDeployContext} from './context.js' import {bundleAndBuildExtensions} from './deploy/bundle.js' -import {AppInterface} from '../models/app/app.js' +import {AppLinkedInterface} from '../models/app/app.js' import {updateAppIdentifiers} from '../models/app/identifiers.js' -import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../utilities/developer-platform-client.js' +import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js' +import {Organization, OrganizationApp} from '../models/organization.js' import {renderInfo, renderSuccess, renderTasks} from '@shopify/cli-kit/node/ui' import {inTemporaryDirectory, mkdir} from '@shopify/cli-kit/node/fs' import {joinPath, dirname} from '@shopify/cli-kit/node/path' @@ -14,12 +14,18 @@ import {useThemebundling} from '@shopify/cli-kit/node/context/local' import {getArrayRejectingUndefined} from '@shopify/cli-kit/common/array' import type {Task} from '@shopify/cli-kit/node/ui' -interface DeployOptions { +export interface DeployOptions { /** The app to be built and uploaded */ - app: AppInterface + app: AppLinkedInterface - /** API key of the app in Partners admin */ - apiKey?: string + /** The remote app to be deployed */ + remoteApp: OrganizationApp + + /** The organization of the remote app */ + organization: Organization + + /** The API client to send authenticated requests */ + developerPlatformClient: DeveloperPlatformClient /** If true, ignore any cached appId or extensionId */ reset: boolean @@ -38,9 +44,6 @@ interface DeployOptions { /** The git reference url of the app version */ commitReference?: string - - /** The API client to send authenticated requests */ - developerPlatformClient?: DeveloperPlatformClient } interface TasksContext { @@ -49,12 +52,11 @@ interface TasksContext { } export async function deploy(options: DeployOptions) { - let developerPlatformClient = - options.developerPlatformClient ?? selectDeveloperPlatformClient({configuration: options.app.configuration}) - // eslint-disable-next-line prefer-const - let {app, identifiers, remoteApp, release} = await ensureDeployContext({...options, developerPlatformClient}) - developerPlatformClient = remoteApp.developerPlatformClient ?? developerPlatformClient - const apiKey = identifiers?.app ?? remoteApp.apiKey + const {app, remoteApp, developerPlatformClient, noRelease} = options + + const identifiers = await ensureDeployContext({...options, developerPlatformClient}) + const release = !noRelease + const apiKey = remoteApp.apiKey outputNewline() if (release) { @@ -122,7 +124,7 @@ export async function deploy(options: DeployOptions) { await uploadThemeExtensions(themeExtensions, {apiKey, identifiers, developerPlatformClient}) } - app = await updateAppIdentifiers({app, identifiers, command: 'deploy', developerPlatformClient}) + await updateAppIdentifiers({app, identifiers, command: 'deploy', developerPlatformClient}) }, }, ] @@ -154,7 +156,7 @@ async function outputCompletionMessage({ release, uploadExtensionsBundleResult, }: { - app: AppInterface + app: AppLinkedInterface release: boolean uploadExtensionsBundleResult: UploadExtensionsBundleOutput }) { diff --git a/packages/app/src/cli/services/function/common.test.ts b/packages/app/src/cli/services/function/common.test.ts index 98a9ed2795..1fd0719823 100644 --- a/packages/app/src/cli/services/function/common.test.ts +++ b/packages/app/src/cli/services/function/common.test.ts @@ -3,6 +3,7 @@ import { testAppLinked, testDeveloperPlatformClient, testFunctionExtension, + testOrganization, testOrganizationApp, } from '../../models/app/app.test-data.js' import {AppLinkedInterface} from '../../models/app/app.js' @@ -34,6 +35,7 @@ beforeEach(async () => { remoteApp: testOrganizationApp(), developerPlatformClient: testDeveloperPlatformClient(), specifications: [], + organization: testOrganization(), }) vi.mocked(renderFatalError).mockReturnValue('') vi.mocked(renderAutocompletePrompt).mockResolvedValue(ourFunction) diff --git a/packages/app/src/cli/services/versions-list.test.ts b/packages/app/src/cli/services/versions-list.test.ts index c5f7d09a30..f2eaf82a5e 100644 --- a/packages/app/src/cli/services/versions-list.test.ts +++ b/packages/app/src/cli/services/versions-list.test.ts @@ -37,6 +37,7 @@ describe('versions-list', () => { await versionList({ app, remoteApp, + organization: ORG1, developerPlatformClient: buildDeveloperPlatformClient(), json: false, }) @@ -53,6 +54,7 @@ describe('versions-list', () => { await versionList({ app, remoteApp, + organization: ORG1, developerPlatformClient: buildDeveloperPlatformClient(), json: false, }) @@ -77,6 +79,7 @@ describe('versions-list', () => { app, remoteApp, json: false, + organization: ORG1, developerPlatformClient, }) @@ -134,6 +137,7 @@ describe('versions-list', () => { remoteApp, json: false, developerPlatformClient, + organization: ORG1, }) // Then @@ -188,6 +192,7 @@ View all 31 app versions in the Test Dashboard ( https://test.shopify.com/org-id remoteApp, json: true, developerPlatformClient, + organization: ORG1, }) // Then diff --git a/packages/app/src/cli/services/versions-list.ts b/packages/app/src/cli/services/versions-list.ts index 5a6c84ef97..fc407e48d6 100644 --- a/packages/app/src/cli/services/versions-list.ts +++ b/packages/app/src/cli/services/versions-list.ts @@ -1,9 +1,8 @@ import {renderCurrentlyUsedConfigInfo} from './context.js' -import {fetchOrgFromId} from './dev/fetch.js' import {AppVersionsQuerySchema} from '../api/graphql/get_versions_list.js' import {AppLinkedInterface} from '../models/app/app.js' import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js' -import {OrganizationApp} from '../models/organization.js' +import {Organization, OrganizationApp} from '../models/organization.js' import colors from '@shopify/cli-kit/node/colors' import {outputContent, outputInfo, outputToken, unstyled} from '@shopify/cli-kit/node/output' import {formatDate} from '@shopify/cli-kit/common/string' @@ -84,22 +83,22 @@ async function fetchAppVersions( interface VersionListOptions { app: AppLinkedInterface remoteApp: OrganizationApp + organization: Organization developerPlatformClient: DeveloperPlatformClient json: boolean } export default async function versionList(options: VersionListOptions) { - const {remoteApp, developerPlatformClient} = options + const {remoteApp, developerPlatformClient, organization} = options const {appVersions, totalResults} = await fetchAppVersions(developerPlatformClient, remoteApp, options.json) - const {businessName: org} = await fetchOrgFromId(remoteApp.organizationId, developerPlatformClient) if (options.json) { return outputInfo(JSON.stringify(appVersions, null, 2)) } renderCurrentlyUsedConfigInfo({ - org, + org: organization.businessName, appName: remoteApp.title, configFile: basename(options.app.configuration.path), })