Skip to content

Commit

Permalink
Merge pull request #4637 from Shopify/use-app-context-in-app-release
Browse files Browse the repository at this point in the history
Use app context in `app release`
  • Loading branch information
isaacroldan authored Oct 15, 2024
2 parents 71d9acb + 7c62e42 commit 549e0da
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 119 deletions.
26 changes: 14 additions & 12 deletions packages/app/src/cli/commands/app/release.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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,
})
Expand Down
36 changes: 0 additions & 36 deletions packages/app/src/cli/services/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ensureDeployContext,
ensureThemeExtensionDevContext,
DeployContextOptions,
ensureReleaseContext,
ensureVersionsListContext,
} from './context.js'
import {createExtension} from './dev/create-extension.js'
Expand Down Expand Up @@ -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
Expand Down
48 changes: 0 additions & 48 deletions packages/app/src/cli/services/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrganizationApp, 'apiSecretKeys'>
Expand Down Expand Up @@ -477,40 +463,6 @@ function includeConfigOnDeployPrompt(configPath: string): Promise<boolean> {
})
}

/**
* 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<ReleaseContextOutput> {
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
Expand Down
25 changes: 10 additions & 15 deletions packages/app/src/cli/services/release.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
})
Expand Down
17 changes: 9 additions & 8 deletions packages/app/src/cli/services/release.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand Down

0 comments on commit 549e0da

Please sign in to comment.