diff --git a/.changeset/blue-taxes-cry.md b/.changeset/blue-taxes-cry.md new file mode 100644 index 00000000000..1da22306571 --- /dev/null +++ b/.changeset/blue-taxes-cry.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': minor +--- + +Added a separate Dev Console link to the `app dev` output for non-embedded apps diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts index 027caff0bcf..8b2669c2404 100644 --- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts +++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts @@ -17,6 +17,7 @@ export interface DevSessionStatus { isReady: boolean previewURL?: string graphiqlURL?: string + appEmbedded?: boolean statusMessage?: {message: string; type: DevSessionStatusMessageType} } 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 1e9602ce520..3a94d858f46 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 @@ -265,7 +265,7 @@ export class DevSession { const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0 const useDevConsole = firstPartyDev() && hasPreview const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL - this.statusManager.updateStatus({previewURL: newPreviewURL}) + this.statusManager.updateStatus({previewURL: newPreviewURL, appEmbedded: event.app.configuration.embedded}) } /** 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 9a2a3723740..3d6d9f4dd20 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 @@ -125,7 +125,8 @@ export async function setupDevProcesses({ ? `http://localhost:${graphiqlPort}/graphiql?key=${encodeURIComponent(resolvedGraphiqlKey)}` : undefined - const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL}) + const appEmbedded = reloadedApp.configuration.embedded + const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL, appEmbedded}) const processes = [ ...(await setupWebProcesses({ diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx index ac93a0479fc..896547607ea 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx @@ -40,6 +40,7 @@ const initialStatus: DevSessionStatus = { isReady: true, previewURL: 'https://shopify.com', graphiqlURL: 'https://graphiql.shopify.com', + appEmbedded: false, } const onAbort = vi.fn() @@ -121,10 +122,12 @@ describe('DevSessionUI', () => { expect(output).toContain('(q) Quit') // Shortcuts and URLs should be visible - expect(output).toContain('(g) Open GraphiQL') - expect(output).toContain('(p) Preview in your browser') + expect(output).toContain('(g) Open GraphiQL (Admin API)') + expect(output).toContain('(p) Open app preview') + expect(output).toContain('(c) Open Dev Console for extension previews') expect(output).toContain('Preview URL: https://shopify.com') expect(output).toContain('GraphiQL URL: https://graphiql.shopify.com') + expect(output).toContain('Dev Console URL: https://mystore.myshopify.com/admin?dev-console=show') renderInstance.unmount() }) @@ -171,6 +174,55 @@ describe('DevSessionUI', () => { renderInstance.unmount() }) + test('opens the dev console URL when c is pressed for non-embedded apps', async () => { + // Given + devSessionStatusManager.updateStatus({appEmbedded: false}) + + // When + const renderInstance = render( + , + ) + + await waitForInputsToBeReady() + await sendInputAndWait(renderInstance, 10, 'c') + + // Then + expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://mystore.myshopify.com/admin?dev-console=show') + + renderInstance.unmount() + }) + + test('does not show dev console shortcut when app is embedded', async () => { + // Given + devSessionStatusManager.updateStatus({appEmbedded: true}) + + // When + const renderInstance = render( + , + ) + + await waitForInputsToBeReady() + + // Then + const output = unstyled(renderInstance.lastFrame()!) + expect(output).not.toContain('(c) Open Dev Console') + expect(output).not.toContain('Dev Console URL') + + renderInstance.unmount() + }) + test('quits when q is pressed', async () => { // Given const abortController = new AbortController() @@ -356,7 +408,7 @@ describe('DevSessionUI', () => { await waitForInputsToBeReady() // Initial state - expect(unstyled(renderInstance.lastFrame()!)).not.toContain('preview in your browser') + expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Open app preview') // When status updates devSessionStatusManager.updateStatus({ @@ -365,7 +417,7 @@ describe('DevSessionUI', () => { graphiqlURL: 'https://new-graphiql.shopify.com', }) - await waitForContent(renderInstance, 'Preview in your browser') + await waitForContent(renderInstance, 'Open app preview') // Then expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://new-preview-url.shopify.com') diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx index 35f3cf44187..af31a0d79fc 100644 --- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx +++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx @@ -7,6 +7,7 @@ import { DevSessionStatusMessageType, } from '../../processes/dev-session/dev-session-status-manager.js' import {MAX_EXTENSION_HANDLE_LENGTH} from '../../../../models/extensions/schemas.js' +import {buildDevConsoleURL} from '../../../../utilities/app/app-url.js' import {OutputProcess} from '@shopify/cli-kit/node/output' import {Alert, ConcurrentOutput, Link, TabularData} from '@shopify/cli-kit/node/ui/components' import {useAbortSignal} from '@shopify/cli-kit/node/ui/hooks' @@ -147,6 +148,16 @@ const DevSessionUI: FunctionComponent = ({ } }, }, + { + key: 'c', + condition: () => Boolean(!status.appEmbedded && status.isReady), + action: async () => { + await metadata.addPublicMetadata(() => ({ + cmd_dev_preview_url_opened: true, + })) + await openURL(buildDevConsoleURL(shopFqdn)) + }, + }, ], content: ( <> @@ -157,14 +168,19 @@ const DevSessionUI: FunctionComponent = ({ )} {canUseShortcuts && ( - {status.graphiqlURL && status.isReady ? ( + {status.isReady ? ( - {figures.pointerSmall} (g) Open GraphiQL (Admin API) in your browser + {figures.pointerSmall} (p) Open app preview ) : null} - {status.isReady ? ( + {!status.appEmbedded && status.isReady ? ( + + {figures.pointerSmall} (c) Open Dev Console for extension previews + + ) : null} + {status.graphiqlURL && status.isReady ? ( - {figures.pointerSmall} (p) Preview in your browser + {figures.pointerSmall} (g) Open GraphiQL (Admin API) ) : null} @@ -181,6 +197,11 @@ const DevSessionUI: FunctionComponent = ({ Preview URL: ) : null} + {status.appEmbedded === false ? ( + + Dev Console URL: + + ) : null} {status.graphiqlURL ? ( GraphiQL URL: diff --git a/packages/app/src/cli/utilities/app/app-url.ts b/packages/app/src/cli/utilities/app/app-url.ts index 79afa5cd860..f201f1be874 100644 --- a/packages/app/src/cli/utilities/app/app-url.ts +++ b/packages/app/src/cli/utilities/app/app-url.ts @@ -13,6 +13,10 @@ export function buildAppURLForAdmin(storeFqdn: string, apiKey: string, adminDoma return `https://${adminDomain}/store/${storeName}/apps/${apiKey}?dev-console=show` } +export function buildDevConsoleURL(storeFqdn: string) { + return `https://${storeFqdn}/admin?dev-console=show` +} + export function buildAppURLForMobile(storeFqdn: string, apiKey: string) { const normalizedFQDN = normalizeStoreFqdn(storeFqdn) const adminUrl = storeAdminUrl(normalizedFQDN) diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index 918d5775ccc..e9f8a6c98f3 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -366,6 +366,7 @@ export class AppManagementClient implements DeveloperPlatformClient { organizationId: String(numberFromGid(app.organizationId)), grantedScopes: app.activeRoot.grantedShopifyApprovalScopes, applicationUrl: appHomeModule?.config?.app_url as string | undefined, + embedded: appHomeModule?.config?.embedded as boolean | undefined, flags: [], developerPlatformClient: this, }