diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-process.test.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-process.test.ts index a07468fb685..f51e4be5a72 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-process.test.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-process.test.ts @@ -20,6 +20,7 @@ import {AbortSignal, AbortController} from '@shopify/cli-kit/node/abort' import {flushPromises} from '@shopify/cli-kit/node/promises' import * as outputContext from '@shopify/cli-kit/node/ui/components' import {readdir} from '@shopify/cli-kit/node/fs' +import {firstPartyDev, skipLocalDevConsole} from '@shopify/cli-kit/node/context/local' vi.mock('@shopify/cli-kit/node/fs') vi.mock('@shopify/cli-kit/node/archiver') @@ -27,6 +28,14 @@ vi.mock('@shopify/cli-kit/node/http') vi.mock('../../../../utilities/app/app-url.js') vi.mock('node-fetch') vi.mock('../../../bundle.js') +vi.mock('@shopify/cli-kit/node/context/local', async (importOriginal) => { + const original = await importOriginal() + return { + ...original, + firstPartyDev: vi.fn().mockReturnValue(false), + skipLocalDevConsole: vi.fn().mockReturnValue(false), + } +}) describe('setupDevSessionProcess', () => { test('returns a dev session process with correct configuration', async () => { @@ -210,8 +219,10 @@ describe('pushUpdatesForDevSession', () => { contextSpy.mockRestore() }) - test('updates preview URL when extension is previewable', async () => { - // Given + test('updates preview URL to appLocalProxyURL when extension is previewable (dev console shown by default)', async () => { + // Given - dev console is shown by default when skipLocalDevConsole is false + vi.mocked(firstPartyDev).mockReturnValue(false) + vi.mocked(skipLocalDevConsole).mockReturnValue(false) const extension = await testUIExtension({type: 'ui_extension'}) const newApp = testAppLinked({allExtensions: [extension]}) @@ -226,6 +237,25 @@ describe('pushUpdatesForDevSession', () => { expect(devSessionStatusManager.status.previewURL).toBe(options.appLocalProxyURL) }) + test('updates preview URL to appPreviewURL when both skip conditions are met', async () => { + // Given - dev console is skipped only when !firstPartyDev() AND skipLocalDevConsole() + vi.mocked(firstPartyDev).mockReturnValue(false) + vi.mocked(skipLocalDevConsole).mockReturnValue(true) + const extension = await testUIExtension({type: 'ui_extension'}) + const newApp = testAppLinked({allExtensions: [extension]}) + + // When + await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options) + await appWatcher.start({stdout, stderr, signal: abortController.signal}) + await flushPromises() + appWatcher.emit('all', {app: newApp, extensionEvents: [{type: 'updated', extension}]}) + await flushPromises() + + // Then + expect(devSessionStatusManager.status.previewURL).toBe(options.appPreviewURL) + vi.mocked(skipLocalDevConsole).mockReturnValue(false) + }) + test('updates preview URL to appPreviewURL when no previewable extensions', async () => { // Given const extension = await testFlowActionExtension() diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts index a6b8ad098d8..82b3b89f802 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts @@ -10,7 +10,7 @@ import {endHRTimeInMs, startHRTime} from '@shopify/cli-kit/node/hrtime' import {ClientError} from 'graphql-request' 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 {firstPartyDev, isUnitTest, skipLocalDevConsole} from '@shopify/cli-kit/node/context/local' import {dirname, joinPath} from '@shopify/cli-kit/node/path' import {readdir} from '@shopify/cli-kit/node/fs' import {SerialBatchProcessor} from '@shopify/cli-kit/node/serial-batch-processor' @@ -240,11 +240,14 @@ export class DevSession { /** * Update the preview URL, it only changes if we move between a non-previewable state and a previewable state. * (i.e. if we go from a state with no extensions to a state with ui-extensions or vice versa) + * Skip the dev console only when BOTH: SHOPIFY_CLI_1P_DEV is NOT enabled AND SHOPIFY_SKIP_LOCAL_DEV_CONSOLE is set. * @param event - The app event */ private updatePreviewURL(event: AppEvent) { const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0 - const newPreviewURL = hasPreview ? this.options.appLocalProxyURL : this.options.appPreviewURL + const skipDevConsole = !firstPartyDev() && skipLocalDevConsole() + const useDevConsole = !skipDevConsole && hasPreview + const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL this.statusManager.updateStatus({previewURL: newPreviewURL}) } diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index 12bfc659c4e..4b8a460a231 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -35,6 +35,7 @@ import {Config} from '@oclif/core' import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment' import {isStorefrontPasswordProtected} from '@shopify/theme' import {fetchTheme} from '@shopify/cli-kit/node/themes/api' +import {firstPartyDev, skipLocalDevConsole} from '@shopify/cli-kit/node/context/local' vi.mock('../../context/identifiers.js') vi.mock('@shopify/cli-kit/node/session.js') @@ -42,6 +43,7 @@ vi.mock('../fetch.js') vi.mock('@shopify/cli-kit/node/environment') vi.mock('@shopify/theme') vi.mock('@shopify/cli-kit/node/themes/api') +vi.mock('@shopify/cli-kit/node/context/local') beforeEach(() => { // mocked for draft extensions @@ -67,6 +69,10 @@ beforeEach(() => { role: 'theme', processing: false, }) + // By default, firstPartyDev is false (dev console URL only used when enabled) + vi.mocked(firstPartyDev).mockReturnValue(false) + // By default, skipLocalDevConsole is false + vi.mocked(skipLocalDevConsole).mockReturnValue(false) }) const appContextResult = { @@ -162,6 +168,7 @@ describe('setup-dev-processes', () => { graphiqlKey, }) + // Dev console is shown by default (only skipped when !firstPartyDev() AND skipLocalDevConsole()) expect(res.previewUrl).toBe('https://example.com/proxy/extensions/dev-console') expect(res.processes[0]).toMatchObject({ type: 'web', 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 1f5821ff9e5..81cf8500626 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 @@ -22,6 +22,7 @@ import {AppEventWatcher} from '../app-events/app-event-watcher.js' import {reloadApp} from '../../../models/app/loader.js' import {getAvailableTCPPort} from '@shopify/cli-kit/node/tcp' import {isTruthy} from '@shopify/cli-kit/node/context/utilities' +import {firstPartyDev, skipLocalDevConsole} from '@shopify/cli-kit/node/context/local' import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment' import {outputInfo} from '@shopify/cli-kit/node/output' @@ -99,9 +100,12 @@ export async function setupDevProcesses({ const appWatcher = new AppEventWatcher(reloadedApp, network.proxyUrl) // Decide on the appropriate preview URL for a session with these processes + // Skip the dev console only when BOTH: SHOPIFY_CLI_1P_DEV is NOT enabled AND SHOPIFY_SKIP_LOCAL_DEV_CONSOLE is set const anyPreviewableExtensions = reloadedApp.allExtensions.some((ext) => ext.isPreviewable) const devConsoleURL = `${network.proxyUrl}/extensions/dev-console` - const previewURL = anyPreviewableExtensions ? devConsoleURL : appPreviewUrl + const skipDevConsole = !firstPartyDev() && skipLocalDevConsole() + const useDevConsole = !skipDevConsole && anyPreviewableExtensions + const previewURL = useDevConsole ? devConsoleURL : appPreviewUrl const graphiqlURL = shouldRenderGraphiQL ? `http://localhost:${graphiqlPort}/graphiql${graphiqlKey ? `?key=${graphiqlKey}` : ''}` diff --git a/packages/cli-kit/src/private/node/constants.ts b/packages/cli-kit/src/private/node/constants.ts index d76fba74700..6dbe539f8f3 100644 --- a/packages/cli-kit/src/private/node/constants.ts +++ b/packages/cli-kit/src/private/node/constants.ts @@ -19,6 +19,7 @@ export const environmentVariables = { enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT', env: 'SHOPIFY_CLI_ENV', firstPartyDev: 'SHOPIFY_CLI_1P_DEV', + skipLocalDevConsole: 'SHOPIFY_SKIP_LOCAL_DEV_CONSOLE', noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS', partnersToken: 'SHOPIFY_CLI_PARTNERS_TOKEN', runAsUser: 'SHOPIFY_RUN_AS_USER', diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index ce1afcffc57..fde5bc4e490 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -112,6 +112,16 @@ export function firstPartyDev(env = process.env): boolean { return isTruthy(env[environmentVariables.firstPartyDev]) } +/** + * Returns true if the local dev console should be skipped. + * + * @param env - The environment variables from the environment of the current process. + * @returns True if SHOPIFY_SKIP_LOCAL_DEV_CONSOLE is truthy. + */ +export function skipLocalDevConsole(env = process.env): boolean { + return isTruthy(env[environmentVariables.skipLocalDevConsole]) +} + /** * Return gitpodURL if we are running in gitpod. * Https://www.gitpod.io/docs/environment-variables#default-environment-variables.