diff --git a/packages/app/src/cli/commands/app/release.ts b/packages/app/src/cli/commands/app/release.ts index cef7c84f4b..b2ca3dfeef 100644 --- a/packages/app/src/cli/commands/app/release.ts +++ b/packages/app/src/cli/commands/app/release.ts @@ -1,10 +1,9 @@ import {appFlags} from '../../flags.js' -import {AppInterface} from '../../models/app/app.js' -import {loadApp} from '../../models/app/loader.js' import {release} from '../../services/release.js' import {showApiKeyDeprecationWarning} from '../../prompts/deprecation-warnings.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 {getAppConfigurationState} from '../../models/app/loader.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' @@ -64,21 +63,24 @@ export default class Release extends AppCommand { cmd_app_reset_used: flags.reset, })) - const specifications = await loadLocalExtensionsSpecifications() - const app: AppInterface = await loadApp({ - specifications, + 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} = await linkedAppContext({ directory: flags.path, + clientId: apiKey, + forceRelink: flags.reset, userProvidedConfigName: flags.config, }) - const requiredNonTTYFlags = ['force'] - if (!apiKey && !app.configuration.client_id) requiredNonTTYFlags.push('client-id') - this.failMissingNonTTYFlags(flags, requiredNonTTYFlags) - await release({ app, - apiKey, - reset: flags.reset, + remoteApp, + developerPlatformClient, force: flags.force, version: flags.version, }) diff --git a/packages/app/src/cli/services/context.test.ts b/packages/app/src/cli/services/context.test.ts index 30f1c142e3..94c22035d3 100644 --- a/packages/app/src/cli/services/context.test.ts +++ b/packages/app/src/cli/services/context.test.ts @@ -8,7 +8,6 @@ import { ensureDeployContext, ensureThemeExtensionDevContext, DeployContextOptions, - ensureReleaseContext, ensureVersionsListContext, } from './context.js' import {createExtension} from './dev/create-extension.js' @@ -1451,41 +1450,6 @@ describe('ensureDeployContext', () => { }) }) -describe('ensureReleaseContext', () => { - test('updates app identifiers', async () => { - // Given - const app = testApp() - vi.mocked(getAppIdentifiers).mockReturnValue({app: APP2.apiKey}) - vi.mocked(updateAppIdentifiers).mockResolvedValue(app) - const developerPlatformClient = buildDeveloperPlatformClient() - vi.mocked(selectDeveloperPlatformClient).mockReturnValue(developerPlatformClient) - vi.mocked(getCachedAppInfo).mockReturnValue({...CACHED1, appId: 'key2'}) - - // When - const got = await ensureReleaseContext({ - app, - apiKey: 'key2', - reset: false, - force: false, - developerPlatformClient, - }) - - // Then - expect(updateAppIdentifiers).toBeCalledWith({ - app, - identifiers: { - app: APP2.apiKey, - }, - command: 'release', - developerPlatformClient, - }) - - expect(got.app).toEqual(app) - expect(got.remoteApp).toEqual(APP2) - expect(got.developerPlatformClient).toEqual(developerPlatformClient) - }) -}) - describe('ensureThemeExtensionDevContext', () => { test('fetches theme extension when it exists', async () => { // Given diff --git a/packages/app/src/cli/services/context.ts b/packages/app/src/cli/services/context.ts index 9b853cbd39..b0622ee854 100644 --- a/packages/app/src/cli/services/context.ts +++ b/packages/app/src/cli/services/context.ts @@ -261,20 +261,6 @@ function buildOutput( } } -interface ReleaseContextOptions { - app: AppInterface - apiKey?: string - reset: boolean - force: boolean - developerPlatformClient?: DeveloperPlatformClient -} - -interface ReleaseContextOutput { - developerPlatformClient: DeveloperPlatformClient - app: AppInterface - remoteApp: OrganizationApp -} - interface DeployContextOutput { app: AppInterface remoteApp: Omit @@ -477,40 +463,6 @@ function includeConfigOnDeployPrompt(configPath: string): Promise { }) } -/** - * Make sure there is a valid context to execute `release` - * That means we have a valid session, organization and app. - * - * If there is an API key via flag, configuration or env file, we check if it is valid. Otherwise, throw an error. - * If there is no API key (or is invalid), show prompts to select an org and app. - * Finally, the info is updated in the env file. - * - * @param options - Current dev context options - * @returns The selected org, app and dev store - */ -export async function ensureReleaseContext(options: ReleaseContextOptions): Promise { - let developerPlatformClient = - options.developerPlatformClient ?? selectDeveloperPlatformClient({configuration: options.app.configuration}) - const [remoteApp, envIdentifiers] = await fetchAppAndIdentifiers(options, developerPlatformClient, true, true) - developerPlatformClient = remoteApp.developerPlatformClient ?? developerPlatformClient - const identifiers: Identifiers = envIdentifiers as Identifiers - - // eslint-disable-next-line no-param-reassign - options = { - ...options, - app: await updateAppIdentifiers({app: options.app, identifiers, command: 'release', developerPlatformClient}), - } - const result = { - app: options.app, - apiKey: remoteApp.apiKey, - remoteApp, - developerPlatformClient, - } - - await logMetadataForLoadedContext({organizationId: remoteApp.organizationId, apiKey: remoteApp.apiKey}) - return result -} - interface VersionListContextOptions { app: AppInterface apiKey?: string diff --git a/packages/app/src/cli/services/release.test.ts b/packages/app/src/cli/services/release.test.ts index 8f990c7be5..19ba9784b2 100644 --- a/packages/app/src/cli/services/release.test.ts +++ b/packages/app/src/cli/services/release.test.ts @@ -1,12 +1,12 @@ -import {ensureReleaseContext} from './context.js' import {release} from './release.js' import { configExtensionsIdentifiersBreakdown, extensionsIdentifiersReleaseBreakdown, } from './context/breakdown-extensions.js' -import {testApp, testDeveloperPlatformClient} from '../models/app/app.test-data.js' -import {AppInterface} from '../models/app/app.js' +import {testAppLinked, testDeveloperPlatformClient} from '../models/app/app.test-data.js' import {deployOrReleaseConfirmationPrompt} from '../prompts/deploy-release.js' +import {AppLinkedInterface} from '../models/app/app.js' +import {OrganizationApp} from '../models/organization.js' import {beforeEach, describe, expect, vi, test} from 'vitest' import {renderError, renderSuccess, renderTasks, Task} from '@shopify/cli-kit/node/ui' import {AbortSilentError} from '@shopify/cli-kit/node/error' @@ -18,7 +18,7 @@ vi.mock('../api/graphql/app_release.js') vi.mock('./context/breakdown-extensions.js') vi.mock('../prompts/deploy-release.js') -const APP = { +const APP: OrganizationApp = { id: 'app-id', title: 'app-title', apiKey: 'api-key', @@ -47,7 +47,7 @@ beforeEach(() => { describe('release', () => { test("doesn't trigger mutations if the user doesn't confirm", async () => { // Given - const app = testApp() + const app = testAppLinked() vi.mocked(deployOrReleaseConfirmationPrompt).mockResolvedValue(false) // When/Then @@ -56,7 +56,7 @@ describe('release', () => { test('triggers mutations if the user confirms', async () => { // Given - const app = testApp() + const app = testAppLinked() vi.mocked(deployOrReleaseConfirmationPrompt).mockResolvedValue(true) vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => { for (const task of tasks) { @@ -98,7 +98,7 @@ describe('release', () => { test('shows a custom error message with link and message if errors are returned', async () => { // Given - const app = testApp() + const app = testAppLinked() vi.mocked(deployOrReleaseConfirmationPrompt).mockResolvedValue(true) vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => { for (const task of tasks) { @@ -143,23 +143,18 @@ describe('release', () => { }) async function testRelease( - app: AppInterface, + app: AppLinkedInterface, version: string, {developerPlatformClient = testDeveloperPlatformClient()} = {}, ) { // Given - vi.mocked(ensureReleaseContext).mockResolvedValue({ - app, - developerPlatformClient, - remoteApp: APP, - }) - vi.mocked(extensionsIdentifiersReleaseBreakdown).mockResolvedValue(buildExtensionsBreakdown()) vi.mocked(configExtensionsIdentifiersBreakdown).mockResolvedValue(buildConfigExtensionsBreakdown()) await release({ app, - reset: false, + remoteApp: APP, + developerPlatformClient, force: false, version, }) diff --git a/packages/app/src/cli/services/release.ts b/packages/app/src/cli/services/release.ts index e605d13f1f..b00376755c 100644 --- a/packages/app/src/cli/services/release.ts +++ b/packages/app/src/cli/services/release.ts @@ -1,23 +1,24 @@ -import {ensureReleaseContext} from './context.js' import { configExtensionsIdentifiersBreakdown, extensionsIdentifiersReleaseBreakdown, } from './context/breakdown-extensions.js' -import {AppInterface} from '../models/app/app.js' +import {AppLinkedInterface} from '../models/app/app.js' import {AppReleaseSchema} from '../api/graphql/app_release.js' import {deployOrReleaseConfirmationPrompt} from '../prompts/deploy-release.js' +import {OrganizationApp} from '../models/organization.js' +import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js' import {renderError, renderSuccess, renderTasks, TokenItem} from '@shopify/cli-kit/node/ui' import {AbortSilentError} from '@shopify/cli-kit/node/error' interface ReleaseOptions { /** 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 released */ + remoteApp: OrganizationApp - /** If true, ignore any cached appId or extensionId */ - reset: boolean + /** The developer platform client */ + developerPlatformClient: DeveloperPlatformClient /** If true, proceed with deploy without asking for confirmation */ force: boolean @@ -27,7 +28,7 @@ interface ReleaseOptions { } export async function release(options: ReleaseOptions) { - const {developerPlatformClient, app, remoteApp} = await ensureReleaseContext(options) + const {developerPlatformClient, app, remoteApp} = options const {extensionIdentifiersBreakdown, versionDetails} = await extensionsIdentifiersReleaseBreakdown( developerPlatformClient,