Skip to content

Commit 97e0134

Browse files
Merge pull request #6178 from Shopify/07-29-show_post-deploy_message_with_migration_next_steps
Show post-deploy message with migration next steps
2 parents 717e83b + 7e8cefb commit 97e0134

File tree

4 files changed

+190
-5
lines changed

4 files changed

+190
-5
lines changed

packages/app/src/cli/services/context.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,98 @@ describe('ensureDeployContext', () => {
575575
})
576576
unsetAppConfigValueSpy.mockRestore()
577577
})
578+
579+
test('sets didMigrateExtensionsToDevDash to true when app modules are missing registration IDs', async () => {
580+
// Given
581+
const app = testAppWithConfig({config: {client_id: APP2.apiKey}})
582+
const identifiers = {
583+
app: APP2.apiKey,
584+
extensions: {},
585+
extensionIds: {},
586+
extensionsNonUuidManaged: {},
587+
}
588+
vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers)
589+
vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml')
590+
591+
const activeAppVersion = {
592+
appModuleVersions: [
593+
{registrationId: 'id-1', registrationUuid: 'uuid-1', type: 'app_access', registrationTitle: 'module-1'},
594+
{registrationId: '', registrationUuid: 'uuid-2', type: 'pos_ui_extension', registrationTitle: 'module-2'},
595+
{
596+
registrationId: 'id-3',
597+
registrationUuid: 'uuid-3',
598+
type: 'checkout_ui_extension',
599+
registrationTitle: 'module-3',
600+
},
601+
],
602+
}
603+
604+
const developerPlatformClient = buildDeveloperPlatformClient({
605+
supportsAtomicDeployments: true,
606+
activeAppVersion: () => Promise.resolve(activeAppVersion),
607+
})
608+
609+
// When
610+
const result = await ensureDeployContext({
611+
app,
612+
remoteApp: APP2,
613+
organization: ORG1,
614+
reset: false,
615+
force: false,
616+
noRelease: false,
617+
developerPlatformClient,
618+
skipBuild: false,
619+
})
620+
621+
// Then
622+
expect(result.didMigrateExtensionsToDevDash).toBe(true)
623+
})
624+
625+
test('sets didMigrateExtensionsToDevDash to false when all app modules have registration IDs', async () => {
626+
// Given
627+
const app = testAppWithConfig({config: {client_id: APP2.apiKey}})
628+
const identifiers = {
629+
app: APP2.apiKey,
630+
extensions: {},
631+
extensionIds: {},
632+
extensionsNonUuidManaged: {},
633+
}
634+
vi.mocked(ensureDeploymentIdsPresence).mockResolvedValue(identifiers)
635+
vi.mocked(getAppConfigurationFileName).mockReturnValue('shopify.app.toml')
636+
637+
const activeAppVersion = {
638+
appModuleVersions: [
639+
{registrationId: 'id-1', registrationUuid: 'uuid-1', type: 'app_access', registrationTitle: 'module-1'},
640+
{registrationId: 'id-2', registrationUuid: 'uuid-2', type: 'pos_ui_extension', registrationTitle: 'module-2'},
641+
{
642+
registrationId: 'id-3',
643+
registrationUuid: 'uuid-3',
644+
type: 'checkout_ui_extension',
645+
registrationTitle: 'module-3',
646+
},
647+
],
648+
}
649+
650+
const developerPlatformClient = buildDeveloperPlatformClient({
651+
supportsAtomicDeployments: true,
652+
activeAppVersion: () => Promise.resolve(activeAppVersion),
653+
})
654+
655+
// When
656+
const result = await ensureDeployContext({
657+
app,
658+
remoteApp: APP2,
659+
organization: ORG1,
660+
reset: false,
661+
force: false,
662+
noRelease: false,
663+
developerPlatformClient,
664+
skipBuild: false,
665+
})
666+
667+
// Then
668+
expect(result.didMigrateExtensionsToDevDash).toBe(false)
669+
})
578670
})
579671

580672
describe('ensureThemeExtensionDevContext', () => {

packages/app/src/cli/services/context.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export async function ensureThemeExtensionDevContext(
128128
return registration
129129
}
130130

131+
interface EnsureDeployContextResult {
132+
identifiers: Identifiers
133+
didMigrateExtensionsToDevDash: boolean
134+
}
135+
131136
/**
132137
* Make sure there is a valid context to execute `deploy`
133138
* That means we have a valid session, organization and app.
@@ -140,7 +145,7 @@ export async function ensureThemeExtensionDevContext(
140145
* @param developerPlatformClient - The client to access the platform API
141146
* @returns The selected org, app and dev store
142147
*/
143-
export async function ensureDeployContext(options: DeployOptions): Promise<Identifiers> {
148+
export async function ensureDeployContext(options: DeployOptions): Promise<EnsureDeployContextResult> {
144149
const {reset, force, noRelease, app, remoteApp, developerPlatformClient, organization} = options
145150
const activeAppVersion = await developerPlatformClient.activeAppVersion(remoteApp)
146151

@@ -160,7 +165,13 @@ export async function ensureDeployContext(options: DeployOptions): Promise<Ident
160165

161166
await updateAppIdentifiers({app, identifiers, command: 'deploy', developerPlatformClient})
162167

163-
return identifiers
168+
// if the current active app version is missing user_identifiers in some app module, then we are migrating to dev dash
169+
let didMigrateExtensionsToDevDash = false
170+
if (developerPlatformClient.supportsAtomicDeployments && activeAppVersion) {
171+
didMigrateExtensionsToDevDash = activeAppVersion.appModuleVersions.some((version) => !version.registrationId)
172+
}
173+
174+
return {identifiers, didMigrateExtensionsToDevDash}
164175
}
165176

166177
interface ShouldOrPromptIncludeConfigDeployOptions {

packages/app/src/cli/services/deploy.test.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {AppInterface, AppLinkedInterface} from '../models/app/app.js'
2121
import {OrganizationApp} from '../models/organization.js'
2222
import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js'
2323
import {PosSpecIdentifier} from '../models/extensions/specifications/app_config_point_of_sale.js'
24+
import {getTomls} from '../utilities/app/config/getTomls.js'
2425
import {beforeEach, describe, expect, vi, test} from 'vitest'
2526
import {
2627
renderInfo,
@@ -59,6 +60,7 @@ vi.mock('./context/prompts')
5960
vi.mock('./import-extensions.js')
6061
vi.mock('./fetch-extensions.js')
6162
vi.mock('../models/app/loader.js')
63+
vi.mock('../utilities/app/config/getTomls.js')
6264

6365
beforeEach(() => {
6466
// this is needed because using importActual to mock the ui module
@@ -418,6 +420,7 @@ describe('deploy', () => {
418420
// Then
419421
expect(renderSuccess).toHaveBeenCalledWith({
420422
headline: 'New version released to users.',
423+
customSections: [],
421424
body: [
422425
{
423426
link: {
@@ -451,6 +454,7 @@ describe('deploy', () => {
451454
// Then
452455
expect(renderInfo).toHaveBeenCalledWith({
453456
headline: 'New version created, but not released.',
457+
customSections: [],
454458
body: [
455459
{
456460
link: {
@@ -493,6 +497,7 @@ describe('deploy', () => {
493497
},
494498
'\nversion message',
495499
],
500+
customSections: [],
496501
nextSteps: [
497502
[
498503
'Run',
@@ -502,6 +507,45 @@ describe('deploy', () => {
502507
],
503508
})
504509
})
510+
511+
test('shows a custom section when migrating extensions to dev dash', async () => {
512+
// Given
513+
const app = testAppLinked()
514+
515+
vi.mocked(getTomls).mockResolvedValue({
516+
'111': 'shopify.app.prod.toml',
517+
'222': 'shopify.app.stg.toml',
518+
})
519+
520+
// When
521+
await testDeployBundle({app, remoteApp, developerPlatformClient, didMigrateExtensionsToDevDash: true})
522+
523+
// Then
524+
expect(renderSuccess).toHaveBeenCalledWith({
525+
headline: 'New version released to users.',
526+
body: [
527+
{
528+
link: {
529+
label: 'unique-version-tag',
530+
url: 'https://partners.shopify.com/0/apps/0/versions/1',
531+
},
532+
},
533+
'',
534+
],
535+
customSections: [
536+
{
537+
title: 'Next steps',
538+
body: [
539+
'• Map extension IDs to other copies of your app by running',
540+
{command: formatPackageManagerCommand(app.packageManager, 'shopify app deploy')},
541+
'for: ',
542+
{list: {items: ['shopify.app.prod.toml', 'shopify.app.stg.toml']}},
543+
"• Commit to source control to ensure your extension IDs aren't regenerated on the next deploy.",
544+
],
545+
},
546+
],
547+
})
548+
})
505549
})
506550

507551
interface TestDeployBundleInput {
@@ -517,6 +561,7 @@ interface TestDeployBundleInput {
517561
commitReference?: string
518562
appToDeploy?: AppInterface
519563
developerPlatformClient: DeveloperPlatformClient
564+
didMigrateExtensionsToDevDash?: boolean
520565
}
521566

522567
async function testDeployBundle({
@@ -527,6 +572,7 @@ async function testDeployBundle({
527572
commitReference,
528573
appToDeploy,
529574
developerPlatformClient,
575+
didMigrateExtensionsToDevDash = false,
530576
}: TestDeployBundleInput) {
531577
// Given
532578
const extensionsPayload: {[key: string]: string} = {}
@@ -544,7 +590,7 @@ async function testDeployBundle({
544590
extensionsNonUuidManaged: extensionsNonUuidPayload,
545591
}
546592

547-
vi.mocked(ensureDeployContext).mockResolvedValue(identifiers)
593+
vi.mocked(ensureDeployContext).mockResolvedValue({identifiers, didMigrateExtensionsToDevDash})
548594

549595
vi.mocked(uploadExtensionsBundle).mockResolvedValue({
550596
validationErrors: [],

packages/app/src/cli/services/deploy.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js
1010
import {Organization, OrganizationApp} from '../models/organization.js'
1111
import {reloadApp} from '../models/app/loader.js'
1212
import {ExtensionRegistration} from '../api/graphql/all_app_extension_registrations.js'
13+
import {getTomls} from '../utilities/app/config/getTomls.js'
1314
import {renderInfo, renderSuccess, renderTasks, renderConfirmationPrompt, isTTY} from '@shopify/cli-kit/node/ui'
1415
import {mkdir} from '@shopify/cli-kit/node/fs'
1516
import {joinPath, dirname} from '@shopify/cli-kit/node/path'
1617
import {outputNewline, outputInfo, formatPackageManagerCommand} from '@shopify/cli-kit/node/output'
1718
import {getArrayRejectingUndefined} from '@shopify/cli-kit/common/array'
1819
import {AbortError, AbortSilentError} from '@shopify/cli-kit/node/error'
19-
import type {Task} from '@shopify/cli-kit/node/ui'
20+
import type {AlertCustomSection, Task, TokenItem} from '@shopify/cli-kit/node/ui'
2021

2122
export interface DeployOptions {
2223
/** The app to be built and uploaded */
@@ -177,7 +178,11 @@ export async function deploy(options: DeployOptions) {
177178
force,
178179
})
179180

180-
const identifiers = await ensureDeployContext({...options, app, developerPlatformClient})
181+
const {identifiers, didMigrateExtensionsToDevDash} = await ensureDeployContext({
182+
...options,
183+
app,
184+
developerPlatformClient,
185+
})
181186
const release = !noRelease
182187
const apiKey = remoteApp.apiKey
183188

@@ -252,6 +257,7 @@ export async function deploy(options: DeployOptions) {
252257
app,
253258
release,
254259
uploadExtensionsBundleResult,
260+
didMigrateExtensionsToDevDash,
255261
})
256262

257263
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -271,30 +277,60 @@ async function outputCompletionMessage({
271277
app,
272278
release,
273279
uploadExtensionsBundleResult,
280+
didMigrateExtensionsToDevDash,
274281
}: {
275282
app: AppLinkedInterface
276283
release: boolean
277284
uploadExtensionsBundleResult: UploadExtensionsBundleOutput
285+
didMigrateExtensionsToDevDash: boolean
278286
}) {
279287
const linkAndMessage = [
280288
{link: {label: uploadExtensionsBundleResult.versionTag ?? 'version', url: uploadExtensionsBundleResult.location}},
281289
uploadExtensionsBundleResult.message ? `\n${uploadExtensionsBundleResult.message}` : '',
282290
]
291+
let customSections: AlertCustomSection[] = []
292+
if (didMigrateExtensionsToDevDash) {
293+
const tomls = await getTomls(app.directory)
294+
const tomlsWithoutCurrent = Object.values(tomls).filter((toml) => toml !== tomls[app.configuration.client_id])
295+
296+
const body: TokenItem = []
297+
if (tomlsWithoutCurrent.length > 0) {
298+
body.push(
299+
'• Map extension IDs to other copies of your app by running',
300+
{
301+
command: formatPackageManagerCommand(app.packageManager, 'shopify app deploy'),
302+
},
303+
'for: ',
304+
{
305+
list: {
306+
items: tomlsWithoutCurrent,
307+
},
308+
},
309+
)
310+
}
311+
312+
body.push("• Commit to source control to ensure your extension IDs aren't regenerated on the next deploy.")
313+
customSections = [{title: 'Next steps', body}]
314+
}
315+
283316
if (release) {
284317
return uploadExtensionsBundleResult.deployError
285318
? renderInfo({
286319
headline: 'New version created, but not released.',
287320
body: [...linkAndMessage, `\n\n${uploadExtensionsBundleResult.deployError}`],
321+
customSections,
288322
})
289323
: renderSuccess({
290324
headline: 'New version released to users.',
291325
body: linkAndMessage,
326+
customSections,
292327
})
293328
}
294329

295330
return renderSuccess({
296331
headline: 'New version created.',
297332
body: linkAndMessage,
333+
customSections,
298334
nextSteps: [
299335
[
300336
'Run',

0 commit comments

Comments
 (0)