From b15c8a4979e1a925b8f4526bbc176f622b3f4ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Tue, 19 Nov 2024 12:38:28 +0100 Subject: [PATCH 1/2] Move action required message to the spec --- .../models/extensions/extension-instance.ts | 10 +++++- .../cli/models/extensions/specification.ts | 14 +++++++- .../specifications/app_config_app_access.ts | 6 ++++ .../dev/processes/dev-session.test.ts | 4 ++- .../cli/services/dev/processes/dev-session.ts | 34 ++++++++++++++----- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/app/src/cli/models/extensions/extension-instance.ts b/packages/app/src/cli/models/extensions/extension-instance.ts index 86f698a829c..6c67924cca2 100644 --- a/packages/app/src/cli/models/extensions/extension-instance.ts +++ b/packages/app/src/cli/models/extensions/extension-instance.ts @@ -22,7 +22,7 @@ import { import {bundleThemeExtension} from '../../services/extensions/bundle.js' import {Identifiers} from '../app/identifiers.js' import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' -import {AppConfigurationWithoutPath} from '../app/app.js' +import {AppConfigurationWithoutPath, CurrentAppConfiguration} from '../app/app.js' import {ok} from '@shopify/cli-kit/node/result' import {constantize, slugify} from '@shopify/cli-kit/common/string' import {hashString, randomUUID} from '@shopify/cli-kit/node/crypto' @@ -418,6 +418,14 @@ export class ExtensionInstance { + if (!this.specification.getDevSessionActionUpdateMessage) return undefined + return this.specification.getDevSessionActionUpdateMessage(this.configuration, appConfig, storeFqdn) + } + private buildHandle() { switch (this.specification.uidStrategy) { case 'single': diff --git a/packages/app/src/cli/models/extensions/specification.ts b/packages/app/src/cli/models/extensions/specification.ts index 3caff410958..90ddbfde137 100644 --- a/packages/app/src/cli/models/extensions/specification.ts +++ b/packages/app/src/cli/models/extensions/specification.ts @@ -4,7 +4,7 @@ import {ExtensionInstance} from './extension-instance.js' import {blocks} from '../../constants.js' import {Flag} from '../../utilities/developer-platform-client.js' -import {AppConfigurationWithoutPath} from '../app/app.js' +import {AppConfigurationWithoutPath, CurrentAppConfiguration} from '../app/app.js' import {loadLocalesConfig} from '../../utilities/extensions/locales-configuration.js' import {Result} from '@shopify/cli-kit/node/result' import {capitalize} from '@shopify/cli-kit/common/string' @@ -62,6 +62,11 @@ export interface ExtensionSpecification) => Promise hasExtensionPointTarget?(config: TConfiguration, target: string): boolean appModuleFeatures: (config?: TConfiguration) => ExtensionFeature[] + getDevSessionActionUpdateMessage?: ( + config: TConfiguration, + appConfig: CurrentAppConfiguration, + storeFqdn: string, + ) => Promise /** * If required, convert configuration from the format used in the local filesystem to that expected by the platform. @@ -167,6 +172,7 @@ export function createExtensionSpecification ExtensionFeature[] transformConfig?: TransformationConfig | CustomTransformationConfig uidStrategy?: UidStrategy + getDevSessionActionUpdateMessage?: ( + config: TConfiguration, + appConfig: CurrentAppConfiguration, + storeFqdn: string, + ) => Promise }): ExtensionSpecification { const appModuleFeatures = spec.appModuleFeatures ?? (() => []) return createExtensionSpecification({ @@ -224,6 +235,7 @@ export function createConfigExtensionSpecification { + const scopesURL = await buildAppURLForWeb(storeFqdn, appConfig.client_id) + return outputContent`Scopes updated. ${outputToken.link('Open app to accept scopes.', scopesURL)}`.value + }, }) export default appAccessSpec diff --git a/packages/app/src/cli/services/dev/processes/dev-session.test.ts b/packages/app/src/cli/services/dev/processes/dev-session.test.ts index 3a4a6a9bed2..e16ccd17c1a 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session.test.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session.test.ts @@ -4,6 +4,7 @@ import {AppLinkedInterface} from '../../../models/app/app.js' import {AppEventWatcher} from '../app-events/app-event-watcher.js' import {buildAppURLForWeb} from '../../../utilities/app/app-url.js' import { + testAppAccessConfigExtension, testAppLinked, testDeveloperPlatformClient, testUIExtension, @@ -165,7 +166,8 @@ describe('pushUpdatesForDevSession', () => { test('handles scope changes and displays action required message', async () => { // Given vi.mocked(buildAppURLForWeb).mockResolvedValue('https://test.myshopify.com/admin/apps/test') - const event = {extensionEvents: [{type: 'updated', extension: {handle: 'app-access'}}], app} + const appAccess = await testAppAccessConfigExtension() + const event = {extensionEvents: [{type: 'updated', extension: appAccess}], app} const contextSpy = vi.spyOn(outputContext, 'useConcurrentOutputContext') // When diff --git a/packages/app/src/cli/services/dev/processes/dev-session.ts b/packages/app/src/cli/services/dev/processes/dev-session.ts index df682b62a0d..c360ae8cd6d 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session.ts @@ -3,7 +3,6 @@ import {DeveloperPlatformClient} from '../../../utilities/developer-platform-cli import {AppLinkedInterface} from '../../../models/app/app.js' import {getExtensionUploadURL} from '../../deploy/upload.js' import {AppEvent, AppEventWatcher} from '../app-events/app-event-watcher.js' -import {buildAppURLForWeb} from '../../../utilities/app/app-url.js' import {readFileSync, writeFile} from '@shopify/cli-kit/node/fs' import {dirname, joinPath} from '@shopify/cli-kit/node/path' import {AbortSignal} from '@shopify/cli-kit/node/abort' @@ -16,6 +15,7 @@ import {useConcurrentOutputContext} from '@shopify/cli-kit/node/ui/components' import {JsonMapType} from '@shopify/cli-kit/node/toml' import {AbortError} from '@shopify/cli-kit/node/error' import {isUnitTest} from '@shopify/cli-kit/node/context/local' +import {getArrayRejectingUndefined} from '@shopify/cli-kit/common/array' import {Writable} from 'stream' interface DevSessionOptions { @@ -141,13 +141,7 @@ async function handleDevSessionResult( ) { if (result.status === 'updated') { await printSuccess(`✅ Updated`, processOptions.stdout) - const scopeChanges = event?.extensionEvents.find((eve) => eve.extension.handle === 'app-access') - if (scopeChanges) { - await printWarning(`🔄 Action required`, processOptions.stdout) - const scopesURL = await buildAppURLForWeb(processOptions.storeFqdn, processOptions.apiKey) - const message = outputContent`└ Scopes updated. ${outputToken.link('Open app to accept scopes.', scopesURL)}` - await printWarning(message.value, processOptions.stdout) - } + await printActionRequiredMessages(processOptions, event) } else if (result.status === 'created') { isDevSessionReady = true await printSuccess(`✅ Ready, watching for changes in your app `, processOptions.stdout) @@ -162,6 +156,30 @@ async function handleDevSessionResult( if (!isDevSessionReady && !isUnitTest()) throw new AbortError('Failed to create dev session') } +/** + * Some extensions may require the user to take some action after an update in the dev session. + * This function will print those action messages to the terminal. + */ +async function printActionRequiredMessages(processOptions: DevSessionProcessOptions, event?: AppEvent) { + if (!event) return + const extensionEvents = event.extensionEvents ?? [] + const warningMessages = getArrayRejectingUndefined( + await Promise.all( + extensionEvents.map((eve) => + eve.extension.getDevSessionActionUpdateMessage(event.app.configuration, processOptions.storeFqdn), + ), + ), + ) + + if (warningMessages.length) { + await printWarning(`🔄 Action required`, processOptions.stdout) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + warningMessages.forEach(async (message) => { + await printWarning(outputContent`└ ${message}`.value, processOptions.stdout) + }) + } +} + /** * Bundle all extensions and upload them to the developer platform * Generate a new manifest in the bundle folder, zip it and upload it to GCS. From 3ef6f86d7bab920630e33752e5d79b32345d2d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Wed, 20 Nov 2024 15:31:32 +0100 Subject: [PATCH 2/2] add tests --- packages/app/src/cli/models/extensions/specification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/cli/models/extensions/specification.ts b/packages/app/src/cli/models/extensions/specification.ts index 90ddbfde137..fffe23a1651 100644 --- a/packages/app/src/cli/models/extensions/specification.ts +++ b/packages/app/src/cli/models/extensions/specification.ts @@ -172,7 +172,7 @@ export function createExtensionSpecification