diff --git a/packages/app/src/cli/services/dev/processes/draftable-extension.ts b/packages/app/src/cli/services/dev/processes/draftable-extension.ts index 6dddab40ad..ef64e9fad9 100644 --- a/packages/app/src/cli/services/dev/processes/draftable-extension.ts +++ b/packages/app/src/cli/services/dev/processes/draftable-extension.ts @@ -1,16 +1,15 @@ import {BaseProcess, DevProcessFunction} from './types.js' import {updateExtensionDraft} from '../update-extension.js' -import {setupExtensionWatcher} from '../extension/bundler.js' import {ExtensionInstance} from '../../../models/extensions/extension-instance.js' import {AppInterface} from '../../../models/app/app.js' import {PartnersAppForIdentifierMatching, ensureDeploymentIdsPresence} from '../../context/identifiers.js' import {getAppIdentifiers} from '../../../models/app/identifiers.js' import {installJavy} from '../../function/build.js' import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js' +import {AppEvent, AppEventWatcher, EventType} from '../app-events/app-event-watcher.js' import {performActionWithRetryAfterRecovery} from '@shopify/cli-kit/common/retry' import {AbortError} from '@shopify/cli-kit/node/error' import {useConcurrentOutputContext} from '@shopify/cli-kit/node/ui/components' -import {outputWarn} from '@shopify/cli-kit/node/output' interface DraftableExtensionOptions { extensions: ExtensionInstance[] @@ -19,6 +18,7 @@ interface DraftableExtensionOptions { remoteExtensionIds: {[key: string]: string} proxyUrl: string localApp: AppInterface + appWatcher: AppEventWatcher } export interface DraftableExtensionProcess extends BaseProcess { @@ -26,8 +26,8 @@ export interface DraftableExtensionProcess extends BaseProcess = async ( - {stderr, stdout, abortSignal: signal}, - {extensions, developerPlatformClient, apiKey, remoteExtensionIds: remoteExtensions, proxyUrl, localApp: app}, + {stderr, stdout}, + {developerPlatformClient, apiKey, remoteExtensionIds: remoteExtensions, localApp: app, appWatcher}, ) => { // Force the download of the javy binary in advance to avoid later problems, // as it might be done multiple times in parallel. https://github.com/Shopify/cli/issues/2877 @@ -37,63 +37,35 @@ export const pushUpdatesForDraftableExtensions: DevProcessFunction { + const handleAppEvent = (event: AppEvent) => { + const extensionEvents = event.extensionEvents + .filter((ev) => ev.type === EventType.Updated) + .filter((ev) => ev.buildResult?.status === 'ok') + + for (const extensionEvent of extensionEvents) { + const extension = extensionEvent.extension + const registrationId = remoteExtensions[extension.localIdentifier] + if (!registrationId) throw new AbortError(`Extension ${extension.localIdentifier} not found on remote app.`) return useConcurrentOutputContext({outputPrefix: extension.outputPrefix}, async () => { - await extension.build({ - app, - stdout, - stderr, - useTasks: false, - signal, - environment: 'development', - }) - const registrationId = remoteExtensions[extension.localIdentifier] - if (!registrationId) throw new AbortError(`Extension ${extension.localIdentifier} not found on remote app.`) - // Initial draft update for each extension - await updateExtensionDraft({ - extension, - developerPlatformClient, - apiKey, - registrationId, - stdout, - stderr, - appConfiguration: app.configuration, - }) - // Watch for changes - return setupExtensionWatcher({ - extension, - app, - url: proxyUrl, - stdout, - stderr, - signal, - onChange: async () => { - // At this point the extension has already been built and is ready to be updated - return performActionWithRetryAfterRecovery( - async () => - updateExtensionDraft({ - extension, - developerPlatformClient, - apiKey, - registrationId, - stdout, - stderr, - appConfiguration: app.configuration, - }), - refreshToken, - ) - }, - onReloadAndBuildError: async (error) => { - const draftUpdateErrorMessage = extension.draftMessages.errorMessage - if (draftUpdateErrorMessage) { - outputWarn(`${draftUpdateErrorMessage}: ${error.message}`, stdout) - } - }, - }) + return performActionWithRetryAfterRecovery( + async () => + updateExtensionDraft({ + extension, + developerPlatformClient, + apiKey, + registrationId, + stdout, + stderr, + appConfiguration: app.configuration, + bundlePath: appWatcher.buildOutputPath, + }), + refreshToken, + ) }) - }), - ) + } + } + + appWatcher.onEvent(handleAppEvent).onStart(handleAppEvent) } export async function setupDraftableExtensionsProcess({ diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts index 5cf563395e..27c528eef3 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts @@ -143,6 +143,7 @@ export async function setupDevProcesses({ apiKey, developerPlatformClient, proxyUrl: network.proxyUrl, + appWatcher, }), await setupPreviewThemeAppExtensionsProcess({ remoteApp, diff --git a/packages/app/src/cli/services/dev/update-extension.test.ts b/packages/app/src/cli/services/dev/update-extension.test.ts index f6a0e299b8..8bc3971ebb 100644 --- a/packages/app/src/cli/services/dev/update-extension.test.ts +++ b/packages/app/src/cli/services/dev/update-extension.test.ts @@ -51,8 +51,9 @@ describe('updateExtensionDraft()', () => { directory: tmpDir, }) - await mkdir(joinPath(tmpDir, 'dist')) - await writeFile(mockExtension.outputPath, 'test content') + await mkdir(joinPath(tmpDir, 'mock-handle', 'dist')) + const outputPath = mockExtension.getOutputPathForDirectory(tmpDir) + await writeFile(outputPath, 'test content') await updateExtensionDraft({ extension: mockExtension, @@ -62,6 +63,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: tmpDir, }) expect(developerPlatformClient.updateExtension).toHaveBeenCalledWith({ @@ -93,6 +95,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: 'dir', }) expect(developerPlatformClient.updateExtension).toHaveBeenCalledWith({ @@ -136,6 +139,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: tmpDir, }) expect(developerPlatformClient.updateExtension).toHaveBeenCalledWith({ @@ -173,6 +177,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: tmpDir, }) expect(developerPlatformClient.updateExtension).toHaveBeenCalledWith({ @@ -211,6 +216,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: tmpDir, }) expect(developerPlatformClient.updateExtension).toHaveBeenCalledWith({ @@ -253,8 +259,9 @@ describe('updateExtensionDraft()', () => { type: 'web_pixel_extension', }) - await mkdir(joinPath(tmpDir, 'dist')) - await writeFile(mockExtension.outputPath, 'test content') + await mkdir(joinPath(tmpDir, mockExtension.handle, 'dist')) + const outputPath = mockExtension.getOutputPathForDirectory(tmpDir) + await writeFile(outputPath, 'test content') await updateExtensionDraft({ extension: mockExtension, @@ -264,6 +271,7 @@ describe('updateExtensionDraft()', () => { stdout, stderr, appConfiguration: placeholderAppConfiguration, + bundlePath: tmpDir, }) expect(stderr.write).toHaveBeenCalledWith('Error while updating drafts: Error1, Error2') diff --git a/packages/app/src/cli/services/dev/update-extension.ts b/packages/app/src/cli/services/dev/update-extension.ts index 156b066050..7227be9774 100644 --- a/packages/app/src/cli/services/dev/update-extension.ts +++ b/packages/app/src/cli/services/dev/update-extension.ts @@ -24,6 +24,7 @@ interface UpdateExtensionDraftOptions { stdout: Writable stderr: Writable appConfiguration: AppConfigurationWithoutPath + bundlePath: string } export async function updateExtensionDraft({ @@ -34,10 +35,12 @@ export async function updateExtensionDraft({ stdout, stderr, appConfiguration, + bundlePath, }: UpdateExtensionDraftOptions) { let encodedFile: string | undefined if (extension.features.includes('esbuild')) { - const content = await readFile(extension.outputPath) + const outputPath = extension.getOutputPathForDirectory(bundlePath) + const content = await readFile(outputPath) if (!content) return encodedFile = Buffer.from(content).toString('base64') }